lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xio/xio.go (about)

     1  // Copyright (C) 2017-2019  Nexedi SA and Contributors.
     2  //                          Kirill Smelkov <kirr@nexedi.com>
     3  //
     4  // This program is free software: you can Use, Study, Modify and Redistribute
     5  // it under the terms of the GNU General Public License version 3, or (at your
     6  // option) any later version, as published by the Free Software Foundation.
     7  //
     8  // You can also Link and Combine this program with other software covered by
     9  // the terms of any of the Free Software licenses or any of the Open Source
    10  // Initiative approved licenses and Convey the resulting work. Corresponding
    11  // source of such a combination shall include the source code for all other
    12  // software used.
    13  //
    14  // This program is distributed WITHOUT ANY WARRANTY; without even the implied
    15  // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    16  //
    17  // See COPYING file for full licensing terms.
    18  // See https://www.nexedi.com/licensing for rationale and options.
    19  
    20  // Package xio provides addons to standard package io.
    21  //
    22  //   - Reader, Writer, ReadWriter, etc are io analogs that add support for contexts.
    23  //   - BindCtx*(X, ctx) converts xio.X into io.X that implicitly passes ctx
    24  //     to xio.X and can be used in legacy code.
    25  //   - WithCtx*(X) converts io.X back into xio.X that accepts context.
    26  //     It is the opposite operation for BindCtx, but for arbitrary io.X
    27  //     returned xio.X handles context only on best-effort basis. In
    28  //     particular IO cancellation is not reliably handled for os.File .
    29  //   - Pipe amends io.Pipe and creates synchronous in-memory pipe that
    30  //     supports IO cancellation.
    31  //
    32  // Miscellaneous utilities:
    33  //
    34  //   - CountReader provides InputOffset for a Reader.
    35  package xio
    36  
    37  import (
    38  	"context"
    39  	"io"
    40  )
    41  
    42  // Reader is like io.Reader but additionally takes context for Read.
    43  type Reader interface {
    44  	Read(ctx context.Context, dst []byte) (n int, err error)
    45  }
    46  
    47  // Writer is like io.Writer but additionally takes context for Write.
    48  type Writer interface {
    49  	Write(ctx context.Context, src []byte) (n int, err error)
    50  }
    51  
    52  // ReadWriter combines Reader and Writer.
    53  type ReadWriter interface {
    54  	Reader
    55  	Writer
    56  }
    57  
    58  // ReadCloser combines Reader and io.Closer.
    59  type ReadCloser interface {
    60  	Reader
    61  	io.Closer
    62  }
    63  
    64  // WriteCloser combines Writer and io.Closer.
    65  type WriteCloser interface {
    66  	Writer
    67  	io.Closer
    68  }
    69  
    70  // ReadWriteCloser combines Reader, Writer and io.Closer.
    71  type ReadWriteCloser interface {
    72  	Reader
    73  	Writer
    74  	io.Closer
    75  }
    76  
    77  
    78  // BindCtx*(xio.X, ctx) -> io.X
    79  //
    80  // XXX better just BindCtx(x T, ctx) -> T with all x IO methods without ctx,
    81  // but that needs either generics, or support from reflect to preserve optional
    82  // methods: https://github.com/golang/go/issues/16522.
    83  
    84  
    85  // BindCtxR binds Reader r and ctx into io.Reader which passes ctx to r on every Read.
    86  func BindCtxR(r Reader, ctx context.Context) io.Reader {
    87  	// BindCtx(WithCtx(X), BG) = X
    88  	if ctx.Done() == nil {
    89  		switch s := r.(type) {
    90  		case *stubCtxR:   return s.r
    91  		case *stubCtxRW:  return s.rw
    92  		case *stubCtxRC:  return s.r
    93  		case *stubCtxRWC: return s.rw
    94  		}
    95  	}
    96  
    97  	return &bindCtxR{r, ctx}
    98  }
    99  type bindCtxR struct {r Reader; ctx context.Context}
   100  func (b *bindCtxR) Read(dst []byte) (int, error)	{ return b.r.Read(b.ctx, dst) }
   101  
   102  // BindCtxW binds Writer w and ctx into io.Writer which passes ctx to w on every Write.
   103  func BindCtxW(w Writer, ctx context.Context) io.Writer {
   104  	if ctx.Done() == nil {
   105  		switch s := w.(type) {
   106  		case *stubCtxW:   return s.w
   107  		case *stubCtxRW:  return s.rw
   108  		case *stubCtxWC:  return s.w
   109  		case *stubCtxRWC: return s.rw
   110  		}
   111  	}
   112  	return &bindCtxW{w, ctx}
   113  }
   114  type bindCtxW struct {w Writer; ctx context.Context}
   115  func (b *bindCtxW) Write(src []byte) (int, error)	{ return b.w.Write(b.ctx, src)	}
   116  
   117  // BindCtxRW binds ReadWriter rw and ctx into io.ReadWriter which passes ctx to
   118  // rw on every Read and Write.
   119  func BindCtxRW(rw ReadWriter, ctx context.Context) io.ReadWriter {
   120  	if ctx.Done() == nil {
   121  		switch s := rw.(type) {
   122  		case *stubCtxRW:  return s.rw
   123  		case *stubCtxRWC: return s.rw
   124  		}
   125  	}
   126  	return &bindCtxRW{rw, ctx}
   127  }
   128  type bindCtxRW struct {rw ReadWriter; ctx context.Context}
   129  func (b *bindCtxRW) Read (dst []byte) (int, error)	{ return b.rw.Read (b.ctx, dst) }
   130  func (b *bindCtxRW) Write(src []byte) (int, error)	{ return b.rw.Write(b.ctx, src) }
   131  
   132  // BindCtxRC binds ReadCloser r and ctx into io.ReadCloser which passes ctx to r on every Read.
   133  func BindCtxRC(r ReadCloser, ctx context.Context) io.ReadCloser {
   134  	if ctx.Done() == nil {
   135  		switch s := r.(type) {
   136  		case *stubCtxRC:  return s.r
   137  		case *stubCtxRWC: return s.rw
   138  		}
   139  	}
   140  	return &bindCtxRC{r, ctx}
   141  }
   142  type bindCtxRC struct {r ReadCloser; ctx context.Context}
   143  func (b *bindCtxRC) Read(dst []byte) (int, error)	{ return b.r.Read(b.ctx, dst)	}
   144  func (b *bindCtxRC) Close() error			{ return b.r.Close()		}
   145  
   146  // BindCtxWC binds WriteCloser w and ctx into io.WriteCloser which passes ctx to w on every Write.
   147  func BindCtxWC(w WriteCloser, ctx context.Context) io.WriteCloser {
   148  	if ctx.Done() == nil {
   149  		switch s := w.(type) {
   150  		case *stubCtxWC:  return s.w
   151  		case *stubCtxRWC: return s.rw
   152  		}
   153  	}
   154  	return &bindCtxWC{w, ctx}
   155  }
   156  type bindCtxWC struct {w WriteCloser; ctx context.Context}
   157  func (b *bindCtxWC) Write(src []byte) (int, error)	{ return b.w.Write(b.ctx, src)	}
   158  func (b *bindCtxWC) Close() error			{ return b.w.Close()		}
   159  
   160  // BindCtxRWC binds ReadWriteCloser rw and ctx into io.ReadWriteCloser
   161  // which passes ctx to rw on every Read and Write.
   162  func BindCtxRWC(rw ReadWriteCloser, ctx context.Context) io.ReadWriteCloser {
   163  	if ctx.Done() == nil {
   164  		switch s := rw.(type) {
   165  		case *stubCtxRWC: return s.rw
   166  		}
   167  	}
   168  	return &bindCtxRWC{rw, ctx}
   169  }
   170  type bindCtxRWC struct {rw ReadWriteCloser; ctx context.Context}
   171  func (b *bindCtxRWC) Read(dst []byte) (int, error)	{ return b.rw.Read(b.ctx, dst)	}
   172  func (b *bindCtxRWC) Write(src []byte) (int, error)	{ return b.rw.Write(b.ctx, src)	}
   173  func (b *bindCtxRWC) Close() error			{ return b.rw.Close()		}
   174  
   175  
   176  // WithCtx*(io.X) -> xio.X that handles ctx on best-effort basis.
   177  //
   178  // FIXME for arbitrary io.X for now ctx is completely ignored.
   179  // TODO add support for cancellation if io.X provides working .Set{Read/Write}Deadline:
   180  // https://medium.com/@zombiezen/canceling-i-o-in-go-capn-proto-5ae8c09c5b29
   181  // https://github.com/golang/go/issues/20280
   182  
   183  // WithCtxR converts io.Reader r into Reader that accepts ctx.
   184  //
   185  // It returns original IO object if r was created via BindCtx*, but in general
   186  // returned Reader will handle context only on best-effort basis.
   187  func WithCtxR(r io.Reader) Reader {
   188  	// WithCtx(BindCtx(X)) = X
   189  	switch b := r.(type) {
   190  	case *bindCtxR:   return b.r
   191  	case *bindCtxRW:  return b.rw
   192  	case *bindCtxRC:  return b.r
   193  	case *bindCtxRWC: return b.rw
   194  	}
   195  
   196  	return &stubCtxR{r}
   197  }
   198  type stubCtxR struct {r io.Reader}
   199  func (s *stubCtxR) Read(ctx context.Context, dst []byte) (int, error)	{ return s.r.Read(dst) }
   200  
   201  // WithCtxW converts io.Writer w into Writer that accepts ctx.
   202  //
   203  // It returns original IO object if w was created via BindCtx*, but in general
   204  // returned Writer will handle context only on best-effort basis.
   205  func WithCtxW(w io.Writer) Writer {
   206  	switch b := w.(type) {
   207  	case *bindCtxW:   return b.w
   208  	case *bindCtxRW:  return b.rw
   209  	case *bindCtxWC:  return b.w
   210  	case *bindCtxRWC: return b.rw
   211  	}
   212  	return &stubCtxW{w}
   213  }
   214  type stubCtxW struct {w io.Writer}
   215  func (s *stubCtxW) Write(ctx context.Context, src []byte) (int, error)	{ return s.w.Write(src) }
   216  
   217  // WithCtxRW converts io.ReadWriter rw into ReadWriter that accepts ctx.
   218  //
   219  // It returns original IO object if rw was created via BindCtx*, but in general
   220  // returned ReadWriter will handle context only on best-effort basis.
   221  func WithCtxRW(rw io.ReadWriter) ReadWriter {
   222  	switch b := rw.(type) {
   223  	case *bindCtxRW:  return b.rw
   224  	case *bindCtxRWC: return b.rw
   225  	}
   226  	return &stubCtxRW{rw}
   227  }
   228  type stubCtxRW struct {rw io.ReadWriter}
   229  func (s *stubCtxRW) Read (ctx context.Context, dst []byte) (int, error)	{ return s.rw.Read (dst) }
   230  func (s *stubCtxRW) Write(ctx context.Context, src []byte) (int, error)	{ return s.rw.Write(src) }
   231  
   232  // WithCtxRC converts io.ReadCloser r into ReadCloser that accepts ctx.
   233  //
   234  // It returns original IO object if r was created via BindCtx*, but in general
   235  // returned ReadCloser will handle context only on best-effort basis.
   236  func WithCtxRC(r io.ReadCloser) ReadCloser {
   237  	switch b := r.(type) {
   238  	case *bindCtxRC:  return b.r
   239  	case *bindCtxRWC: return b.rw
   240  	}
   241  	return &stubCtxRC{r}
   242  }
   243  type stubCtxRC struct {r io.ReadCloser}
   244  func (s *stubCtxRC) Read (ctx context.Context, dst []byte) (int, error)	{ return s.r.Read(dst) }
   245  func (s *stubCtxRC) Close() error					{ return s.r.Close() }
   246  
   247  // WithCtxWC converts io.WriteCloser w into WriteCloser that accepts ctx.
   248  //
   249  // It returns original IO object if w was created via BindCtx*, but in general
   250  // returned WriteCloser will handle context only on best-effort basis.
   251  func WithCtxWC(w io.WriteCloser) WriteCloser {
   252  	switch b := w.(type) {
   253  	case *bindCtxWC:  return b.w
   254  	case *bindCtxRWC: return b.rw
   255  	}
   256  	return &stubCtxWC{w}
   257  }
   258  type stubCtxWC struct {w io.WriteCloser}
   259  func (s *stubCtxWC) Write(ctx context.Context, src []byte) (int, error)	{ return s.w.Write(src) }
   260  func (s *stubCtxWC) Close() error					{ return s.w.Close() }
   261  
   262  // WithCtxRWC converts io.ReadWriteCloser rw into ReadWriteCloser that accepts ctx.
   263  //
   264  // It returns original IO object if rw was created via BindCtx*, but in general
   265  // returned ReadWriteCloser will handle context only on best-effort basis.
   266  func WithCtxRWC(rw io.ReadWriteCloser) ReadWriteCloser {
   267  	switch b := rw.(type) {
   268  	case *bindCtxRWC: return b.rw
   269  	}
   270  	return &stubCtxRWC{rw}
   271  }
   272  type stubCtxRWC struct {rw io.ReadWriteCloser}
   273  func (s *stubCtxRWC) Read (ctx context.Context, dst []byte) (int, error){ return s.rw.Read (dst) }
   274  func (s *stubCtxRWC) Write(ctx context.Context, src []byte) (int, error){ return s.rw.Write(src) }
   275  func (s *stubCtxRWC) Close() error					{ return s.rw.Close() }
   276  
   277  
   278  // ----------------------------------------
   279  
   280  
   281  // CountedReader is a Reader that count total bytes read.
   282  type CountedReader struct {
   283  	r     Reader
   284  	nread int64
   285  }
   286  
   287  func (cr *CountedReader) Read(ctx context.Context, p []byte) (int, error) {
   288  	n, err := cr.r.Read(ctx, p)
   289  	cr.nread += int64(n)
   290  	return n, err
   291  }
   292  
   293  // InputOffset returns the number of bytes read.
   294  func (cr *CountedReader) InputOffset() int64 {
   295  	return cr.nread
   296  }
   297  
   298  // CountReader wraps r with CountedReader.
   299  func CountReader(r Reader) *CountedReader {
   300  	return &CountedReader{r, 0}
   301  }