| package ioutils | |
| import ( | |
| "context" | |
| "io" | |
| ) | |
| // CopyCtx copies from src to dst until either EOF is reached on src or a context is cancelled. | |
| // The writer is not closed when the context is cancelled. | |
| // | |
| // After CopyCtx exits due to context cancellation, the goroutine that performed | |
| // the copy may still be running if either the reader or writer blocks. | |
| func CopyCtx(ctx context.Context, dst io.Writer, src io.Reader) (n int64, err error) { | |
| copyDone := make(chan struct{}) | |
| src = &readerCtx{ctx: ctx, r: src} | |
| go func() { | |
| n, err = io.Copy(dst, src) | |
| close(copyDone) | |
| }() | |
| select { | |
| case <-ctx.Done(): | |
| return -1, ctx.Err() | |
| case <-copyDone: | |
| } | |
| return n, err | |
| } | |
| type readerCtx struct { | |
| ctx context.Context | |
| r io.Reader | |
| } | |
| // NewCtxReader wraps the given reader with a reader that doesn't proceed with | |
| // reading if the context is done. | |
| // | |
| // Note: Read will still block if the underlying reader blocks. | |
| func NewCtxReader(ctx context.Context, r io.Reader) io.Reader { | |
| return &readerCtx{ctx: ctx, r: r} | |
| } | |
| func (r *readerCtx) Read(p []byte) (n int, err error) { | |
| if err := r.ctx.Err(); err != nil { | |
| return 0, err | |
| } | |
| n, outErr := r.r.Read(p) | |
| if err := r.ctx.Err(); err != nil { | |
| return 0, err | |
| } | |
| return n, outErr | |
| } |