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
 | |
| }
 |