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