API-Docker
view release on metacpan or search on metacpan
lib/API/Docker/Error/Truncated.pm view on Meta::CPAN
has summary => (
is => 'ro',
);
sub as_string { $_[0]->message . $_[0]->location }
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
API::Docker::Error::Truncated - The daemon closed before the response it announced was complete
=head1 VERSION
version 0.004
=head1 SYNOPSIS
# A tar the daemon stopped sending halfway is not a tar.
my $tar = eval { $docker->images->get_tar('busybox') };
if (my $err = $@) {
die $err unless ref $err
&& $err->isa('API::Docker::Error::Truncated');
warn 'got ' . length($err->partial) . ' of '
. $err->expected . ' bytes; retrying';
$tar = $docker->images->get_tar('busybox');
}
=head1 DESCRIPTION
L<API::Docker::Role::HTTP> croaks with an object of this class when the daemon
closed the connection in the middle of a response -- a status line with no
terminator, a header block with no blank line to close it, a body shorter than
its C<Content-Length>, a chunk shorter than its own header, a chunk header cut
in half, or a chunked body with no terminating zero chunk. It is raised in one
more place that is not a closed connection but has the same consequence: a
chunk size line that arrived in full and is not a hexadecimal number, which
would otherwise be misread as a zero chunk and end the body early (see
L</phase>).
It is a structural check, not a heuristic, and it asks one of two questions
depending on how the piece is delimited. Where the response announced a length
it compares what arrived against it. Where the framing is by terminator
instead -- the head, and the chunk headers -- it asks whether the terminator
came before the stream ended, which needs nothing to compare and is just as
decidable. Neither is a guess about content: a header block that never closed
is not a short one, it is an unfinished one.
A body delimited by nothing but the close -- C<attach>,
C<< logs(follow => 1) >>, C</exec/{id}/start>, the whole
C<application/vnd.docker.raw-stream> family -- announces no end and has no
terminator either, so there an EOF B<is> the end and this is never raised.
Its B<head> is framed like any other, and is checked like any other.
=head2 Why it is fatal
For the same reason L<API::Docker::Error::Timeout> is, and the two are the
same defect reached by different routes: a short body satisfies every return
shape this role promises and is indistinguishable from a complete one.
C<ndjson> promises an ArrayRef of events and gets a shorter one; C<raw>
promises the response bytes and gets fewer of them; the default promises the
decoded body and gets whatever the truncated bytes happened to parse as. A
half tarball that looks whole is the worst of them, and it is the case this
exists for.
Nothing is lost by raising it: L</partial> carries the bytes a buffered read
had collected and L</summary> the count a streamed one had delivered, so a
caller who wants what arrived can have it. What it cannot do any more is
mistake it for everything.
=head2 What it is not
Not a timeout. Nothing waited and nothing expired -- the daemon answered, and
then the stream ended early. L<API::Docker::Error::Timeout> is raised when the
daemon goes B<quiet> for longer than a C<read_timeout>, which is a bound the
caller asked for; this needs no option and is on for every request.
Not a status. Where a status line arrived intact it said 200 and the response
after it did not follow; where L</phase> is C<'status-line'> there was no
usable status to begin with. An engine that reports a failure the normal way
raises L<API::Docker::Error::HTTP>, and one that reports it inside an HTTP 200
stream raises L<API::Docker::Error::Stream>. This is the third thing: no
report at all, because the connection went away mid-sentence.
Nor is it the daemon answering nothing whatsoever. A connection that closed
before a single byte of the status line is still the plain
C<No response from Docker daemon> croak it has always been -- there is no
half-sent response to describe, and that string predates every error class
here.
A response with a status of 400 or above raises this rather than
L<API::Docker::Error::HTTP> when B<its> body is the one cut short, which is
the same rule the timeout follows: the transport cannot tell a caller what the
engine said when it did not finish saying it. Read L</partial> for the part of
the error body that did arrive.
=head2 It is still the string it replaces
Like the other three error classes here, this one overloads stringification
(with C<< fallback => 1 >>, so comparison, concatenation, C<sprintf> and
matching all work through it) and produces what a plain C<croak> would have
died with: the reason, followed by Carp's own C< at FILE line N.> location
suffix, naming the same frame.
Unlike the other three it replaces no string, because there was nothing here
to replace -- a truncated response used to be returned rather than raised.
That makes it the one exception in this distribution that existing code cannot
have been catching, which is why it is a documented behaviour change and not a
bug fix in passing.
( run in 0.497 second using v1.01-cache-2.11-cpan-4ef0a570458 )