├── LICENSE ├── README.md ├── error.go ├── evloop_epoll.go ├── evloop_select.go ├── go.mod └── poll.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Jose Luis Aracil (pepe@72horas.net) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of poll nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #poll [![GoDoc](https://godoc.org/github.com/jaracil/poll?status.png)](https://godoc.org/github.com/jaracil/poll) 2 | poll is an efficient char device access package for Go 3 | 4 | Download: 5 | ```shell 6 | go get github.com/jaracil/poll 7 | ``` 8 | 9 | ##Description: 10 | 11 | Poll is an efficient char device access package for Go, based on Nick Patavalis 12 | (npat@efault.net) poller package. 13 | 14 | It uses EPOLL(7) on Linux and SELECT(2) on the rest of Posix Oses. 15 | Allows concurent Read and Write operations from and to multiple 16 | file-descriptors without allocating one OS thread for every blocked 17 | operation. It behaves similarly to Go's netpoller (which multiplexes 18 | network connections) without requiring special support from the Go 19 | runtime. 20 | 21 | It can be used with tty devices, character devices, pipes, 22 | FIFOs, GPIOs, TUNs/TAPs and any Unix file-descriptor that is epoll(7)-able 23 | or select(2)-able. In addition it allows the user to set timeouts (deadlines) 24 | for read and write operations. 25 | 26 | All operations on *poll.File are thread-safe; you can use the same File 27 | from multiple go-routines. It is, for example, safe to close a file 28 | blocked on a Read or Write call from another go-routine. In this case all blocked read/write operations are awakened. 29 | * * * 30 | 31 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Jose Luis Aracil Gomez (pepe@diselpro.com) 2 | // Based on Nick Patavalis (npat@efault.net) poller package. 3 | // All rights reserved. 4 | // Use of this source code is governed by a BSD-style license that can 5 | // be found in the LICENSE.txt file. 6 | 7 | package poll 8 | 9 | // Error is the type for the errors returned by poller functions and 10 | // methods. See also the ErrXXX constants. 11 | type Error int 12 | 13 | // Errors returned by poller functions and methods. In addition to 14 | // these, poller functions and methods may return the errors reported 15 | // by the underlying system calls (open(2), read(2), write(2), etc.), 16 | // as well as io.EOF and io.ErrUnexpectedEOF. 17 | const ( 18 | ErrClosed Error = 1 // Use of closed poller file-descriptor 19 | ErrTimeout Error = 2 // Operation timed-out 20 | ) 21 | 22 | // Error returns a string describing the error. 23 | func (e Error) Error() string { 24 | switch e { 25 | case ErrClosed: 26 | return "use of closed descriptor" 27 | case ErrTimeout: 28 | return "I/O timeout error" 29 | } 30 | return "unknown error" 31 | } 32 | 33 | // Timeout returns true if the error indicates a timeout condition. 34 | func (e Error) Timeout() bool { 35 | return e == ErrTimeout 36 | } 37 | 38 | // Temporary returns true if the error indicates a temporary condition 39 | // (re-atempting the operation may succeed). 40 | func (e Error) Temporary() bool { 41 | return e.Timeout() 42 | } 43 | -------------------------------------------------------------------------------- /evloop_epoll.go: -------------------------------------------------------------------------------- 1 | //go:build linux && !select 2 | // +build linux,!select 3 | 4 | // Copyright (c) 2015, Jose Luis Aracil Gomez (pepe@diselpro.com) 5 | // Based on Nick Patavalis (npat@efault.net) poller package. 6 | // All rights reserved. 7 | // Use of this source code is governed by a BSD-style license that can 8 | // be found in the LICENSE.txt file. 9 | 10 | package poll 11 | 12 | import ( 13 | "log" 14 | "sync" 15 | "syscall" 16 | ) 17 | 18 | var epfd int = -1 19 | var fdm map[int]*File = map[int]*File{} 20 | var fdmLock sync.Mutex 21 | 22 | func init() { 23 | fd, err := syscall.EpollCreate1(syscall.EPOLL_CLOEXEC) 24 | if err != nil { 25 | log.Panicf("poller: EpollCreate1: %s", err.Error()) 26 | } 27 | epfd = fd 28 | go evLoop() 29 | } 30 | 31 | func startTrack(fd int, write bool) {} // startTRack non needed in epoll loop 32 | func stopTrack(fd int, write bool) {} // stopTRack non needed in epoll loop 33 | 34 | func register(f *File) (err error) { 35 | fdmLock.Lock() 36 | fdm[f.fd] = f 37 | ev := syscall.EpollEvent{ 38 | Events: syscall.EPOLLIN | 39 | syscall.EPOLLOUT | 40 | syscall.EPOLLRDHUP | 41 | (syscall.EPOLLET & 0xffffffff), 42 | Fd: int32(f.fd)} 43 | err = syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, f.fd, &ev) 44 | fdmLock.Unlock() 45 | return 46 | } 47 | 48 | func unregister(f *File) (err error) { 49 | fdmLock.Lock() 50 | delete(fdm, f.fd) 51 | var ev syscall.EpollEvent 52 | err = syscall.EpollCtl(epfd, syscall.EPOLL_CTL_DEL, f.fd, &ev) 53 | fdmLock.Unlock() 54 | return 55 | } 56 | 57 | func epollEv(ev *syscall.EpollEvent, write bool) { 58 | var fdc *fdCtl 59 | fdmLock.Lock() 60 | fd := fdm[int(ev.Fd)] 61 | fdmLock.Unlock() 62 | if fd == nil { 63 | // Drop event. Probably stale FD. 64 | return 65 | } 66 | if !write { 67 | fdc = &fd.r 68 | } else { 69 | fdc = &fd.w 70 | } 71 | fdc.cond.L.Lock() 72 | fdc.cond.Broadcast() 73 | fdc.cond.L.Unlock() 74 | } 75 | 76 | func evLoop() { 77 | events := make([]syscall.EpollEvent, 128) 78 | for { 79 | n, err := syscall.EpollWait(epfd, events, -1) 80 | if err != nil { 81 | if err == syscall.EINTR { 82 | continue 83 | } 84 | log.Panicf("poller: EpollWait: %s", err.Error()) 85 | } 86 | for i := 0; i < n; i++ { 87 | ev := &events[i] 88 | if ev.Events&(syscall.EPOLLIN| 89 | syscall.EPOLLRDHUP| 90 | syscall.EPOLLHUP| 91 | syscall.EPOLLERR) != 0 { 92 | epollEv(ev, false) 93 | } 94 | if ev.Events&(syscall.EPOLLOUT| 95 | syscall.EPOLLHUP| 96 | syscall.EPOLLERR) != 0 { 97 | epollEv(ev, true) 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /evloop_select.go: -------------------------------------------------------------------------------- 1 | // +build select darwin freebsd dragonfly netbsd openbsd plan9 solaris 2 | 3 | // Copyright (c) 2015, Jose Luis Aracil Gomez (pepe@diselpro.com) 4 | // Based on Nick Patavalis (npat@efault.net) poller package. 5 | // All rights reserved. 6 | // Use of this source code is governed by a BSD-style license that can 7 | // be found in the LICENSE.txt file. 8 | 9 | package poll 10 | 11 | import ( 12 | "os" 13 | "sync" 14 | "syscall" 15 | "time" 16 | "unsafe" 17 | ) 18 | 19 | var fdTrR map[int]struct{} = map[int]struct{}{} 20 | var fdTrW map[int]struct{} = map[int]struct{}{} 21 | var fdTrLock sync.Mutex 22 | 23 | var fdm map[int]*File = map[int]*File{} 24 | var fdmLock sync.Mutex 25 | var wakeupR *os.File 26 | var wakeupW *os.File 27 | 28 | func init() { 29 | var err error 30 | wakeupR, wakeupW, err = os.Pipe() 31 | if err != nil { 32 | panic(err.Error()) 33 | } 34 | err = syscall.SetNonblock(int(wakeupR.Fd()), true) 35 | if err != nil { 36 | panic(err.Error()) 37 | } 38 | err = syscall.SetNonblock(int(wakeupW.Fd()), true) 39 | if err != nil { 40 | panic(err.Error()) 41 | } 42 | go evLoop() 43 | } 44 | 45 | func wakeup() { 46 | wakeupW.Write([]byte{0}) 47 | } 48 | 49 | func startTrack(fd int, write bool) { 50 | fdTrLock.Lock() 51 | last := false 52 | if write { 53 | _, last = fdTrW[fd] 54 | fdTrW[fd] = struct{}{} 55 | } else { 56 | _, last = fdTrR[fd] 57 | fdTrR[fd] = struct{}{} 58 | } 59 | if !last { 60 | wakeup() 61 | } 62 | fdTrLock.Unlock() 63 | } 64 | 65 | func stopTrack(fd int, write bool) { 66 | fdTrLock.Lock() 67 | last := false 68 | if write { 69 | _, last = fdTrW[fd] 70 | delete(fdTrW, fd) 71 | } else { 72 | _, last = fdTrR[fd] 73 | delete(fdTrR, fd) 74 | } 75 | if last { 76 | wakeup() 77 | } 78 | fdTrLock.Unlock() 79 | } 80 | 81 | func register(f *File) error { 82 | fdmLock.Lock() 83 | fdm[f.fd] = f 84 | fdmLock.Unlock() 85 | return nil 86 | } 87 | 88 | func unregister(f *File) error { 89 | fdmLock.Lock() 90 | delete(fdm, f.fd) 91 | fdmLock.Unlock() 92 | return nil 93 | } 94 | 95 | func getFile(fd int) *File { 96 | fdmLock.Lock() 97 | f := fdm[fd] 98 | fdmLock.Unlock() 99 | return f 100 | } 101 | 102 | func evLoop() { 103 | dummy := make([]byte, 1024) 104 | fdR := &FdSet{} 105 | fdW := &FdSet{} 106 | wupFd := int(wakeupR.Fd()) 107 | topFd := wupFd 108 | for { 109 | topFd = wupFd 110 | fdR.Reset() 111 | fdW.Reset() 112 | fdR.Set(wupFd) 113 | fdTrLock.Lock() 114 | for k, _ := range fdTrR { 115 | fdR.Set(k) 116 | if k > topFd { 117 | topFd = k 118 | } 119 | } 120 | for k, _ := range fdTrW { 121 | fdW.Set(k) 122 | if k > topFd { 123 | topFd = k 124 | } 125 | } 126 | fdTrLock.Unlock() 127 | n, err := Select(topFd+1, fdR, fdW, nil, -1) 128 | if err != nil { 129 | continue 130 | } 131 | if fdR.IsSet(wupFd) { 132 | n-- 133 | wakeupR.Read(dummy) 134 | } 135 | for x := 0; x <= topFd && n > 0; x++ { 136 | if fdR.IsSet(x) { 137 | n-- 138 | fdTrLock.Lock() 139 | delete(fdTrR, x) 140 | fdTrLock.Unlock() 141 | file := getFile(x) 142 | if file != nil { 143 | file.r.cond.L.Lock() 144 | file.r.cond.Broadcast() 145 | file.r.cond.L.Unlock() 146 | } 147 | } 148 | if fdW.IsSet(x) { 149 | n-- 150 | fdTrLock.Lock() 151 | delete(fdTrW, x) 152 | fdTrLock.Unlock() 153 | file := getFile(x) 154 | if file != nil { 155 | file.w.cond.L.Lock() 156 | file.w.cond.Broadcast() 157 | file.w.cond.L.Unlock() 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | // Select syscall stuff 165 | 166 | const ( 167 | FD_BITS = int(unsafe.Sizeof(0) * 8) 168 | FD_SETSIZE = 1024 169 | ) 170 | 171 | type FdSet struct { 172 | bits [FD_SETSIZE / FD_BITS]int 173 | } 174 | 175 | func (fds *FdSet) Reset() { 176 | for i := 0; i < len(fds.bits); i++ { 177 | fds.bits[i] = 0 178 | } 179 | } 180 | 181 | func (fds *FdSet) Set(fd int) { 182 | mask := uint(1) << (uint(fd) % uint(FD_BITS)) 183 | 184 | fds.bits[fd/FD_BITS] |= int(mask) 185 | } 186 | 187 | func (fds *FdSet) UnSet(fd int) { 188 | mask := uint(1) << (uint(fd) % uint(FD_BITS)) 189 | 190 | fds.bits[fd/FD_BITS] &^= int(mask) 191 | } 192 | 193 | func (fds *FdSet) IsSet(fd int) bool { 194 | mask := uint(1) << (uint(fd) % uint(FD_BITS)) 195 | 196 | return (fds.bits[fd/FD_BITS] & int(mask)) != 0 197 | } 198 | 199 | func Select(n int, r, w, e *FdSet, timeout time.Duration) (int, error) { 200 | rfds := (*syscall.FdSet)(unsafe.Pointer(r)) 201 | wfds := (*syscall.FdSet)(unsafe.Pointer(w)) 202 | efds := (*syscall.FdSet)(unsafe.Pointer(e)) 203 | 204 | if timeout >= 0 { 205 | tv := syscall.NsecToTimeval(timeout.Nanoseconds()) 206 | return syscall.Select(n, rfds, wfds, efds, &tv) 207 | } 208 | return syscall.Select(n, rfds, wfds, efds, nil) 209 | } 210 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jaracil/poll 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /poll.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Jose Luis Aracil Gomez (pepe@diselpro.com) 2 | // Based on Nick Patavalis (npat@efault.net) poller package. 3 | // All rights reserved. 4 | // Use of this source code is governed by a BSD-style license that can 5 | // be found in the LICENSE.txt file. 6 | 7 | package poll 8 | 9 | import ( 10 | "io" 11 | "sync" 12 | "syscall" 13 | "time" 14 | ) 15 | 16 | const ( 17 | O_RDONLY int = syscall.O_RDONLY // open the file read-only. 18 | O_WRONLY int = syscall.O_WRONLY // open the file write-only. 19 | O_RDWR int = syscall.O_RDWR // open the file read-write. 20 | O_NONBLOCK int = syscall.O_NONBLOCK // open in non block mode. 21 | ) 22 | 23 | // fdCtl keeps control fields (locks, timers, etc) for a single 24 | // direction. For every File there is one fdCtl for Read operations and 25 | // another for Write operations. 26 | type fdCtl struct { 27 | m sync.Mutex 28 | cond *sync.Cond 29 | deadline time.Time 30 | timer *time.Timer 31 | timeout bool 32 | } 33 | 34 | // OsFile interface with *os.File methods used in NewFromFile 35 | type OsFile interface { 36 | Close() error 37 | Fd() uintptr 38 | Name() string 39 | } 40 | 41 | // File is an *os.File like object who adds polling capabilities 42 | type File struct { 43 | closed bool // Set by Close(), never cleared 44 | fd int 45 | name string 46 | closeF func() error 47 | // Must hold respective lock to access 48 | r fdCtl // Control fields for Read operations 49 | w fdCtl // Control fields for Write operations 50 | } 51 | 52 | // NewFile returns a new File with the given file descriptor and name. 53 | func NewFile(fd uintptr, name string) (*File, error) { 54 | err := syscall.SetNonblock(int(fd), true) 55 | if err != nil { 56 | return nil, err 57 | } 58 | file := &File{fd: int(fd), name: name} 59 | file.r.cond = sync.NewCond(&sync.Mutex{}) 60 | file.w.cond = sync.NewCond(&sync.Mutex{}) 61 | err = register(file) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return file, nil 66 | } 67 | 68 | // Open the named path for reading, writing or both, depnding on the 69 | // flags argument. 70 | func Open(name string, flags int) (*File, error) { 71 | fd, err := syscall.Open(name, flags|syscall.O_CLOEXEC|syscall.O_NONBLOCK, 0666) 72 | if err != nil { 73 | return nil, err 74 | } 75 | return NewFile(uintptr(fd), name) 76 | } 77 | 78 | // NewFromFile returns a new *poll.File based on the given *os.File. 79 | // You don't need to worry about closing the *os.File, *poll.File already does it. 80 | func NewFromFile(of OsFile) (*File, error) { 81 | f, err := NewFile(of.Fd(), of.Name()) 82 | if err != nil { 83 | return nil, err 84 | } 85 | f.closeF = of.Close 86 | return f, nil 87 | } 88 | 89 | // Name returns the name of the file as presented to Open. 90 | func (f *File) Name() string { 91 | return f.name 92 | } 93 | 94 | // Fd returns the integer Unix file descriptor referencing the open file. 95 | func (f *File) Fd() uintptr { 96 | return uintptr(f.fd) 97 | } 98 | 99 | // WriteString is like Write, but writes the contents of string s rather than a slice of bytes. 100 | func (f *File) WriteString(s string) (int, error) { 101 | return f.Write([]byte(s)) 102 | } 103 | 104 | // Read reads up to len(b) bytes from the File. 105 | // It returns the number of bytes read and an error, if any. 106 | func (f *File) Read(p []byte) (n int, err error) { 107 | f.r.m.Lock() 108 | n, err = f.sysrw(false, p) 109 | f.r.m.Unlock() 110 | return 111 | } 112 | 113 | // Write writes len(b) bytes to the File. 114 | // It returns the number of bytes written and an error, if any. 115 | // Write returns a non-nil error when n != len(b). 116 | func (f *File) Write(p []byte) (n int, err error) { 117 | f.w.m.Lock() 118 | for n != len(p) { 119 | var nn int 120 | nn, err = f.sysrw(true, p[n:]) 121 | n += nn 122 | if err != nil { 123 | break 124 | } 125 | } 126 | f.w.m.Unlock() 127 | return 128 | } 129 | 130 | func (f *File) sysrw(write bool, p []byte) (n int, err error) { 131 | var fdc *fdCtl 132 | var rwfun func(int, []byte) (int, error) 133 | var errEOF error 134 | 135 | if !write { 136 | // Prepare things for Read. 137 | fdc = &f.r 138 | rwfun = syscall.Read 139 | errEOF = io.EOF 140 | } else { 141 | // Prepare things for Write. 142 | fdc = &f.w 143 | rwfun = syscall.Write 144 | errEOF = io.ErrUnexpectedEOF 145 | } 146 | // Read & Write are identical 147 | fdc.cond.L.Lock() 148 | defer fdc.cond.L.Unlock() 149 | for { 150 | if f.closed { 151 | return 0, ErrClosed 152 | } 153 | if fdc.timeout { 154 | return 0, ErrTimeout 155 | } 156 | n, err = rwfun(f.fd, p) 157 | if err != nil { 158 | n = 0 159 | if err != syscall.EAGAIN { 160 | break 161 | } 162 | // EAGAIN 163 | startTrack(f.fd, write) 164 | fdc.cond.Wait() 165 | if f.closed || fdc.timeout { 166 | stopTrack(f.fd, write) 167 | } 168 | continue 169 | } 170 | if n == 0 && len(p) != 0 { 171 | err = errEOF 172 | break 173 | } 174 | break 175 | } 176 | return n, err 177 | } 178 | 179 | //Close closes the File, rendering it unusable for I/O. It returns an error, if any. 180 | func (f *File) Close() error { 181 | if err := f.Lock(); err != nil { 182 | return err 183 | } 184 | defer f.Unlock() 185 | f.closed = true 186 | unregister(f) 187 | if f.r.timer != nil { 188 | f.r.timer.Stop() 189 | } 190 | if f.w.timer != nil { 191 | f.w.timer.Stop() 192 | } 193 | // Wake up everybody waiting on File. 194 | f.r.cond.Broadcast() 195 | f.w.cond.Broadcast() 196 | if f.closeF != nil { 197 | return f.closeF() 198 | } 199 | return syscall.Close(f.fd) 200 | } 201 | 202 | // SetDeadline sets the deadline for Read and write operations on File. 203 | func (f *File) SetDeadline(t time.Time) error { 204 | if err := f.SetReadDeadline(t); err != nil { 205 | return err 206 | } 207 | return f.SetWriteDeadline(t) 208 | } 209 | 210 | // SetReadDeadline sets the deadline for Read operations on File. 211 | func (f *File) SetReadDeadline(t time.Time) error { 212 | return f.setDeadline(false, t) 213 | } 214 | 215 | // SetWriteDeadline sets the deadline for Write operations on File. 216 | func (f *File) SetWriteDeadline(t time.Time) error { 217 | return f.setDeadline(true, t) 218 | } 219 | 220 | func (f *File) setDeadline(write bool, t time.Time) error { 221 | var fdc *fdCtl 222 | 223 | if !write { 224 | // Setting read deadline 225 | fdc = &f.r 226 | } else { 227 | // Setting write deadline 228 | fdc = &f.w 229 | } 230 | // R & W deadlines are handled identically 231 | fdc.cond.L.Lock() 232 | if f.closed { 233 | fdc.cond.L.Unlock() 234 | return ErrClosed 235 | } 236 | fdc.deadline = t 237 | fdc.timeout = false 238 | if t.IsZero() { 239 | if fdc.timer != nil { 240 | fdc.timer.Stop() 241 | } 242 | } else { 243 | d := t.Sub(time.Now()) 244 | if fdc.timer == nil { 245 | fdc.timer = time.AfterFunc(d, 246 | func() { f.timerEvent(write) }) 247 | } else { 248 | fdc.timer.Stop() 249 | fdc.timer.Reset(d) 250 | } 251 | } 252 | fdc.cond.L.Unlock() 253 | return nil 254 | } 255 | 256 | // Lock locks the file. It must be called before perfoming 257 | // miscellaneous operations (e.g. ioctls) on the underlying system 258 | // file descriptor. 259 | func (f *File) Lock() error { 260 | f.r.cond.L.Lock() 261 | f.w.cond.L.Lock() 262 | if f.closed { 263 | f.w.cond.L.Unlock() 264 | f.r.cond.L.Unlock() 265 | return ErrClosed 266 | } 267 | return nil 268 | } 269 | 270 | // Unlock unlocks the file. 271 | func (f *File) Unlock() { 272 | f.w.cond.L.Unlock() 273 | f.r.cond.L.Unlock() 274 | } 275 | 276 | func (f *File) timerEvent(write bool) { 277 | var fdc *fdCtl 278 | 279 | if !write { 280 | // A Read timeout 281 | fdc = &f.r 282 | } else { 283 | // A Write timeout 284 | fdc = &f.w 285 | } 286 | fdc.cond.L.Lock() 287 | if !f.closed && !fdc.timeout && 288 | !fdc.deadline.IsZero() && !fdc.deadline.After(time.Now()) { 289 | fdc.timeout = true 290 | fdc.cond.Broadcast() 291 | } 292 | fdc.cond.L.Unlock() 293 | } 294 | --------------------------------------------------------------------------------