From d35d8ec81b75365c2835d6984d337b4d62e0302d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 30 Oct 2025 10:38:42 +0100 Subject: [PATCH] client: Client.ContainerLogs: close reader on context cancellation Use a cancelReadCloser to automatically close the reader when the context is cancelled. Consumers are still recommended to manually close the reader, but the cancelReadCloser makes the Close idempotent. Signed-off-by: Sebastiaan van Stijn --- client/container_logs.go | 21 ++++--------------- integration/container/nat_test.go | 6 +++--- .../moby/moby/client/container_logs.go | 21 ++++--------------- 3 files changed, 11 insertions(+), 37 deletions(-) diff --git a/client/container_logs.go b/client/container_logs.go index 2245fa9149..636ab22129 100644 --- a/client/container_logs.go +++ b/client/container_logs.go @@ -30,6 +30,8 @@ type ContainerLogsResult interface { // ContainerLogs returns the logs generated by a container in an [io.ReadCloser]. // It's up to the caller to close the stream. // +// The underlying [io.ReadCloser] is automatically closed if the context is canceled, +// // The stream format on the response uses one of two formats: // // - If the container is using a TTY, there is only a single stream (stdout) @@ -102,30 +104,15 @@ func (cli *Client) ContainerLogs(ctx context.Context, containerID string, option return nil, err } return &containerLogsResult{ - body: resp.Body, + ReadCloser: newCancelReadCloser(ctx, resp.Body), }, nil } type containerLogsResult struct { - // body must be closed to avoid a resource leak - body io.ReadCloser + io.ReadCloser } var ( _ io.ReadCloser = (*containerLogsResult)(nil) _ ContainerLogsResult = (*containerLogsResult)(nil) ) - -func (r *containerLogsResult) Read(p []byte) (int, error) { - if r == nil || r.body == nil { - return 0, io.EOF - } - return r.body.Read(p) -} - -func (r *containerLogsResult) Close() error { - if r == nil || r.body == nil { - return nil - } - return r.body.Close() -} diff --git a/integration/container/nat_test.go b/integration/container/nat_test.go index d132229bf5..4dd4d4bfbb 100644 --- a/integration/container/nat_test.go +++ b/integration/container/nat_test.go @@ -100,14 +100,14 @@ func TestNetworkLoopbackNat(t *testing.T) { poll.WaitOn(t, container.IsStopped(ctx, apiClient, cID)) - body, err := apiClient.ContainerLogs(ctx, cID, client.ContainerLogsOptions{ + logs, err := apiClient.ContainerLogs(ctx, cID, client.ContainerLogsOptions{ ShowStdout: true, }) assert.NilError(t, err) - defer body.Close() + defer logs.Close() var b bytes.Buffer - _, err = io.Copy(&b, body) + _, err = io.Copy(&b, logs) assert.NilError(t, err) assert.Check(t, is.Equal(msg, strings.TrimSpace(b.String()))) diff --git a/vendor/github.com/moby/moby/client/container_logs.go b/vendor/github.com/moby/moby/client/container_logs.go index 2245fa9149..636ab22129 100644 --- a/vendor/github.com/moby/moby/client/container_logs.go +++ b/vendor/github.com/moby/moby/client/container_logs.go @@ -30,6 +30,8 @@ type ContainerLogsResult interface { // ContainerLogs returns the logs generated by a container in an [io.ReadCloser]. // It's up to the caller to close the stream. // +// The underlying [io.ReadCloser] is automatically closed if the context is canceled, +// // The stream format on the response uses one of two formats: // // - If the container is using a TTY, there is only a single stream (stdout) @@ -102,30 +104,15 @@ func (cli *Client) ContainerLogs(ctx context.Context, containerID string, option return nil, err } return &containerLogsResult{ - body: resp.Body, + ReadCloser: newCancelReadCloser(ctx, resp.Body), }, nil } type containerLogsResult struct { - // body must be closed to avoid a resource leak - body io.ReadCloser + io.ReadCloser } var ( _ io.ReadCloser = (*containerLogsResult)(nil) _ ContainerLogsResult = (*containerLogsResult)(nil) ) - -func (r *containerLogsResult) Read(p []byte) (int, error) { - if r == nil || r.body == nil { - return 0, io.EOF - } - return r.body.Read(p) -} - -func (r *containerLogsResult) Close() error { - if r == nil || r.body == nil { - return nil - } - return r.body.Close() -}