client: Client.TaskLogs: 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 <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-10-30 11:36:29 +01:00
parent d35d8ec81b
commit 175e4e5048
2 changed files with 18 additions and 38 deletions

View File

@@ -27,9 +27,14 @@ type TaskLogsResult interface {
io.ReadCloser io.ReadCloser
} }
// TaskLogs returns the logs generated by a task. // TaskLogs returns the logs generated by a service in a [TaskLogsResult].
// It's up to the caller to close the stream. // as an [io.ReadCloser]. Callers should close the stream.
//
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogsOptions) (TaskLogsResult, error) { func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogsOptions) (TaskLogsResult, error) {
// TODO(thaJeztah): this function needs documentation about the format of ths stream (similar to for container logs)
// TODO(thaJeztah): migrate CLI utilities to the client where suitable; https://github.com/docker/cli/blob/v29.0.0-rc.1/cli/command/service/logs.go#L73-L348
query := url.Values{} query := url.Values{}
if options.ShowStdout { if options.ShowStdout {
query.Set("stdout", "1") query.Set("stdout", "1")
@@ -65,30 +70,15 @@ func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogs
return nil, err return nil, err
} }
return &taskLogsResult{ return &taskLogsResult{
body: resp.Body, ReadCloser: newCancelReadCloser(ctx, resp.Body),
}, nil }, nil
} }
type taskLogsResult struct { type taskLogsResult struct {
// body must be closed to avoid a resource leak io.ReadCloser
body io.ReadCloser
} }
var ( var (
_ io.ReadCloser = (*taskLogsResult)(nil) _ io.ReadCloser = (*taskLogsResult)(nil)
_ ContainerLogsResult = (*taskLogsResult)(nil) _ ContainerLogsResult = (*taskLogsResult)(nil)
) )
func (r *taskLogsResult) Read(p []byte) (int, error) {
if r == nil || r.body == nil {
return 0, io.EOF
}
return r.body.Read(p)
}
func (r *taskLogsResult) Close() error {
if r == nil || r.body == nil {
return nil
}
return r.body.Close()
}

View File

@@ -27,9 +27,14 @@ type TaskLogsResult interface {
io.ReadCloser io.ReadCloser
} }
// TaskLogs returns the logs generated by a task. // TaskLogs returns the logs generated by a service in a [TaskLogsResult].
// It's up to the caller to close the stream. // as an [io.ReadCloser]. Callers should close the stream.
//
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogsOptions) (TaskLogsResult, error) { func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogsOptions) (TaskLogsResult, error) {
// TODO(thaJeztah): this function needs documentation about the format of ths stream (similar to for container logs)
// TODO(thaJeztah): migrate CLI utilities to the client where suitable; https://github.com/docker/cli/blob/v29.0.0-rc.1/cli/command/service/logs.go#L73-L348
query := url.Values{} query := url.Values{}
if options.ShowStdout { if options.ShowStdout {
query.Set("stdout", "1") query.Set("stdout", "1")
@@ -65,30 +70,15 @@ func (cli *Client) TaskLogs(ctx context.Context, taskID string, options TaskLogs
return nil, err return nil, err
} }
return &taskLogsResult{ return &taskLogsResult{
body: resp.Body, ReadCloser: newCancelReadCloser(ctx, resp.Body),
}, nil }, nil
} }
type taskLogsResult struct { type taskLogsResult struct {
// body must be closed to avoid a resource leak io.ReadCloser
body io.ReadCloser
} }
var ( var (
_ io.ReadCloser = (*taskLogsResult)(nil) _ io.ReadCloser = (*taskLogsResult)(nil)
_ ContainerLogsResult = (*taskLogsResult)(nil) _ ContainerLogsResult = (*taskLogsResult)(nil)
) )
func (r *taskLogsResult) Read(p []byte) (int, error) {
if r == nil || r.body == nil {
return 0, io.EOF
}
return r.body.Read(p)
}
func (r *taskLogsResult) Close() error {
if r == nil || r.body == nil {
return nil
}
return r.body.Close()
}