okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
f434aad
main
1//go:build linux 2// +build linux 3 4package uv 5 6import ( 7"errors" 8"fmt" 9"io" 10"os" 11"sync" 12"time" 13 14"golang.org/x/sys/unix" 15) 16 17// newPollReader creates a new pollReader for the given io.Reader. 18func newPollReader (reader io.Reader ) (pollReader ,error ) { 19file ,ok := reader .(File ) 20if ! ok { 21return newFallbackReader (reader ) 22 } 23 24epoll ,err := unix .EpollCreate1 (0 ) 25if err != nil { 26return nil ,fmt .Errorf ("create epoll: %w" ,err ) 27 } 28 29r := & epollReader { 30reader :reader , 31file :file , 32epoll :epoll , 33 } 34 35r .cancelSignalReader ,r .cancelSignalWriter ,err = os .Pipe () 36if err != nil { 37_ = unix .Close (epoll ) 38return nil ,err 39 } 40 41err = unix .EpollCtl (epoll ,unix .EPOLL_CTL_ADD ,int (file .Fd ()),& unix.EpollEvent { 42Events :unix .EPOLLIN , 43Fd :int32 (file .Fd ()), 44 }) 45if err != nil { 46_ = unix .Close (epoll ) 47_ = r .cancelSignalReader .Close () 48_ = r .cancelSignalWriter .Close () 49return nil ,fmt .Errorf ("add reader to epoll interest list: %w" ,err ) 50 } 51 52err = unix .EpollCtl (epoll ,unix .EPOLL_CTL_ADD ,int (r .cancelSignalReader .Fd ()),& unix.EpollEvent { 53Events :unix .EPOLLIN , 54Fd :int32 (r .cancelSignalReader .Fd ()), 55 }) 56if err != nil { 57_ = unix .Close (epoll ) 58_ = r .cancelSignalReader .Close () 59_ = r .cancelSignalWriter .Close () 60return nil ,fmt .Errorf ("add cancel signal to epoll interest list: %w" ,err ) 61 } 62 63return r ,nil 64} 65 66// epollReader implements pollReader using the Linux epoll API. 67type epollReader struct { 68reader io.Reader 69file File 70cancelSignalReader * os.File 71cancelSignalWriter * os.File 72epoll int 73mu sync.Mutex 74canceled bool 75} 76 77// Read reads data from the underlying reader. 78func (r * epollReader )Read (p []byte ) (int ,error ) { 79r .mu .Lock () 80if r .canceled { 81r .mu .Unlock () 82return 0 ,ErrCanceled 83 } 84r .mu .Unlock () 85 86return r .reader .Read (p ) 87} 88 89// Poll waits for data to be available to read with the given timeout. 90func (r * epollReader )Poll (timeout time.Duration ) (bool ,error ) { 91r .mu .Lock () 92if r .canceled { 93r .mu .Unlock () 94return false ,ErrCanceled 95 } 96r .mu .Unlock () 97 98events := make ([]unix.EpollEvent ,1 ) 99 100timeoutMs := - 1 101if timeout >= 0 { 102timeoutMs = int (timeout .Milliseconds ()) 103 } 104 105for { 106n ,err := unix .EpollWait (r .epoll ,events ,timeoutMs ) 107if errors .Is (err ,unix .EINTR ) { 108continue // try again if the syscall was interrupted 109 } 110 111if err != nil { 112return false ,fmt .Errorf ("epoll wait: %w" ,err ) 113 } 114 115if n == 0 { 116return false ,nil // timeout 117 } 118 119break 120 } 121 122switch events [0 ].Fd { 123case int32 (r .file .Fd ()): 124return true ,nil 125case int32 (r .cancelSignalReader .Fd ()): 126// remove signal from pipe 127var b [1 ]byte 128_ ,readErr := r .cancelSignalReader .Read (b [:]) 129if readErr != nil { 130return false ,fmt .Errorf ("reading cancel signal: %w" ,readErr ) 131 } 132return false ,ErrCanceled 133 } 134 135return false ,fmt .Errorf ("unknown error" ) 136} 137 138// Cancel cancels any ongoing poll or read operations. 139func (r * epollReader )Cancel ()bool { 140r .mu .Lock () 141r .canceled = true 142r .mu .Unlock () 143 144// send cancel signal 145_ ,err := r .cancelSignalWriter .Write ([]byte {'c' }) 146return err == nil 147} 148 149// Close closes the reader and releases any resources. 150func (r * epollReader )Close ()error { 151var errMsgs []error 152 153// close epoll 154err := unix .Close (r .epoll ) 155if err != nil { 156errMsgs = append (errMsgs ,fmt .Errorf ("closing epoll: %w" ,err )) 157 } 158 159// close pipe 160err = r .cancelSignalWriter .Close () 161if err != nil { 162errMsgs = append (errMsgs ,fmt .Errorf ("closing cancel signal writer: %w" ,err )) 163 } 164 165err = r .cancelSignalReader .Close () 166if err != nil { 167errMsgs = append (errMsgs ,fmt .Errorf ("closing cancel signal reader: %w" ,err )) 168 } 169 170if len (errMsgs )> 0 { 171return errors .Join (errMsgs ... ) 172 } 173 174return nil 175}