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 }