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

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE-go file.
     4  
     5  // Pipe adapter to connect code expecting an xio.Reader
     6  // with code expecting an xio.Writer.
     7  
     8  package xio
     9  
    10  import (
    11  	"context"
    12  	"io"
    13  	"sync"
    14  )
    15  
    16  // onceError is an object that will only store an error once.
    17  type onceError struct {
    18  	sync.Mutex // guards following
    19  	err        error
    20  }
    21  
    22  func (a *onceError) Store(err error) {
    23  	a.Lock()
    24  	defer a.Unlock()
    25  	if a.err != nil {
    26  		return
    27  	}
    28  	a.err = err
    29  }
    30  func (a *onceError) Load() error {
    31  	a.Lock()
    32  	defer a.Unlock()
    33  	return a.err
    34  }
    35  
    36  // A pipe is the shared pipe structure underlying PipeReader and PipeWriter.
    37  type pipe struct {
    38  	wrMu sync.Mutex // Serializes Write operations
    39  	wrCh chan []byte
    40  	rdCh chan int
    41  
    42  	once sync.Once // Protects closing done
    43  	done chan struct{}
    44  	rerr onceError
    45  	werr onceError
    46  }
    47  
    48  func (p *pipe) Read(ctx context.Context, b []byte) (n int, err error) {
    49  	select {
    50  	case <-p.done:
    51  		return 0, p.readCloseError()
    52  	case <-ctx.Done():
    53  		return 0, ctx.Err()
    54  	default:
    55  	}
    56  
    57  	select {
    58  	case bw := <-p.wrCh:
    59  		nr := copy(b, bw)
    60  		p.rdCh <- nr
    61  		return nr, nil
    62  	case <-p.done:
    63  		return 0, p.readCloseError()
    64  	case <-ctx.Done():
    65  		return 0, ctx.Err()
    66  	}
    67  }
    68  
    69  func (p *pipe) readCloseError() error {
    70  	rerr := p.rerr.Load()
    71  	if werr := p.werr.Load(); rerr == nil && werr != nil {
    72  		return werr
    73  	}
    74  	return io.ErrClosedPipe
    75  }
    76  
    77  func (p *pipe) CloseRead(err error) error {
    78  	if err == nil {
    79  		err = io.ErrClosedPipe
    80  	}
    81  	p.rerr.Store(err)
    82  	p.once.Do(func() { close(p.done) })
    83  	return nil
    84  }
    85  
    86  func (p *pipe) Write(ctx context.Context, b []byte) (n int, err error) {
    87  	select {
    88  	case <-p.done:
    89  		return 0, p.writeCloseError()
    90  	case <-ctx.Done():
    91  		return 0, ctx.Err()
    92  	default:
    93  		p.wrMu.Lock()
    94  		defer p.wrMu.Unlock()
    95  	}
    96  
    97  	for once := true; once || len(b) > 0; once = false {
    98  		select {
    99  		case p.wrCh <- b:
   100  			nw := <-p.rdCh
   101  			b = b[nw:]
   102  			n += nw
   103  		case <-p.done:
   104  			return n, p.writeCloseError()
   105  		case <-ctx.Done():
   106  			return n, ctx.Err()
   107  		}
   108  	}
   109  	return n, nil
   110  }
   111  
   112  func (p *pipe) writeCloseError() error {
   113  	werr := p.werr.Load()
   114  	if rerr := p.rerr.Load(); werr == nil && rerr != nil {
   115  		return rerr
   116  	}
   117  	return io.ErrClosedPipe
   118  }
   119  
   120  func (p *pipe) CloseWrite(err error) error {
   121  	if err == nil {
   122  		err = io.EOF
   123  	}
   124  	p.werr.Store(err)
   125  	p.once.Do(func() { close(p.done) })
   126  	return nil
   127  }
   128  
   129  // A PipeReader is the read half of a pipe.
   130  //
   131  // It is similar to io.PipeReader, but additionally provides cancellation support for Read.
   132  type PipeReader struct {
   133  	p *pipe
   134  }
   135  
   136  // Read implements xio.Reader interface:
   137  // it reads data from the pipe, blocking until a writer
   138  // arrives or the write end is closed.
   139  // If the write end is closed with an error, that error is
   140  // returned as err; otherwise err is EOF.
   141  func (r *PipeReader) Read(ctx context.Context, data []byte) (n int, err error) {
   142  	return r.p.Read(ctx, data)
   143  }
   144  
   145  // Close closes the reader; subsequent writes to the
   146  // write half of the pipe will return the error io.ErrClosedPipe.
   147  func (r *PipeReader) Close() error {
   148  	return r.CloseWithError(nil)
   149  }
   150  
   151  // CloseWithError closes the reader; subsequent writes
   152  // to the write half of the pipe will return the error err.
   153  //
   154  // CloseWithError never overwrites the previous error if it exists
   155  // and always returns nil.
   156  func (r *PipeReader) CloseWithError(err error) error {
   157  	return r.p.CloseRead(err)
   158  }
   159  
   160  // A PipeWriter is the write half of a pipe.
   161  //
   162  // It is similar to io.PipeWriter, but additionally provides cancellation support for Write.
   163  type PipeWriter struct {
   164  	p *pipe
   165  }
   166  
   167  // Write implements xio.Writer interface:
   168  // it writes data to the pipe, blocking until one or more readers
   169  // have consumed all the data or the read end is closed.
   170  // If the read end is closed with an error, that err is
   171  // returned as err; otherwise err is io.ErrClosedPipe.
   172  func (w *PipeWriter) Write(ctx context.Context, data []byte) (n int, err error) {
   173  	return w.p.Write(ctx, data)
   174  }
   175  
   176  // Close closes the writer; subsequent reads from the
   177  // read half of the pipe will return no bytes and EOF.
   178  func (w *PipeWriter) Close() error {
   179  	return w.CloseWithError(nil)
   180  }
   181  
   182  // CloseWithError closes the writer; subsequent reads from the
   183  // read half of the pipe will return no bytes and the error err,
   184  // or EOF if err is nil.
   185  //
   186  // CloseWithError never overwrites the previous error if it exists
   187  // and always returns nil.
   188  func (w *PipeWriter) CloseWithError(err error) error {
   189  	return w.p.CloseWrite(err)
   190  }
   191  
   192  // Pipe creates a synchronous in-memory pipe.
   193  // It can be used to connect code expecting a xio.Reader
   194  // with code expecting a xio.Writer.
   195  //
   196  // Reads and Writes on the pipe are matched one to one
   197  // except when multiple Reads are needed to consume a single Write.
   198  // That is, each Write to the PipeWriter blocks until it has satisfied
   199  // one or more Reads from the PipeReader that fully consume
   200  // the written data.
   201  // The data is copied directly from the Write to the corresponding
   202  // Read (or Reads); there is no internal buffering.
   203  //
   204  // It is safe to call Read and Write in parallel with each other or with Close.
   205  // Parallel calls to Read and parallel calls to Write are also safe:
   206  // the individual calls will be gated sequentially.
   207  //
   208  // Pipe is similar to io.Pipe but additionally provides cancellation support
   209  // for Read and Write.
   210  func Pipe() (*PipeReader, *PipeWriter) {
   211  	p := &pipe{
   212  		wrCh: make(chan []byte),
   213  		rdCh: make(chan int),
   214  		done: make(chan struct{}),
   215  	}
   216  	return &PipeReader{p}, &PipeWriter{p}
   217  }