Ver Fonte

merge into current master, make work under py3

Chris McDonough há 11 anos atrás
pai
commit
75623f5811

+ 7 - 4
supervisor/options.py

@@ -13,7 +13,6 @@ import grp
 import resource
 import stat
 import pkg_resources
-import select
 import glob
 import platform
 import warnings
@@ -48,6 +47,7 @@ from supervisor.datatypes import profile_options
 from supervisor import loggers
 from supervisor import states
 from supervisor import xmlrpc
+from supervisor import poller
 
 mydir = os.path.abspath(os.path.dirname(__file__))
 version_txt = os.path.join(mydir, 'version.txt')
@@ -451,6 +451,7 @@ class ServerOptions(Options):
         self.parse_warnings = []
         self.parse_infos = []
         self.signal_receiver = SignalReceiver()
+        self.poller = poller.Poller(self)
 
     def version(self, dummy):
         """Print version to stdout and exit(0).
@@ -1056,6 +1057,11 @@ class ServerOptions(Options):
         return configs
 
     def daemonize(self):
+        self.poller.before_daemonize()
+        self._daemonize()
+        self.poller.after_daemonize()
+
+    def _daemonize(self):
         # To daemonize, we need to become the leader of our own session
         # (process) group.  If we do not, signals sent to our
         # parent process will also be sent to us.   This might be bad because
@@ -1229,9 +1235,6 @@ class ServerOptions(Options):
             except OSError:
                 pass
 
-    def select(self, r, w, x, timeout):
-        return select.select(r, w, x, timeout)
-
     def kill(self, pid, signal):
         os.kill(pid, signal)
 

+ 213 - 0
supervisor/poller.py

@@ -0,0 +1,213 @@
+import select
+import errno
+
+class BasePoller:
+
+    def __init__(self, options):
+        self.options = options
+        self.initialize()
+
+    def initialize(self):
+        pass
+
+    def register_readable(self, fd):
+        raise NotImplementedError
+
+    def register_writable(self, fd):
+        raise NotImplementedError
+
+    def unregister(self, fd):
+        raise NotImplementedError
+
+    def poll(self, timeout):
+        raise NotImplementedError
+
+    def before_daemonize(self):
+        pass
+
+    def after_daemonize(self):
+        pass
+
+
+class SelectPoller(BasePoller):
+
+    def initialize(self):
+        self._select = select
+        self._init_fdsets()
+
+    def register_readable(self, fd):
+        self.readable.add(fd)
+
+    def register_writable(self, fd):
+        self.writable.add(fd)
+
+    def unregister(self, fd):
+        if fd in self.readable:
+            self.readable.remove(fd)
+        if fd in self.writable:
+            self.writable.remove(fd)
+
+    def unregister_all(self):
+        self._init_fdsets()
+
+    def poll(self, timeout):
+        try:
+            r, w, x = self._select.select(
+                self.readable,
+                self.writable,
+                [], timeout
+                )
+        except select.error as err:
+            if err.args[0] == errno.EINTR:
+                self.options.logger.blather('EINTR encountered in poll')
+                return [], []
+            if err.args[0] == errno.EBADF:
+                self.options.logger.blather('EBADF encountered in poll')
+                self.unregister_all()
+                return [], []
+            raise
+        return r, w
+
+    def _init_fdsets(self):
+        self.readable = set()
+        self.writable = set()
+
+class PollPoller(BasePoller):
+
+    def initialize(self):
+        self._poller = select.poll()
+        self.READ = select.POLLIN | select.POLLPRI | select.POLLHUP
+        self.WRITE = select.POLLOUT
+
+    def register_readable(self, fd):
+        self._poller.register(fd, self.READ)
+
+    def register_writable(self, fd):
+        self._poller.register(fd, self.WRITE)
+
+    def unregister(self, fd):
+        self._poller.unregister(fd)
+
+    def poll(self, timeout):
+        fds = self._poll_fds(timeout)
+        readables, writables = [], []
+        for fd, eventmask in fds:
+            if self._ignore_invalid(fd, eventmask):
+                continue
+            if eventmask & self.READ:
+                readables.append(fd)
+            if eventmask & self.WRITE:
+                writables.append(fd)
+        return readables, writables
+
+    def _poll_fds(self, timeout):
+        try:
+            return self._poller.poll(timeout * 1000)
+        except select.error as err:
+            if err.args[0] == errno.EINTR:
+                self.options.logger.blather('EINTR encountered in poll')
+                return []
+            raise
+
+    def _ignore_invalid(self, fd, eventmask):
+        if eventmask & select.POLLNVAL:
+            # POLLNVAL means `fd` value is invalid, not open.
+            # When a process quits it's `fd`s are closed so there
+            # is no more reason to keep this `fd` registered
+            # If the process restarts it's `fd`s are registered again
+            self.unregister(fd)
+            return True
+        return False
+
+class KQueuePoller(BasePoller):
+    '''
+    Wrapper for select.kqueue()/kevent()
+    '''
+
+    max_events = 1000
+
+    def initialize(self):
+        self._kqueue = select.kqueue()
+        self.readables = set()
+        self.writables = set()
+
+    def register_readable(self, fd):
+        self.readables.add(fd)
+        kevent = select.kevent(fd, filter=select.KQ_FILTER_READ,
+                               flags=select.KQ_EV_ADD)
+        self._kqueue_control(fd, kevent)
+
+    def register_writable(self, fd):
+        self.writables.add(fd)
+        kevent = select.kevent(fd, filter=select.KQ_FILTER_WRITE,
+                               flags=select.KQ_EV_ADD)
+        self._kqueue_control(fd, kevent)
+
+    def unregister(self, fd):
+        kevent = select.kevent(
+            fd,
+            filter=(select.KQ_FILTER_READ | select.KQ_FILTER_WRITE),
+            flags=select.KQ_EV_DELETE
+            )
+        self._forget_fd(fd)
+        self._kqueue_control(fd, kevent)
+
+    def _kqueue_control(self, fd, kevent):
+        try:
+            self._kqueue.control([kevent], 0)
+        except OSError as error:
+            if error.errno == errno.EBADF:
+                self.options.logger.blather('EBADF encountered in kqueue. '
+                                            'Invalid file descriptor %s' % fd)
+            else:
+                raise
+
+    def _forget_fd(self, fd):
+        for collection in (self.readables, self.writables):
+            try:
+                collection.remove(fd)
+            except KeyError:
+                pass
+
+    def poll(self, timeout):
+        readables, writables = [], []
+
+        try:
+            kevents = self._kqueue.control(None, self.max_events, timeout)
+        except OSError as error:
+            if error.errno == errno.EINTR:
+                self.options.logger.blather('EINTR encountered in poll')
+                return readables, writables
+            raise
+
+        for kevent in kevents:
+            if kevent.filter == select.KQ_FILTER_READ:
+                readables.append(kevent.ident)
+            if kevent.filter == select.KQ_FILTER_WRITE:
+                writables.append(kevent.ident)
+
+        return readables, writables
+
+    def before_daemonize(self):
+        self._kqueue.close()
+        self._kqueue = None
+
+    def after_daemonize(self):
+        self._kqueue = select.kqueue()
+        for fd in self.readables:
+            self.register_readable(fd)
+        for fd in self.writables:
+            self.register_writable(fd)
+
+def implements_poll():
+    return hasattr(select, 'poll')
+
+def implements_kqueue():
+    return hasattr(select, 'kqueue')
+
+if implements_kqueue():
+    Poller = KQueuePoller
+elif implements_poll():
+    Poller = PollPoller
+else:
+    Poller = SelectPoller

+ 4 - 13
supervisor/supervisord.py

@@ -209,22 +209,13 @@ class Supervisor:
                     # killing everything), it's OK to shutdown or reload
                     raise asyncore.ExitNow
 
-            r, w, x = [], [], []
-
             for fd, dispatcher in combined_map.items():
                 if dispatcher.readable():
-                    r.append(fd)
+                    self.options.poller.register_readable(fd)
                 if dispatcher.writable():
-                    w.append(fd)
-
-            try:
-                r, w, x = self.options.select(r, w, x, timeout)
-            except select.error, err:
-                r = w = x = []
-                if err.args[0] == errno.EINTR:
-                    self.options.logger.blather('EINTR encountered in select')
-                else:
-                    raise
+                    self.options.poller.register_writable(fd)
+
+            r, w = self.options.poller.poll(timeout)
 
             for fd in r:
                 if combined_map.has_key(fd):

+ 14 - 8
supervisor/tests/base.py

@@ -54,8 +54,6 @@ class DummyOptions:
         self.privsdropped = None
         self.logs_reopened = False
         self.environment_processed = False
-        self.select_result = [], [], []
-        self.select_error = None
         self.write_accept = None
         self.write_error = None
         self.tempfile_name = '/foo/bar'
@@ -70,6 +68,7 @@ class DummyOptions:
         self.changed_directory = False
         self.chdir_error = None
         self.umaskset = None
+        self.poller = DummyPoller(self)
 
     def getLogger(self, *args, **kw):
         logger = DummyLogger()
@@ -221,12 +220,6 @@ class DummyOptions:
     def mktempfile(self, prefix, suffix, dir):
         return self.tempfile_name
 
-    def select(self, r, w, x, timeout):
-        import select
-        if self.select_error:
-            raise select.error(self.select_error)
-        return self.select_result
-
     def remove(self, path):
         import os
         if self.remove_error:
@@ -1130,6 +1123,19 @@ class DummyEvent:
     def __str__(self):
         return 'dummy event'
 
+class DummyPoller:
+    def __init__(self, options):
+        self.result = [], []
+
+    def register_readable(self, fd):
+        pass
+
+    def register_writable(self, fd):
+        pass
+
+    def poll(self, timeout):
+        return self.result
+        
 def dummy_handler(event, result):
     pass
 

+ 8 - 0
supervisor/tests/test_options.py

@@ -2997,6 +2997,14 @@ class ServerOptionsTests(unittest.TestCase):
         msg = instance.dropPrivileges(42)
         self.assertEqual(msg, "Can't drop privilege as nonroot user")
 
+    def test_daemonize_notifies_poller_before_and_after_fork(self):
+        instance = self._makeOne()
+        instance._daemonize = lambda: None
+        instance.poller = Mock()
+        instance.daemonize()
+        instance.poller.before_daemonize.assert_called_once_with()
+        instance.poller.after_daemonize.assert_called_once_with()
+
 class TestProcessConfig(unittest.TestCase):
     def _getTargetClass(self):
         from supervisor.options import ProcessConfig

+ 361 - 0
supervisor/tests/test_poller.py

@@ -0,0 +1,361 @@
+import sys
+import unittest
+import errno
+import select
+from mock import Mock
+
+from supervisor.poller import SelectPoller, PollPoller, KQueuePoller
+from supervisor.poller import implements_poll, implements_kqueue
+from supervisor.tests.base import DummyOptions
+
+# this base class is used instead of unittest.TestCase to hide
+# a TestCase subclass from test runner when the implementation is
+# not available
+SkipTestCase = object
+
+class SelectPollerTests(unittest.TestCase):
+
+    def _makeOne(self, options):
+        return SelectPoller(options)
+
+    def test_register_readable(self):
+        poller = self._makeOne(DummyOptions())
+        poller.register_readable(6)
+        poller.register_readable(7)
+        self.assertEqual(sorted(poller.readable), [6,7])
+
+    def test_register_writable(self):
+        poller = self._makeOne(DummyOptions())
+        poller.register_writable(6)
+        poller.register_writable(7)
+        self.assertEqual(sorted(poller.writable), [6,7])
+
+    def test_poll_returns_readables_and_writables(self):
+        _select = DummySelect(result={'readables': [6],
+                                      'writables': [8]})
+        poller = self._makeOne(DummyOptions())
+        poller._select = _select
+        poller.register_readable(6)
+        poller.register_readable(7)
+        poller.register_writable(8)
+        readables, writables = poller.poll(1)
+        self.assertEqual(readables, [6])
+        self.assertEqual(writables, [8])
+
+    def test_poll_ignores_eintr(self):
+        _select = DummySelect(error=errno.EINTR)
+        options = DummyOptions()
+        poller = self._makeOne(options)
+        poller._select = _select
+        poller.register_readable(6)
+        poller.poll(1)
+        self.assertEqual(options.logger.data[0], 'EINTR encountered in poll')
+
+    def test_poll_ignores_ebadf(self):
+        _select = DummySelect(error=errno.EBADF)
+        options = DummyOptions()
+        poller = self._makeOne(options)
+        poller._select = _select
+        poller.register_readable(6)
+        poller.poll(1)
+        self.assertEqual(options.logger.data[0], 'EBADF encountered in poll')
+        self.assertEqual(list(poller.readable), [])
+        self.assertEqual(list(poller.writable), [])
+
+    def test_poll_uncaught_exception(self):
+        _select = DummySelect(error=errno.EPERM)
+        options = DummyOptions()
+        poller = self._makeOne(options)
+        poller._select = _select
+        poller.register_readable(6)
+        self.assertRaises(select.error, poller.poll, 1)
+
+if implements_kqueue():
+    KQueuePollerTestsBase = unittest.TestCase
+else:
+    KQueuePollerTestsBase = SkipTestCase
+
+class KQueuePollerTests(KQueuePollerTestsBase):
+
+    def _makeOne(self, options):
+        return KQueuePoller(options)
+
+    def test_register_readable(self):
+        kqueue = DummyKQueue()
+        poller = self._makeOne(DummyOptions())
+        poller._kqueue = kqueue
+        poller.register_readable(6)
+        self.assertEqual(list(poller.readables), [6])
+        self.assertEqual(len(kqueue.registered_kevents), 1)
+        self.assertReadEventAdded(kqueue.registered_kevents[0], 6)
+
+    def test_register_writable(self):
+        kqueue = DummyKQueue()
+        poller = self._makeOne(DummyOptions())
+        poller._kqueue = kqueue
+        poller.register_writable(7)
+        self.assertEqual(list(poller.writables), [7])
+        self.assertEqual(len(kqueue.registered_kevents), 1)
+        self.assertWriteEventAdded(kqueue.registered_kevents[0], 7)
+
+    def test_unregister(self):
+        kqueue = DummyKQueue()
+        poller = self._makeOne(DummyOptions())
+        poller._kqueue = kqueue
+        poller.register_writable(7)
+        poller.register_readable(8)
+        poller.unregister(7)
+        poller.unregister(100)  # not registered, ignore error
+        self.assertEqual(list(poller.writables), [])
+        self.assertEqual(list(poller.readables), [8])
+        self.assertWriteEventAdded(kqueue.registered_kevents[0], 7)
+        self.assertReadEventAdded(kqueue.registered_kevents[1], 8)
+        self.assertDeletedEvent(kqueue.registered_kevents[2], 7)
+
+    def test_poll_returns_readables_and_writables(self):
+        kqueue = DummyKQueue(result=[(6, select.KQ_FILTER_READ),
+                                     (7, select.KQ_FILTER_READ),
+                                     (8, select.KQ_FILTER_WRITE)])
+        poller = self._makeOne(DummyOptions())
+        poller._kqueue = kqueue
+        poller.register_readable(6)
+        poller.register_readable(7)
+        poller.register_writable(8)
+        readables, writables = poller.poll(1000)
+        self.assertEqual(readables, [6,7])
+        self.assertEqual(writables, [8])
+
+    def test_poll_ignores_eintr(self):
+        kqueue = DummyKQueue(raise_errno_poll=errno.EINTR)
+        options = DummyOptions()
+        poller = self._makeOne(options)
+        poller._kqueue = kqueue
+        poller.register_readable(6)
+        poller.poll(1000)
+        self.assertEqual(options.logger.data[0], 'EINTR encountered in poll')
+
+    def test_register_readable_and_writable_ignores_ebadf(self):
+        _kqueue = DummyKQueue(raise_errno_register=errno.EBADF)
+        options = DummyOptions()
+        poller = self._makeOne(options)
+        poller._kqueue = _kqueue
+        poller.register_readable(6)
+        poller.register_writable(7)
+        self.assertEqual(options.logger.data[0],
+                         'EBADF encountered in kqueue. Invalid file descriptor 6')
+        self.assertEqual(options.logger.data[1],
+                         'EBADF encountered in kqueue. Invalid file descriptor 7')
+
+    def test_register_uncaught_exception(self):
+        _kqueue = DummyKQueue(raise_errno_register=errno.ENOMEM)
+        options = DummyOptions()
+        poller = self._makeOne(options)
+        poller._kqueue = _kqueue
+        self.assertRaises(OSError, poller.register_readable, 5)
+
+    def test_poll_uncaught_exception(self):
+        kqueue = DummyKQueue(raise_errno_poll=errno.EINVAL)
+        options = DummyOptions()
+        poller = self._makeOne(options)
+        poller._kqueue = kqueue
+        poller.register_readable(6)
+        self.assertRaises(OSError, poller.poll, 1000)
+
+    def test_before_daemonize_closes_kqueue(self):
+        mock_kqueue = Mock()
+        options = DummyOptions()
+        poller = self._makeOne(options)
+        poller._kqueue = mock_kqueue
+        poller.before_daemonize()
+        mock_kqueue.close.assert_called_once_with()
+        self.assertEqual(poller._kqueue, None)
+
+    def test_after_daemonize_restores_kqueue(self):
+        options = DummyOptions()
+        poller = self._makeOne(options)
+        poller.readables = [1]
+        poller.writables = [3]
+        poller.register_readable = Mock()
+        poller.register_writable = Mock()
+        poller.after_daemonize()
+        self.assertTrue(isinstance(poller._kqueue, select.kqueue))
+        poller.register_readable.assert_called_with(1)
+        poller.register_writable.assert_called_with(3)
+
+    def assertReadEventAdded(self, kevent, fd):
+        self.assertKevent(kevent, fd, select.KQ_FILTER_READ, select.KQ_EV_ADD)
+
+    def assertWriteEventAdded(self, kevent, fd):
+        self.assertKevent(kevent, fd, select.KQ_FILTER_WRITE, select.KQ_EV_ADD)
+
+    def assertDeletedEvent(self, kevent, fd):
+        self.assertKevent(kevent, fd, select.KQ_FILTER_READ | select.KQ_FILTER_WRITE,
+                          select.KQ_EV_DELETE)
+
+    def assertKevent(self, kevent, ident, filter, flags):
+        self.assertEqual(kevent.ident, ident)
+        self.assertEqual(kevent.filter, filter)
+        self.assertEqual(kevent.flags, flags)
+
+
+if implements_poll():
+    PollerPollTestsBase = unittest.TestCase
+else:
+    PollerPollTestsBase = SkipTestCase
+
+class PollerPollTests(PollerPollTestsBase):
+
+    def _makeOne(self, options):
+        return PollPoller(options)
+
+    def test_register_readable(self):
+        select_poll = DummySelectPoll()
+        poller = self._makeOne(DummyOptions())
+        poller._poller = select_poll
+        poller.register_readable(6)
+        poller.register_readable(7)
+        self.assertEqual(select_poll.registered_as_readable, [6,7])
+
+    def test_register_writable(self):
+        select_poll = DummySelectPoll()
+        poller = self._makeOne(DummyOptions())
+        poller._poller = select_poll
+        poller.register_writable(6)
+        self.assertEqual(select_poll.registered_as_writable, [6])
+
+    def test_poll_returns_readables_and_writables(self):
+        select_poll = DummySelectPoll(result=[(6, select.POLLIN),
+                                              (7, select.POLLPRI),
+                                              (8, select.POLLOUT),
+                                              (9, select.POLLHUP)])
+        poller = self._makeOne(DummyOptions())
+        poller._poller = select_poll
+        poller.register_readable(6)
+        poller.register_readable(7)
+        poller.register_writable(8)
+        poller.register_readable(9)
+        readables, writables = poller.poll(1000)
+        self.assertEqual(readables, [6,7,9])
+        self.assertEqual(writables, [8])
+
+    def test_poll_ignores_eintr(self):
+        select_poll = DummySelectPoll(error=errno.EINTR)
+        options = DummyOptions()
+        poller = self._makeOne(options)
+        poller._poller = select_poll
+        poller.register_readable(9)
+        poller.poll(1000)
+        self.assertEqual(options.logger.data[0], 'EINTR encountered in poll')
+
+    def test_poll_uncaught_exception(self):
+        select_poll = DummySelectPoll(error=errno.EBADF)
+        options = DummyOptions()
+        poller = self._makeOne(options)
+        poller._poller = select_poll
+        poller.register_readable(9)
+        self.assertRaises(select.error, poller.poll, 1000)
+
+    def test_poll_ignores_and_unregisters_closed_fd(self):
+        select_poll = DummySelectPoll(result=[(6, select.POLLNVAL),
+                                              (7, select.POLLPRI)])
+        poller = self._makeOne(DummyOptions())
+        poller._poller = select_poll
+        poller.register_readable(6)
+        poller.register_readable(7)
+        readables, writables = poller.poll(1000)
+        self.assertEqual(readables, [7])
+        self.assertEqual(select_poll.unregistered, [6])
+
+class DummySelect(object):
+    '''
+    Fake implementation of select.select()
+    '''
+    def __init__(self, result=None, error=None):
+        result = result or {}
+        self.readables = result.get('readables', [])
+        self.writables = result.get('writables', [])
+        self.error = error
+
+    def select(self, r, w, x, timeout):
+        if self.error:
+            raise select.error(self.error)
+        return self.readables, self.writables, []
+
+class DummySelectPoll(object):
+    '''
+    Fake implementation of select.poll()
+    '''
+    def __init__(self, result=None, error=None):
+        self.result = result or []
+        self.error = error
+        self.registered_as_readable = []
+        self.registered_as_writable = []
+        self.unregistered = []
+
+    def register(self, fd, eventmask):
+        if eventmask == select.POLLIN | select.POLLPRI | select.POLLHUP:
+            self.registered_as_readable.append(fd)
+        elif eventmask == select.POLLOUT:
+            self.registered_as_writable.append(fd)
+        else:
+            raise ValueError("Registered a fd on unknown eventmask: '{0}'".format(eventmask))
+
+    def unregister(self, fd):
+        self.unregistered.append(fd)
+
+    def poll(self, timeout):
+        if self.error:
+            raise select.error(self.error)
+        return self.result
+
+
+class DummyKQueue(object):
+    '''
+    Fake implementation of select.kqueue()
+    '''
+    def __init__(self, result=None, raise_errno_poll=None, raise_errno_register=None):
+        self.result = result or []
+        self.errno_poll = raise_errno_poll
+        self.errno_register = raise_errno_register
+        self.registered_kevents = []
+        self.registered_flags = []
+
+    def control(self, kevents, max_events, timeout=None):
+        if kevents is None:    # being called on poll()
+            self.assert_max_events_on_poll(max_events)
+            self.raise_error(self.errno_poll)
+            return self.build_result()
+
+        self.assert_max_events_on_register(max_events)
+        self.raise_error(self.errno_register)
+        self.registered_kevents.extend(kevents)
+
+    def raise_error(self, err):
+        if not err: return
+        ex = OSError()
+        ex.errno = err
+        raise ex
+
+    def build_result(self):
+        return [FakeKEvent(ident, filter) for ident,filter in self.result]
+
+    def assert_max_events_on_poll(self, max_events):
+        assert max_events == KQueuePoller.max_events, (
+            "`max_events` parameter of `kqueue.control() should be %d"
+            % KQueuePoller.max_events)
+
+    def assert_max_events_on_register(self, max_events):
+        assert max_events == 0, (
+            "`max_events` parameter of `kqueue.control()` should be 0 on register")
+
+class FakeKEvent(object):
+    def __init__(self, ident, filter):
+        self.ident = ident
+        self.filter = filter
+
+
+def test_suite():
+    return unittest.findTestCases(sys.modules[__name__])
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

+ 3 - 21
supervisor/tests/test_supervisord.py

@@ -447,26 +447,9 @@ class SupervisordTests(unittest.TestCase):
         supervisord.runforever()
         self.assertEqual(len(supervisord.ticks), 3)
 
-    def test_runforever_select_eintr(self):
-        options = DummyOptions()
-        import errno
-        options.select_error = errno.EINTR
-        supervisord = self._makeOne(options)
-        options.test = True
-        supervisord.runforever()
-        self.assertEqual(options.logger.data[0], 'EINTR encountered in select')
-
-    def test_runforever_select_uncaught_exception(self):
-        options = DummyOptions()
-        import errno
-        options.select_error = errno.EBADF
-        supervisord = self._makeOne(options)
-        import select
-        options.test = True
-        self.assertRaises(select.error, supervisord.runforever)
-
-    def test_runforever_select_dispatchers(self):
+    def test_runforever_poll_dispatchers(self):
         options = DummyOptions()
+        options.poller.result = [6], [7, 8]
         supervisord = self._makeOne(options)
         pconfig = DummyPConfig(options, 'foo', '/bin/foo',)
         gconfig = DummyPGroupConfig(options, pconfigs=[pconfig])
@@ -476,7 +459,6 @@ class SupervisordTests(unittest.TestCase):
         error = DummyDispatcher(writable=True, error=OSError)
         pgroup.dispatchers = {6:readable, 7:writable, 8:error}
         supervisord.process_groups = {'foo': pgroup}
-        options.select_result = [6], [7, 8], []
         options.test = True
         supervisord.runforever()
         self.assertEqual(pgroup.transitioned, True)
@@ -486,6 +468,7 @@ class SupervisordTests(unittest.TestCase):
 
     def test_runforever_select_dispatcher_exitnow(self):
         options = DummyOptions()
+        options.poller.result = [6], []
         supervisord = self._makeOne(options)
         pconfig = DummyPConfig(options, 'foo', '/bin/foo',)
         gconfig = DummyPGroupConfig(options, pconfigs=[pconfig])
@@ -494,7 +477,6 @@ class SupervisordTests(unittest.TestCase):
         exitnow = DummyDispatcher(readable=True, error=asyncore.ExitNow)
         pgroup.dispatchers = {6:exitnow}
         supervisord.process_groups = {'foo': pgroup}
-        options.select_result = [6], [], []
         options.test = True
         self.assertRaises(asyncore.ExitNow, supervisord.runforever)