Commit 37584a0e authored by Kirill Smelkov's avatar Kirill Smelkov

xio: Add Pipe - io.Pipe analog that supports cancellation

I need this to support sysread(/head/watch) cancellation in WCFS
filesystem [1,2,3].

[1] wendelin.core@b17aeb8c
[2] wendelin.core@f05271b1
[3] wendelin.core@5ba816da
parents 0e368363 d2dc6c09
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE-go file.
// Pipe adapter to connect code expecting an xio.Reader
// with code expecting an xio.Writer.
package xio
import (
"context"
"io"
"sync"
)
// onceError is an object that will only store an error once.
type onceError struct {
sync.Mutex // guards following
err error
}
func (a *onceError) Store(err error) {
a.Lock()
defer a.Unlock()
if a.err != nil {
return
}
a.err = err
}
func (a *onceError) Load() error {
a.Lock()
defer a.Unlock()
return a.err
}
// A pipe is the shared pipe structure underlying PipeReader and PipeWriter.
type pipe struct {
wrMu sync.Mutex // Serializes Write operations
wrCh chan []byte
rdCh chan int
once sync.Once // Protects closing done
done chan struct{}
rerr onceError
werr onceError
}
func (p *pipe) Read(ctx context.Context, b []byte) (n int, err error) {
select {
case <-p.done:
return 0, p.readCloseError()
case <-ctx.Done():
return 0, ctx.Err()
default:
}
select {
case bw := <-p.wrCh:
nr := copy(b, bw)
p.rdCh <- nr
return nr, nil
case <-p.done:
return 0, p.readCloseError()
case <-ctx.Done():
return 0, ctx.Err()
}
}
func (p *pipe) readCloseError() error {
rerr := p.rerr.Load()
if werr := p.werr.Load(); rerr == nil && werr != nil {
return werr
}
return io.ErrClosedPipe
}
func (p *pipe) CloseRead(err error) error {
if err == nil {
err = io.ErrClosedPipe
}
p.rerr.Store(err)
p.once.Do(func() { close(p.done) })
return nil
}
func (p *pipe) Write(ctx context.Context, b []byte) (n int, err error) {
select {
case <-p.done:
return 0, p.writeCloseError()
case <-ctx.Done():
return 0, ctx.Err()
default:
p.wrMu.Lock()
defer p.wrMu.Unlock()
}
for once := true; once || len(b) > 0; once = false {
select {
case p.wrCh <- b:
nw := <-p.rdCh
b = b[nw:]
n += nw
case <-p.done:
return n, p.writeCloseError()
case <-ctx.Done():
return n, ctx.Err()
}
}
return n, nil
}
func (p *pipe) writeCloseError() error {
werr := p.werr.Load()
if rerr := p.rerr.Load(); werr == nil && rerr != nil {
return rerr
}
return io.ErrClosedPipe
}
func (p *pipe) CloseWrite(err error) error {
if err == nil {
err = io.EOF
}
p.werr.Store(err)
p.once.Do(func() { close(p.done) })
return nil
}
// A PipeReader is the read half of a pipe.
//
// It is similar to io.PipeReader, but additionally provides cancellation support for Read.
type PipeReader struct {
p *pipe
}
// Read implements xio.Reader interface:
// it reads data from the pipe, blocking until a writer
// arrives or the write end is closed.
// If the write end is closed with an error, that error is
// returned as err; otherwise err is EOF.
func (r *PipeReader) Read(ctx context.Context, data []byte) (n int, err error) {
return r.p.Read(ctx, data)
}
// Close closes the reader; subsequent writes to the
// write half of the pipe will return the error io.ErrClosedPipe.
func (r *PipeReader) Close() error {
return r.CloseWithError(nil)
}
// CloseWithError closes the reader; subsequent writes
// to the write half of the pipe will return the error err.
//
// CloseWithError never overwrites the previous error if it exists
// and always returns nil.
func (r *PipeReader) CloseWithError(err error) error {
return r.p.CloseRead(err)
}
// A PipeWriter is the write half of a pipe.
//
// It is similar to io.PipeWriter, but additionally provides cancellation support for Write.
type PipeWriter struct {
p *pipe
}
// Write implements xio.Writer interface:
// it writes data to the pipe, blocking until one or more readers
// have consumed all the data or the read end is closed.
// If the read end is closed with an error, that err is
// returned as err; otherwise err is io.ErrClosedPipe.
func (w *PipeWriter) Write(ctx context.Context, data []byte) (n int, err error) {
return w.p.Write(ctx, data)
}
// Close closes the writer; subsequent reads from the
// read half of the pipe will return no bytes and EOF.
func (w *PipeWriter) Close() error {
return w.CloseWithError(nil)
}
// CloseWithError closes the writer; subsequent reads from the
// read half of the pipe will return no bytes and the error err,
// or EOF if err is nil.
//
// CloseWithError never overwrites the previous error if it exists
// and always returns nil.
func (w *PipeWriter) CloseWithError(err error) error {
return w.p.CloseWrite(err)
}
// Pipe creates a synchronous in-memory pipe.
// It can be used to connect code expecting a xio.Reader
// with code expecting a xio.Writer.
//
// Reads and Writes on the pipe are matched one to one
// except when multiple Reads are needed to consume a single Write.
// That is, each Write to the PipeWriter blocks until it has satisfied
// one or more Reads from the PipeReader that fully consume
// the written data.
// The data is copied directly from the Write to the corresponding
// Read (or Reads); there is no internal buffering.
//
// It is safe to call Read and Write in parallel with each other or with Close.
// Parallel calls to Read and parallel calls to Write are also safe:
// the individual calls will be gated sequentially.
//
// Pipe is similar to io.Pipe but additionally provides cancellation support
// for Read and Write.
func Pipe() (*PipeReader, *PipeWriter) {
p := &pipe{
wrCh: make(chan []byte),
rdCh: make(chan int),
done: make(chan struct{}),
}
return &PipeReader{p}, &PipeWriter{p}
}
This diff is collapsed.
......@@ -26,6 +26,8 @@
// It is the opposite operation for BindCtx, but for arbitrary io.X
// returned xio.X handles context only on best-effort basis. In
// particular IO cancellation is not reliably handled for os.File .
// - Pipe amends io.Pipe and creates synchronous in-memory pipe that
// supports IO cancellation.
//
// Miscellaneous utilities:
//
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment