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}