forked from charmbracelet/ultraviolet
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpoll_fallback.go
More file actions
158 lines (140 loc) Β· 3.1 KB
/
Copy pathpoll_fallback.go
File metadata and controls
158 lines (140 loc) Β· 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package uv
import (
"bufio"
"io"
"sync"
"time"
)
// newFallbackReader creates a new fallbackReader for the given io.Reader.
func newFallbackReader(reader io.Reader) (pollReader, error) {
return &fallbackReader{
reader: bufio.NewReader(reader),
cancelChan: make(chan struct{}),
dataChan: make(chan struct{}, 1),
}, nil
}
// fallbackReader implements pollReader using goroutines and buffered I/O.
// This is a fallback implementation that works on all platforms.
type fallbackReader struct {
reader *bufio.Reader
cancelChan chan struct{}
dataChan chan struct{}
mu sync.Mutex
canceled bool
started bool
}
// Read reads data from the underlying reader.
func (r *fallbackReader) Read(p []byte) (int, error) {
r.mu.Lock()
if r.canceled {
r.mu.Unlock()
return 0, ErrCanceled
}
r.mu.Unlock()
n, err := r.reader.Read(p)
// If we get an error during a concurrent cancel, prefer ErrCanceled
if err != nil {
r.mu.Lock()
if r.canceled {
r.mu.Unlock()
return 0, ErrCanceled
}
r.mu.Unlock()
}
return n, err
}
// Poll waits for data to be available to read with the given timeout.
// This implementation starts a background goroutine to check for buffered
// data availability.
func (r *fallbackReader) Poll(timeout time.Duration) (bool, error) {
r.mu.Lock()
if r.canceled {
r.mu.Unlock()
return false, ErrCanceled
}
// Start the background reader goroutine if not already started
if !r.started {
r.started = true
go r.checkBuffered()
}
r.mu.Unlock()
if timeout < 0 {
// Wait indefinitely
select {
case <-r.dataChan:
// Put it back for next poll/read
select {
case r.dataChan <- struct{}{}:
default:
}
return true, nil
case <-r.cancelChan:
return false, ErrCanceled
}
}
// Wait with timeout
timer := time.NewTimer(timeout)
defer timer.Stop()
select {
case <-r.dataChan:
// Put it back for next poll/read
select {
case r.dataChan <- struct{}{}:
default:
}
return true, nil
case <-timer.C:
return false, nil
case <-r.cancelChan:
return false, ErrCanceled
}
}
// checkBuffered runs in a background goroutine to signal when data is available.
func (r *fallbackReader) checkBuffered() {
for {
select {
case <-r.cancelChan:
return
default:
}
// Check if data is buffered
r.mu.Lock()
if r.canceled {
r.mu.Unlock()
return
}
r.mu.Unlock()
// Peek at one byte to check if data is available
// This will block until data arrives
_, err := r.reader.Peek(1)
if err != nil {
// If error (including EOF), stop the goroutine
return
}
// Signal that data is available
select {
case r.dataChan <- struct{}{}:
case <-r.cancelChan:
return
}
// Wait a bit before checking again to avoid busy loop
time.Sleep(10 * time.Millisecond)
}
}
// Cancel cancels any ongoing poll or read operations.
func (r *fallbackReader) Cancel() bool {
r.mu.Lock()
if r.canceled {
r.mu.Unlock()
return false
}
r.canceled = true
r.mu.Unlock()
close(r.cancelChan)
return true
}
// Close closes the reader and releases any resources.
func (r *fallbackReader) Close() error {
r.Cancel()
return nil
}