cancelreader.go

 1package cancelreader
 2
 3import (
 4	"fmt"
 5	"io"
 6	"sync"
 7)
 8
 9// ErrCanceled gets returned when trying to read from a canceled reader.
10var ErrCanceled = fmt.Errorf("read canceled")
11
12// CancelReader is a io.Reader whose Read() calls can be canceled without data
13// being consumed. The cancelReader has to be closed.
14type CancelReader interface {
15	io.ReadCloser
16
17	// Cancel cancels ongoing and future reads an returns true if it succeeded.
18	Cancel() bool
19}
20
21// File represents an input/output resource with a file descriptor.
22type File interface {
23	io.ReadWriteCloser
24
25	// Fd returns its file descriptor
26	Fd() uintptr
27
28	// Name returns its file name.
29	Name() string
30}
31
32// fallbackCancelReader implements cancelReader but does not actually support
33// cancelation during an ongoing Read() call. Thus, Cancel() always returns
34// false. However, after calling Cancel(), new Read() calls immediately return
35// errCanceled and don't consume any data anymore.
36type fallbackCancelReader struct {
37	r io.Reader
38	cancelMixin
39}
40
41// newFallbackCancelReader is a fallback for NewReader that cannot actually
42// cancel an ongoing read but will immediately return on future reads if it has
43// been canceled.
44func newFallbackCancelReader(reader io.Reader) (CancelReader, error) {
45	return &fallbackCancelReader{r: reader}, nil
46}
47
48func (r *fallbackCancelReader) Read(data []byte) (int, error) {
49	if r.isCanceled() {
50		return 0, ErrCanceled
51	}
52
53	n, err := r.r.Read(data)
54	/*
55		If the underlying reader is a blocking reader (e.g. an open connection),
56		it might happen that 1 goroutine cancels the reader while its stuck in
57		the read call waiting for something.
58		If that happens, we should still cancel the read.
59	*/
60	if r.isCanceled() {
61		return 0, ErrCanceled
62	}
63	return n, err // nolint: wrapcheck
64}
65
66func (r *fallbackCancelReader) Cancel() bool {
67	r.setCanceled()
68	return false
69}
70
71func (r *fallbackCancelReader) Close() error {
72	return nil
73}
74
75// cancelMixin represents a goroutine-safe cancelation status.
76type cancelMixin struct {
77	unsafeCanceled bool
78	lock           sync.Mutex
79}
80
81func (c *cancelMixin) isCanceled() bool {
82	c.lock.Lock()
83	defer c.lock.Unlock()
84
85	return c.unsafeCanceled
86}
87
88func (c *cancelMixin) setCanceled() {
89	c.lock.Lock()
90	defer c.lock.Unlock()
91
92	c.unsafeCanceled = true
93}