main
Raw Download raw file
  1// Package ctxio provides io.Reader and io.Writer wrappers that
  2// respect context.Contexts. Use these at the interface between
  3// your context code and your io.
  4//
  5// WARNING: read the code. see how writes and reads will continue
  6// until you cancel the io. Maybe this package should provide
  7// versions of io.ReadCloser and io.WriteCloser that automatically
  8// call .Close when the context expires. But for now -- since in my
  9// use cases I have long-lived connections with ephemeral io wrappers
 10// -- this has yet to be a need.
 11package ctxio
 12
 13import (
 14	"io"
 15
 16	context "golang.org/x/net/context"
 17)
 18
 19type ioret struct {
 20	n   int
 21	err error
 22}
 23
 24type Writer interface {
 25	io.Writer
 26}
 27
 28type ctxWriter struct {
 29	w   io.Writer
 30	ctx context.Context
 31}
 32
 33// NewWriter wraps a writer to make it respect given Context.
 34// If there is a blocking write, the returned Writer will return
 35// whenever the context is cancelled (the return values are n=0
 36// and err=ctx.Err().)
 37//
 38// Note well: this wrapper DOES NOT ACTUALLY cancel the underlying
 39// write-- there is no way to do that with the standard go io
 40// interface. So the read and write _will_ happen or hang. So, use
 41// this sparingly, make sure to cancel the read or write as necesary
 42// (e.g. closing a connection whose context is up, etc.)
 43//
 44// Furthermore, in order to protect your memory from being read
 45// _after_ you've cancelled the context, this io.Writer will
 46// first make a **copy** of the buffer.
 47func NewWriter(ctx context.Context, w io.Writer) *ctxWriter {
 48	if ctx == nil {
 49		ctx = context.Background()
 50	}
 51	return &ctxWriter{ctx: ctx, w: w}
 52}
 53
 54func (w *ctxWriter) Write(buf []byte) (int, error) {
 55	buf2 := make([]byte, len(buf))
 56	copy(buf2, buf)
 57
 58	c := make(chan ioret, 1)
 59
 60	go func() {
 61		n, err := w.w.Write(buf2)
 62		c <- ioret{n, err}
 63		close(c)
 64	}()
 65
 66	select {
 67	case r := <-c:
 68		return r.n, r.err
 69	case <-w.ctx.Done():
 70		return 0, w.ctx.Err()
 71	}
 72}
 73
 74type Reader interface {
 75	io.Reader
 76}
 77
 78type ctxReader struct {
 79	r   io.Reader
 80	ctx context.Context
 81}
 82
 83// NewReader wraps a reader to make it respect given Context.
 84// If there is a blocking read, the returned Reader will return
 85// whenever the context is cancelled (the return values are n=0
 86// and err=ctx.Err().)
 87//
 88// Note well: this wrapper DOES NOT ACTUALLY cancel the underlying
 89// write-- there is no way to do that with the standard go io
 90// interface. So the read and write _will_ happen or hang. So, use
 91// this sparingly, make sure to cancel the read or write as necesary
 92// (e.g. closing a connection whose context is up, etc.)
 93//
 94// Furthermore, in order to protect your memory from being read
 95// _before_ you've cancelled the context, this io.Reader will
 96// allocate a buffer of the same size, and **copy** into the client's
 97// if the read succeeds in time.
 98func NewReader(ctx context.Context, r io.Reader) *ctxReader {
 99	return &ctxReader{ctx: ctx, r: r}
100}
101
102func (r *ctxReader) Read(buf []byte) (int, error) {
103	buf2 := make([]byte, len(buf))
104
105	c := make(chan ioret, 1)
106
107	go func() {
108		n, err := r.r.Read(buf2)
109		c <- ioret{n, err}
110		close(c)
111	}()
112
113	select {
114	case ret := <-c:
115		copy(buf, buf2)
116		return ret.n, ret.err
117	case <-r.ctx.Done():
118		return 0, r.ctx.Err()
119	}
120}