1 # vim: set encoding=utf-8 et sw=4 sts=4 :
6 Please see EventLoop class documentation for more info.
12 from select import POLLIN, POLLPRI, POLLERR
13 import logging ; log = logging.getLogger('pymin.eventloop')
15 __all__ = ('EventLoop', 'LoopInterruptedError')
17 class LoopInterruptedError(RuntimeError):
19 LoopInterruptedError(select_error) -> LoopInterruptedError instance.
21 This class is raised when the event loop is interrupted in an unexpected
22 way. It wraps a select error, which can be accessed using the 'select_error'
26 def __init__(self, select_error):
27 r"""Initialize the object.
29 See the class documentation for more info.
31 self.select_error = select_error
34 r"repr(obj) -> Object representation."
35 return 'LoopInterruptedError(select_error=%r)' % self.select_error
38 r"str(obj) -> String representation."
39 return 'Loop interrupted: %s' % self.select_error
41 # Flag to know if a signal was caught
44 # Alarm Signal handler
45 def signal_handler(signum, stack_frame):
47 signals.append(signum)
50 r"""EventLoop(file[, handler[, signals]]]) -> EventLoop.
52 This class implements a simple event loop based on select module.
53 It "listens" to activity a single 'file' object (a file, a pipe,
54 a socket, or even a simple file descriptor) and calls a 'handler'
55 function (or the handle() method if you prefer subclassing) every
56 time the file is ready for reading (or has an error).
58 'signals' is a dictionary with signals to be handled by the loop,
59 where keys are signal numbers and values are callbacks (which takes
60 2 arguments, first the event loop that captured the signal, and then
61 the captured signal number). Callbacks can be None if all signals
62 are handled by the handle_signal() member function.
64 This is a really simple example of usage using a hanlder callable:
67 >>> def handle(event_loop):
68 data = os.read(event_loop.fileno, 100)
69 os.write(1, 'Received message: %r\n' % data)
70 >>> p = EventLoop(0, handle)
73 In this example only one event is handled (see the 'once' argument
76 A more complex example, making a subclass and explicitly stopping
77 the loop, looks something like this:
79 >>> class Test(EventLoop):
81 >>> data = os.read(self.fileno, 100)
82 >>> os.write(1, 'Received message: %r\n' % data)
83 >>> def handle_signal(self, signum):
84 >>> os.write(1, 'Signal %d received, stopping\n' % signum)
86 >>> p = Test(0, signals={signal.SIGTERM: None, signal.SIGINT: None})
89 This example loops until the user enter interrupts the program (by
90 pressing Ctrl-C) or untile the program is terminated by a TERM signal
91 (kill) when stop() is called and the event loop is exited.
94 def __init__(self, file, handler=None, signals=None):
95 r"""Initialize the EventLoop object.
97 See EventLoop class documentation for more info.
99 log.debug(u'EventLoop(%r, %r, %r)', file, handler, signals)
100 self.poll = select.poll()
102 self.__register(file)
103 self.handler = handler
104 self.signals = dict()
107 for (signum, sighandler) in signals.items():
108 self.set_signal(signum, sighandler)
110 def __register(self, file):
111 r"__register(file) -> None :: Register a new file for polling."
113 self.poll.register(self.fileno, POLLIN | POLLPRI | POLLERR)
115 def set_signal(self, signum, sighandler):
116 prev = self.signals.get(signum, None)
117 # If the signal was not already handled, handle it
118 if signum not in self.signals:
119 signal.signal(signum, signal_handler)
120 self.signals[signum] = sighandler
123 def get_signal_handler(self, signum):
124 return self.signals[signum]
126 def unset_signal(self, signum):
127 prev = self.signals[signum]
128 # Restore the default handler
129 signal.signal(signum, signal.SIG_DFL)
132 def set_file(self, file):
133 r"""set_file(file) -> None :: New file object to be monitored
135 Unregister the previous file object being monitored and register
138 self.poll.unregister(self.fileno)
139 self.__register(file)
142 r"get_file() -> file object/int :: Get the current file object/fd."
145 file = property(get_file, set_file, doc='File object (or descriptor)')
147 def get_fileno(self):
148 r"get_fileno() -> int :: Get the current file descriptor"
149 if hasattr(self.file, 'fileno'):
150 return self.file.fileno()
153 fileno = property(get_fileno, doc='File descriptor (never a file object)')
156 r"""stop() -> None :: Stop the event loop.
158 The event loop will be interrupted as soon as the current handler
161 log.debug(u'EventLoop.stop()')
164 def loop(self, once=False):
165 r"""loop([once]) -> None :: Wait for events.
167 Wait for events and handle then when they arrive. If once is True,
168 then only 1 event is processed and then this method returns.
170 log.debug(u'EventLoop.loop(%s)', once)
171 # List of pending signals
175 log.debug(u'EventLoop.loop: polling')
176 res = self.poll.poll()
177 except select.error, e:
178 # The error is not an interrupt caused by a signal, then raise
179 if e.args[0] != errno.EINTR or not signals:
180 raise LoopInterruptedError(e)
181 # If we have signals to process, we just do it
182 have_signals = bool(signals)
184 signum = signals.pop(0)
185 log.debug(u'EventLoop.loop: processing signal %d...', signum)
186 self.handle_signal(signum)
187 # No signals to process, execute the regular handler
189 log.debug(u'EventLoop.loop: processing event...')
191 # Look if we have to stop
192 if self._stop or once:
193 log.debug(u'EventLoop.loop: stopped')
198 r"handle() -> None :: Handle file descriptor events."
201 def handle_signal(self, signum):
202 r"handle_signal(signum) -> None :: Handles signals."
203 self.signals[signum](self, signum)
205 if __name__ == '__main__':
208 level = logging.DEBUG,
209 format = '%(asctime)s %(levelname)-8s %(message)s',
210 datefmt = '%H:%M:%S',
216 def handle(event_loop):
217 data = os.read(event_loop.fileno, 100)
218 os.write(1, 'Received message: %r\n' % data)
220 p = EventLoop(0, handle)
222 os.write(1, 'Say something once:\n')
224 os.write(1, 'Great!\n')
226 class Test(EventLoop):
228 data = os.read(self.fileno, 100)
229 os.write(1, 'Received message: %r\n' % data)
230 def handle_signal(self, signum):
231 os.write(1, 'Signal %d received, stopping\n' % signum)
234 p = Test(0, signals={signal.SIGTERM: None, signal.SIGINT: None})
236 os.write(1, 'Say a lot of things, then press Ctrl-C or kill me to stop: ')
238 os.write(1, 'Ok, bye!\n')