async def epoll_wait(self, events: Pointer[EpollEventList], timeout: int) -> t.Tuple[Pointer[EpollEventList], Pointer]: self._validate() with events.borrow(self.task) as events_n: num = await rsyscall.near.epoll_wait( self.task.sysif, self.near, events_n, events.size()//EpollEvent.sizeof(), timeout) valid_size = num * EpollEvent.sizeof() return events.split(valid_size)
async def _run(self) -> None: input_buf: Pointer = await self.epfd.task.malloc( EpollEventList, 32 * EpollEvent.sizeof()) number_to_cb: t.Dict[int, Continuation[EPOLL]] = {} registered_activity_fd: t.Optional[FileDescriptor] = None while True: if self.wait_readable: await self.wait_readable() activity_fd = self.epfd.task.sysif.get_activity_fd() if activity_fd and (registered_activity_fd is not activity_fd): # the activity fd changed, we need to register the new one if registered_activity_fd: # delete the old registered activity fd await self.epfd.epoll_ctl(EPOLL_CTL.DEL, registered_activity_fd) activity_fd_number = self.allocate_number(int(activity_fd)) # start up a coroutine to consume events from the activity_fd async def devnull(activity_fd_number=activity_fd_number): while True: await self.queue.request(activity_fd_number) reset(devnull()) await self.epfd.epoll_ctl( EPOLL_CTL.ADD, activity_fd, await self.epfd.task.ptr( EpollEvent( activity_fd_number, # not edge triggered; we don't want to block if there's # anything that can be read. EPOLL.IN | EPOLL.RDHUP | EPOLL.PRI | EPOLL.ERR | EPOLL.HUP))) registered_activity_fd = activity_fd try: valid_events_buf, rest = await self.epfd.epoll_wait( input_buf, self.timeout) received_events = await valid_events_buf.read() except SyscallHangup: # retry the epoll_wait to support rsyscall.tasks.persistent, as documented there; # for non-persistent tasks this will just fail with a SyscallSendError next time around. continue except Exception as wait_error: final_exn = wait_error break input_buf = valid_events_buf + rest for num, cb in self.queue.fetch_any(): number_to_cb[num] = cb for event in received_events: number_to_cb[event.data].send(event.events) del number_to_cb[event.data] for num, cb in self.queue.fetch_any(): number_to_cb[num] = cb for number in list(self.pending_remove): number_to_cb[number].throw(RemovedFromEpollError()) del number_to_cb[number] self.pending_remove.remove(number) self.queue.close(final_exn)
async def do_wait(self) -> None: """Perform an interruptible epoll_wait, calling callbacks with any events. We take care to store the running state of this method on the object, so that if we're cancelled, someone else can continue the call without dropping any events. It would be nice if we didn't have to do that manually; see "shared coroutine" notion in the docstring of OneAtATime for what we'd prefer. """ async with self.running_wait.needs_run() as needs_run: if needs_run: for number in self.pending_remove: del self.number_to_cb[number] self.pending_remove = set() maxevents = 32 if self.input_buf is None: self.input_buf = await self.ram.malloc(EpollEventList, maxevents * EpollEvent.sizeof()) if self.syscall_response is None: if self.wait_readable: await self.wait_readable() self.syscall_response = await self.epfd.task.sysif.submit_syscall( SYS.epoll_wait, self.epfd.near, self.input_buf.near, maxevents, self.timeout) if self.valid_events_buf is None: count = await self.syscall_response.receive() self.valid_events_buf, _ = self.input_buf.split(count * EpollEvent.sizeof()) received_events = await self.valid_events_buf.read() self.input_buf = None self.valid_events_buf = None self.syscall_response = None for event in received_events: if event.data not in self.pending_remove: self.number_to_cb[event.data](event.events)
async def epoll_ctl(self, op: EPOLL_CTL, fd: FileDescriptor, event: t.Optional[Pointer[EpollEvent]]=None) -> None: self._validate() with fd.borrow(self.task) as fd_n: if event is not None: if event.size() < EpollEvent.sizeof(): raise Exception("pointer is too small", event.size(), "to be an EpollEvent", EpollEvent.sizeof()) with event.borrow(self.task) as event_n: return (await rsyscall.near.epoll_ctl(self.task.sysif, self.near, op, fd_n, event_n)) else: return (await rsyscall.near.epoll_ctl(self.task.sysif, self.near, op, fd_n))
async def register(self, fd: FileDescriptor, events: EPOLL, cb: t.Callable[[EPOLL], None]) -> EpolledFileDescriptor: """Register a file descriptor on this epollfd, for the given events, calling the passed callback. The return value can be used to wait for callback calls, modify the events registered for this file descriptor, and delete the file descriptor from this epollfd. """ number = self.epoll_waiter.add_and_allocate_number(cb) await self.epfd.epoll_ctl(EPOLL_CTL.ADD, fd, await self.ram.ptr(EpollEvent(number, events))) return EpolledFileDescriptor(self, fd, number)
async def register(self, fd: FileDescriptor, events: EPOLL) -> EpolledFileDescriptor: """Register a file descriptor on this epollfd, for the given events, calling the passed callback. The return value can be used to wait for callback calls, modify the events registered for this file descriptor, and delete the file descriptor from this epollfd. """ number = self.epoll_waiter.allocate_number(int(fd.near)) efd = EpolledFileDescriptor(self, fd, number) await self.epfd.epoll_ctl( EPOLL_CTL.ADD, fd, await self.epfd.task.ptr(EpollEvent(number, events))) return efd
async def modify(self, events: EPOLL) -> None: "Change the EPOLL flags that this fd is registered with." await self.epoller.epfd.epoll_ctl( EPOLL_CTL.MOD, self.fd, await self.epoller.ram.ptr(EpollEvent(self.number, events)))