okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
04a6149
upstream
1//go:build solaris || darwin || freebsd || netbsd || openbsd || dragonfly 2// +build solaris darwin freebsd netbsd openbsd dragonfly 3 4package uv 5 6import ( 7"errors" 8"fmt" 9"io" 10"os" 11"strings" 12"sync" 13"time" 14 15"golang.org/x/sys/unix" 16) 17 18// newSelectPollReader creates a new SelectReader for the given io.Reader. 19func newSelectPollReader (reader io.Reader ) (pollReader ,error ) { 20file ,ok := reader .(File ) 21if ! ok || file .Fd ()>= unix .FD_SETSIZE { 22return newFallbackReader (reader ) 23 } 24 25r := & selectReader { 26reader :reader , 27file :file , 28 } 29 30var err error 31r .cancelSignalReader ,r .cancelSignalWriter ,err = os .Pipe () 32if err != nil { 33return nil ,err 34 } 35 36return r ,nil 37} 38 39// selectReader implements pollReader using the POSIX select API. 40type selectReader struct { 41reader io.Reader 42file File 43cancelSignalReader * os.File 44cancelSignalWriter * os.File 45mu sync.Mutex 46canceled bool 47} 48 49// Read reads data from the underlying reader. 50func (r * selectReader )Read (p []byte ) (int ,error ) { 51r .mu .Lock () 52if r .canceled { 53r .mu .Unlock () 54return 0 ,ErrCanceled 55 } 56r .mu .Unlock () 57 58return r .reader .Read (p ) 59} 60 61// Poll waits for data to be available to read with the given timeout. 62func (r * selectReader )Poll (timeout time.Duration ) (bool ,error ) { 63r .mu .Lock () 64if r .canceled { 65r .mu .Unlock () 66return false ,ErrCanceled 67 } 68r .mu .Unlock () 69 70for { 71readerFd := int (r .file .Fd ()) 72abortFd := int (r .cancelSignalReader .Fd ()) 73 74maxFd := readerFd 75if abortFd > maxFd { 76maxFd = abortFd 77 } 78 79// this is a limitation of the select syscall 80if maxFd >= unix .FD_SETSIZE { 81return false ,fmt .Errorf ("cannot select on file descriptor %d which is larger than %d" ,maxFd ,unix .FD_SETSIZE ) 82 } 83 84fdSet := & unix.FdSet {} 85fdSet .Set (readerFd ) 86fdSet .Set (abortFd ) 87 88var tv * unix.Timeval 89if timeout >= 0 { 90t := unix .NsecToTimeval (timeout .Nanoseconds ()) 91tv = & t 92 } 93 94n ,err := unix .Select (maxFd + 1 ,fdSet ,nil ,nil ,tv ) 95if errors .Is (err ,unix .EINTR ) { 96continue // try again if the syscall was interrupted 97 } 98 99if err != nil { 100return false ,fmt .Errorf ("select: %w" ,err ) 101 } 102 103if n == 0 { 104return false ,nil // timeout 105 } 106 107if fdSet .IsSet (abortFd ) { 108// remove signal from pipe 109var b [1 ]byte 110_ ,readErr := r .cancelSignalReader .Read (b [:]) 111if readErr != nil { 112return false ,fmt .Errorf ("reading cancel signal: %w" ,readErr ) 113 } 114return false ,ErrCanceled 115 } 116 117if fdSet .IsSet (readerFd ) { 118return true ,nil 119 } 120 121return false ,fmt .Errorf ("select returned without setting a file descriptor" ) 122 } 123} 124 125// Cancel cancels any ongoing poll or read operations. 126func (r * selectReader )Cancel ()bool { 127r .mu .Lock () 128r .canceled = true 129r .mu .Unlock () 130 131// send cancel signal 132_ ,err := r .cancelSignalWriter .Write ([]byte {'c' }) 133return err == nil 134} 135 136// Close closes the reader and releases any resources. 137func (r * selectReader )Close ()error { 138var errMsgs []string 139 140// close pipe 141err := r .cancelSignalWriter .Close () 142if err != nil { 143errMsgs = append (errMsgs ,fmt .Sprintf ("closing cancel signal writer: %v" ,err )) 144 } 145 146err = r .cancelSignalReader .Close () 147if err != nil { 148errMsgs = append (errMsgs ,fmt .Sprintf ("closing cancel signal reader: %v" ,err )) 149 } 150 151if len (errMsgs )> 0 { 152return fmt .Errorf ("%s" ,strings .Join (errMsgs ,", " )) 153 } 154 155return nil 156}