gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/fdnotifier/fdnotifier.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //go:build linux 16 // +build linux 17 18 // Package fdnotifier contains an adapter that translates IO events (e.g., a 19 // file became readable/writable) from native FDs to the notifications in the 20 // waiter package. It uses epoll in edge-triggered mode to receive notifications 21 // for registered FDs. 22 package fdnotifier 23 24 import ( 25 "fmt" 26 27 "golang.org/x/sys/unix" 28 "gvisor.dev/gvisor/pkg/sync" 29 "gvisor.dev/gvisor/pkg/waiter" 30 ) 31 32 type fdInfo struct { 33 queue *waiter.Queue 34 waiting bool 35 } 36 37 // notifier holds all the state necessary to issue notifications when IO events 38 // occur in the observed FDs. 39 type notifier struct { 40 // epFD is the epoll file descriptor used to register for io 41 // notifications. 42 epFD int 43 44 // mu protects fdMap. 45 mu sync.Mutex 46 47 // fdMap maps file descriptors to their notification queues and waiting 48 // status. 49 fdMap map[int32]*fdInfo 50 } 51 52 // newNotifier creates a new notifier object. 53 func newNotifier() (*notifier, error) { 54 epfd, err := unix.EpollCreate1(0) 55 if err != nil { 56 return nil, err 57 } 58 59 w := ¬ifier{ 60 epFD: epfd, 61 fdMap: make(map[int32]*fdInfo), 62 } 63 64 go w.waitAndNotify() // S/R-SAFE: no waiter exists during save / load. 65 66 return w, nil 67 } 68 69 // waitFD waits on mask for fd. The fdMap mutex must be hold. 70 func (n *notifier) waitFD(fd int32, fi *fdInfo, mask waiter.EventMask) error { 71 if !fi.waiting && mask == 0 { 72 return nil 73 } 74 75 e := unix.EpollEvent{ 76 Events: mask.ToLinux() | unix.EPOLLET, 77 Fd: fd, 78 } 79 80 switch { 81 case !fi.waiting && mask != 0: 82 if err := unix.EpollCtl(n.epFD, unix.EPOLL_CTL_ADD, int(fd), &e); err != nil { 83 return err 84 } 85 fi.waiting = true 86 case fi.waiting && mask == 0: 87 unix.EpollCtl(n.epFD, unix.EPOLL_CTL_DEL, int(fd), nil) 88 fi.waiting = false 89 case fi.waiting && mask != 0: 90 if err := unix.EpollCtl(n.epFD, unix.EPOLL_CTL_MOD, int(fd), &e); err != nil { 91 return err 92 } 93 } 94 95 return nil 96 } 97 98 // addFD adds an FD to the list of FDs observed by n. 99 func (n *notifier) addFD(fd int32, queue *waiter.Queue) error { 100 n.mu.Lock() 101 defer n.mu.Unlock() 102 103 // Panic if we're already notifying on this FD. 104 if _, ok := n.fdMap[fd]; ok { 105 panic(fmt.Sprintf("File descriptor %v added twice", fd)) 106 } 107 108 info := &fdInfo{queue: queue} 109 // We might already have something in queue to wait for. 110 if err := n.waitFD(fd, info, queue.Events()); err != nil { 111 return err 112 } 113 // Add it to the map. 114 n.fdMap[fd] = info 115 return nil 116 } 117 118 // updateFD updates the set of events the fd needs to be notified on. 119 func (n *notifier) updateFD(fd int32) error { 120 n.mu.Lock() 121 defer n.mu.Unlock() 122 123 if fi, ok := n.fdMap[fd]; ok { 124 return n.waitFD(fd, fi, fi.queue.Events()) 125 } 126 127 return nil 128 } 129 130 // RemoveFD removes an FD from the list of FDs observed by n. 131 func (n *notifier) removeFD(fd int32) { 132 n.mu.Lock() 133 defer n.mu.Unlock() 134 135 // Remove from map, then from epoll object. 136 n.waitFD(fd, n.fdMap[fd], 0) 137 delete(n.fdMap, fd) 138 } 139 140 // hasFD returns true if the fd is in the list of observed FDs. 141 func (n *notifier) hasFD(fd int32) bool { 142 n.mu.Lock() 143 defer n.mu.Unlock() 144 145 _, ok := n.fdMap[fd] 146 return ok 147 } 148 149 // waitAndNotify run is its own goroutine and loops waiting for io event 150 // notifications from the epoll object. Once notifications arrive, they are 151 // dispatched to the registered queue. 152 func (n *notifier) waitAndNotify() error { 153 e := make([]unix.EpollEvent, 100) 154 for { 155 v, err := epollWait(n.epFD, e, -1) 156 if err == unix.EINTR { 157 continue 158 } 159 160 if err != nil { 161 return err 162 } 163 164 notified := false 165 n.mu.Lock() 166 for i := 0; i < v; i++ { 167 if fi, ok := n.fdMap[e[i].Fd]; ok { 168 fi.queue.Notify(waiter.EventMaskFromLinux(e[i].Events)) 169 notified = true 170 } 171 } 172 n.mu.Unlock() 173 if notified { 174 // Let goroutines woken by Notify get a chance to run before we 175 // epoll_wait again. 176 sync.Goyield() 177 } 178 } 179 } 180 181 var shared struct { 182 notifier *notifier 183 once sync.Once 184 initErr error 185 } 186 187 // AddFD adds an FD to the list of observed FDs. 188 func AddFD(fd int32, queue *waiter.Queue) error { 189 shared.once.Do(func() { 190 shared.notifier, shared.initErr = newNotifier() 191 }) 192 193 if shared.initErr != nil { 194 return shared.initErr 195 } 196 197 return shared.notifier.addFD(fd, queue) 198 } 199 200 // UpdateFD updates the set of events the fd needs to be notified on. 201 func UpdateFD(fd int32) error { 202 return shared.notifier.updateFD(fd) 203 } 204 205 // RemoveFD removes an FD from the list of observed FDs. 206 func RemoveFD(fd int32) { 207 shared.notifier.removeFD(fd) 208 } 209 210 // HasFD returns true if the FD is in the list of observed FDs. 211 // 212 // This should only be used by tests to assert that FDs are correctly registered. 213 func HasFD(fd int32) bool { 214 return shared.notifier.hasFD(fd) 215 }