how do you implement a reactor without a select?

Michele Simionato michele.simionato at gmail.com
Mon May 7 09:16:52 EDT 2007


I have always been curious about how people implement mainloops (or,
in Twisted terminology, reactors). So I sit down and I wrote the
following simple implementation:

import itertools

class SimpleReactor(object):

    DELAY = 0.001 # seconds

    def __init__(self):
        self._event = {} # action id -> (scheduled time, action, args)
        self._counter = itertools.count(1) # action id generator
        self.running = False

    def callLater(self, deltat, action, *args):
        """Schedule an action with arguments args in deltat seconds.
        Return the action id"""
        now = time.time()
        i = self._counter.next()
        self._event[i] = now + deltat, action, args
        return i

    def cancelCallLater(self, action_id):
        "Cancel the action identified by action_id"
        del self._event[action_id]

    def default_action(self): # to be overridden
        "Invoked at each lap in the mainloop"
        time.sleep(self.DELAY) # don't run too fast, rest a bit

    def cleanup_action(self): # to be overridden
        "Invoked at the end of the mainloop"

    def manage_exc(self, e):
        "Invoked at each call"
        raise e

    def dooneevent(self):
        "Perfom scheduled actions"
        now = time.time()
        for i, (start_time, action, args) in self._event.items():
            if now >= start_time: # it's time to start the action
                self.cancelCallLater(i) # don't run it again
                try:
                    action(*args)
                except Exception, e:
                    self.manage_exc(e)

    def run(self):
        "Run the main loop"
        self.running = True
        try:
            while self.running:
                self.default_action()
                self.dooneevent()
        except KeyboardInterrupt:
            print 'Stopped via CTRL-C'
        finally:
            self.cleanup_action()

    def stop(self):
        self.running = False

Notice that I copied the Twisted terminology, but
I did not look at Twisted implementation because I did not want to
use a select (I assume that the GUI mainloops do not use it either).
The trick I use is to store the actions to perform (which are
callables identified by an integer) in an event dictionary and
to run them in the mainlooop if the current time is greater than
the scheduled time.
I had to add a time.sleep(.001) call in the default_action to avoid
consuming 100%
of the CPU in the loop.
I wonder if real mainloops are done in this way and how bad/good is
this implementation compared to a serious one. Any suggestion/hint/
advice
is well appreciated. Thanks,

 Michele Simionato




More information about the Python-list mailing list