All checks were successful
continuous-integration/drone/push Build is passing
See #478
94 lines
2.2 KiB
Go
94 lines
2.2 KiB
Go
package cancelreader
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
)
|
|
|
|
// ErrCanceled gets returned when trying to read from a canceled reader.
|
|
var ErrCanceled = fmt.Errorf("read canceled")
|
|
|
|
// CancelReader is a io.Reader whose Read() calls can be canceled without data
|
|
// being consumed. The cancelReader has to be closed.
|
|
type CancelReader interface {
|
|
io.ReadCloser
|
|
|
|
// Cancel cancels ongoing and future reads an returns true if it succeeded.
|
|
Cancel() bool
|
|
}
|
|
|
|
// File represents an input/output resource with a file descriptor.
|
|
type File interface {
|
|
io.ReadWriteCloser
|
|
|
|
// Fd returns its file descriptor
|
|
Fd() uintptr
|
|
|
|
// Name returns its file name.
|
|
Name() string
|
|
}
|
|
|
|
// fallbackCancelReader implements cancelReader but does not actually support
|
|
// cancelation during an ongoing Read() call. Thus, Cancel() always returns
|
|
// false. However, after calling Cancel(), new Read() calls immediately return
|
|
// errCanceled and don't consume any data anymore.
|
|
type fallbackCancelReader struct {
|
|
r io.Reader
|
|
cancelMixin
|
|
}
|
|
|
|
// newFallbackCancelReader is a fallback for NewReader that cannot actually
|
|
// cancel an ongoing read but will immediately return on future reads if it has
|
|
// been canceled.
|
|
func newFallbackCancelReader(reader io.Reader) (CancelReader, error) {
|
|
return &fallbackCancelReader{r: reader}, nil
|
|
}
|
|
|
|
func (r *fallbackCancelReader) Read(data []byte) (int, error) {
|
|
if r.isCanceled() {
|
|
return 0, ErrCanceled
|
|
}
|
|
|
|
n, err := r.r.Read(data)
|
|
/*
|
|
If the underlying reader is a blocking reader (e.g. an open connection),
|
|
it might happen that 1 goroutine cancels the reader while its stuck in
|
|
the read call waiting for something.
|
|
If that happens, we should still cancel the read.
|
|
*/
|
|
if r.isCanceled() {
|
|
return 0, ErrCanceled
|
|
}
|
|
return n, err // nolint: wrapcheck
|
|
}
|
|
|
|
func (r *fallbackCancelReader) Cancel() bool {
|
|
r.setCanceled()
|
|
return false
|
|
}
|
|
|
|
func (r *fallbackCancelReader) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// cancelMixin represents a goroutine-safe cancelation status.
|
|
type cancelMixin struct {
|
|
unsafeCanceled bool
|
|
lock sync.Mutex
|
|
}
|
|
|
|
func (c *cancelMixin) isCanceled() bool {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
return c.unsafeCanceled
|
|
}
|
|
|
|
func (c *cancelMixin) setCanceled() {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
c.unsafeCanceled = true
|
|
}
|