From 559123527428e6b65bc137bc2a0d254742d1a70f Mon Sep 17 00:00:00 2001 From: Matthias Urlichs Date: Wed, 28 Feb 2018 17:06:24 +0100 Subject: [PATCH 1/2] Add "trio.hazmat.wait_priority(fd)". This function is only available for epoll. It uses the EPOLLPRI mode to trigger on an "exceptional condition", whatever that is with respect to the given file descriptor. Among other uses, this flag can be used to wait for changes on GPIO inputs, or for mode changes of a PTY slave device (when used on the master). --- docs/source/reference-hazmat.rst | 24 ++++++++++++++++++++++++ trio/_core/_io_epoll.py | 15 +++++++++++++++ trio/hazmat.py | 1 + 3 files changed, 40 insertions(+) diff --git a/docs/source/reference-hazmat.rst b/docs/source/reference-hazmat.rst index 8fd0a85a94..1ba6725351 100644 --- a/docs/source/reference-hazmat.rst +++ b/docs/source/reference-hazmat.rst @@ -194,6 +194,30 @@ Unix-like systems provide the following functions: become writable. +.. function:: wait_priority(fd) + :async: + + Block until there is an "exceptional condition" on the given file + descriptor. + + .. warning:: + + This method uses the ``POLLPRI`` flag to the ``epoll`` system call. + Its semantics are highly specific to the type of file descriptor + you're passing to it. + + This test may be used to + * wait for changes to hardware inputs (Linux GPIO) + * notice changed state of a PTY slave by the master + * … + + :arg fd: + integer file descriptor, or else an object with a ``fileno()`` method + :raises trio.ResourceBusyError: + if another task is already waiting for an exceptional condition on + the given fd. + + Kqueue-specific API ------------------- diff --git a/trio/_core/_io_epoll.py b/trio/_core/_io_epoll.py index da605dd701..68c567cd6a 100644 --- a/trio/_core/_io_epoll.py +++ b/trio/_core/_io_epoll.py @@ -9,6 +9,7 @@ class _EpollStatistics: tasks_waiting_read = attr.ib() tasks_waiting_write = attr.ib() + tasks_waiting_prio = attr.ib() backend = attr.ib(default="epoll") @@ -16,6 +17,7 @@ class _EpollStatistics: class EpollWaiters: read_task = attr.ib(default=None) write_task = attr.ib(default=None) + prio_task = attr.ib(default=None) def flags(self): flags = 0 @@ -23,6 +25,8 @@ def flags(self): flags |= select.EPOLLIN if self.write_task is not None: flags |= select.EPOLLOUT + if self.prio_task is not None: + flags |= select.EPOLLPRI if not flags: return None # XX not sure if EPOLLEXCLUSIVE is actually safe... I think @@ -47,14 +51,18 @@ class EpollIOManager: def statistics(self): tasks_waiting_read = 0 tasks_waiting_write = 0 + tasks_waiting_prio = 0 for waiter in self._registered.values(): if waiter.read_task is not None: tasks_waiting_read += 1 if waiter.write_task is not None: tasks_waiting_write += 1 + if waiter.prio_task is not None: + tasks_waiting_prio += 1 return _EpollStatistics( tasks_waiting_read=tasks_waiting_read, tasks_waiting_write=tasks_waiting_write, + tasks_waiting_prio=tasks_waiting_prio, ) def close(self): @@ -76,6 +84,9 @@ def handle_io(self, timeout): if flags & ~select.EPOLLOUT and waiters.read_task is not None: _core.reschedule(waiters.read_task) waiters.read_task = None + if flags & select.EPOLLPRI and waiters.prio_task is not None: + _core.reschedule(waiters.prio_task) + waiters.prio_task = None self._update_registrations(fd, True) def _update_registrations(self, fd, currently_registered): @@ -122,3 +133,7 @@ async def wait_readable(self, fd): @_public async def wait_writable(self, fd): await self._epoll_wait(fd, "write_task") + + @_public + async def wait_priority(self, fd): + await self._epoll_wait(fd, "prio_task") diff --git a/trio/hazmat.py b/trio/hazmat.py index d57d6b3d3c..5c3f1b4ad5 100644 --- a/trio/hazmat.py +++ b/trio/hazmat.py @@ -22,6 +22,7 @@ "add_instrument", "current_clock", "current_statistics", + "wait_priority", "wait_writable", "wait_readable", "ParkingLot", From 6c41fb43c15cbc4315649a30520ba0c3f01597ea Mon Sep 17 00:00:00 2001 From: Matthias Urlichs Date: Wed, 28 Feb 2018 22:16:49 +0100 Subject: [PATCH 2/2] Ignore EPOLLPRI when selecting for read/writeability That would give us false positives. --- trio/_core/_io_epoll.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/trio/_core/_io_epoll.py b/trio/_core/_io_epoll.py index 68c567cd6a..1d425c1c51 100644 --- a/trio/_core/_io_epoll.py +++ b/trio/_core/_io_epoll.py @@ -78,12 +78,16 @@ def handle_io(self, timeout): # Clever hack stolen from selectors.EpollSelector: an event # with EPOLLHUP or EPOLLERR flags wakes both readers and # writers. - if flags & ~select.EPOLLIN and waiters.write_task is not None: - _core.reschedule(waiters.write_task) - waiters.write_task = None - if flags & ~select.EPOLLOUT and waiters.read_task is not None: + if flags & ~( + select.EPOLLOUT | select.EPOLLPRI + ) and waiters.read_task is not None: _core.reschedule(waiters.read_task) waiters.read_task = None + if flags & ~( + select.EPOLLIN | select.EPOLLPRI + ) and waiters.write_task is not None: + _core.reschedule(waiters.write_task) + waiters.write_task = None if flags & select.EPOLLPRI and waiters.prio_task is not None: _core.reschedule(waiters.prio_task) waiters.prio_task = None