From 19b708e14102e9540fd8ad4e3beeeeb898ddf146 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Wed, 23 Jan 2019 12:44:08 +0000 Subject: [PATCH] issue #415, #477: Poller must handle POLLHUP too. Linux will fire poll() with simply the POLLHUP bit set even though it was not requested, resulting in an infinite loop. --- mitogen/core.py | 8 +++++--- tests/poller_test.py | 44 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/mitogen/core.py b/mitogen/core.py index b5f6a372..a7ff05b2 100644 --- a/mitogen/core.py +++ b/mitogen/core.py @@ -1900,8 +1900,10 @@ class Poller(object): """ pass + _readmask = select.POLLIN | select.POLLHUP + def _update(self, fd): - mask = (((fd in self._rfds) and select.POLLIN) | + mask = (((fd in self._rfds) and self._readmask) | ((fd in self._wfds) and select.POLLOUT)) if mask: self._pollobj.register(fd, mask) @@ -1951,8 +1953,8 @@ class Poller(object): events, _ = io_op(self._pollobj.poll, timeout) for fd, event in events: - if event & select.POLLIN: - _vv and IOLOG.debug('%r: POLLIN for %r', self, fd) + if event & self._readmask: + _vv and IOLOG.debug('%r: POLLIN|POLLHUP for %r', self, fd) data, gen = self._rfds.get(fd, (None, None)) if gen and gen < self._generation: yield data diff --git a/tests/poller_test.py b/tests/poller_test.py index c214f367..1d1e0cd0 100644 --- a/tests/poller_test.py +++ b/tests/poller_test.py @@ -13,6 +13,12 @@ import mitogen.parent import testlib +try: + next +except NameError: + # Python 2.4 + from mitogen.core import next + class SockMixin(object): def tearDown(self): @@ -345,6 +351,22 @@ class FileClosedMixin(PollerMixin, SockMixin): pass +class TtyHangupMixin(PollerMixin): + def test_tty_hangup_detected(self): + # bug in initial select.poll() implementation failed to detect POLLHUP. + master_fd, slave_fd = mitogen.parent.openpty() + try: + self.p.start_receive(master_fd) + self.assertEquals([], list(self.p.poll(0))) + os.close(slave_fd) + slave_fd = None + self.assertEquals([master_fd], list(self.p.poll(0))) + finally: + if slave_fd is not None: + os.close(slave_fd) + os.close(master_fd) + + class DistinctDataMixin(PollerMixin, SockMixin): # Verify different data is yielded for the same FD according to the event # being raised. @@ -368,29 +390,39 @@ class AllMixin(ReceiveStateMixin, FileClosedMixin, DistinctDataMixin, PollMixin, + TtyHangupMixin, CloseMixin): """ Helper to avoid cutpasting mixin names below. """ -@unittest2.skipIf(condition=not hasattr(select, 'select'), - reason='select.select() not supported') class SelectTest(AllMixin, testlib.TestCase): klass = mitogen.core.Poller +SelectTest = unittest2.skipIf( + condition=not hasattr(select, 'select'), + reason='select.select() not supported' +)(SelectTest) + -@unittest2.skipIf(condition=not hasattr(select, 'kqueue'), - reason='select.kqueue() not supported') class KqueueTest(AllMixin, testlib.TestCase): klass = mitogen.parent.KqueuePoller +KqueueTest = unittest2.skipIf( + condition=not hasattr(select, 'kqueue'), + reason='select.kqueue() not supported' +)(KqueueTest) + -@unittest2.skipIf(condition=not hasattr(select, 'epoll'), - reason='select.epoll() not supported') class EpollTest(AllMixin, testlib.TestCase): klass = mitogen.parent.EpollPoller +EpollTest = unittest2.skipIf( + condition=not hasattr(select, 'epoll'), + reason='select.epoll() not supported' +)(EpollTest) + if __name__ == '__main__': unittest2.main()