forked from toolshed/abra
		
	
		
			
				
	
	
		
			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
 | 
						|
}
 |