]> git.llucax.com Git - software/pymin.git/blob - pymin/eventloop.py
Implement timer in PyminDaemon using EventLoop signal handling.
[software/pymin.git] / pymin / eventloop.py
1 # vim: set encoding=utf-8 et sw=4 sts=4 :
2
3 r"""
4 A simple event loop.
5
6 Please see EventLoop class documentation for more info.
7 """
8
9 import select
10 import errno
11 import signal
12 from select import POLLIN, POLLPRI, POLLERR
13
14 __ALL__ = ('EventLoop', 'LoopInterruptedError')
15
16 class LoopInterruptedError(RuntimeError):
17     r"""
18     LoopInterruptedError(select_error) -> LoopInterruptedError instance.
19
20     This class is raised when the event loop is interrupted in an unexpected
21     way. It wraps a select error, which can be accessed using the 'select_error'
22     attribute.
23     """
24
25     def __init__(self, select_error):
26         r"""Initialize the object.
27
28         See the class documentation for more info.
29         """
30         self.select_error = select_error
31
32     def __repr__(self):
33         r"repr(obj) -> Object representation."
34         return 'LoopInterruptedError(select_error=%r)' % self.select_error
35
36     def __str__(self):
37         r"str(obj) -> String representation."
38         return 'Loop interrupted: %s' % self.select_error
39
40 # Flag to know if a signal was caught
41 signals = list()
42
43 # Alarm Signal handler
44 def signal_handler(signum, stack_frame):
45     global signals
46     signals.append(signum)
47
48 class EventLoop:
49     r"""EventLoop(file[, handler[, signals]]]) -> EventLoop.
50
51     This class implements a simple event loop based on select module.
52     It "listens" to activity a single 'file' object (a file, a pipe,
53     a socket, or even a simple file descriptor) and calls a 'handler'
54     function (or the handle() method if you prefer subclassing) every
55     time the file is ready for reading (or has an error).
56
57     'signals' is a dictionary with signals to be handled by the loop,
58     where keys are signal numbers and values are callbacks (which takes
59     2 arguments, first the event loop that captured the signal, and then
60     the captured signal number). Callbacks can be None if all signals
61     are handled by the handle_signal() member function.
62
63     This is a really simple example of usage using a hanlder callable:
64
65     >>> import os
66     >>> def handle(event_loop):
67             data = os.read(event_loop.fileno, 100)
68             os.write(1, 'Received message: %r\n' % data)
69     >>> p = EventLoop(0, handle)
70     >>> p.loop(once=True)
71
72     In this example only one event is handled (see the 'once' argument
73     of loop).
74
75     A more complex example, making a subclass and explicitly stopping
76     the loop, looks something like this:
77
78     >>> class Test(EventLoop):
79     >>>     def handle(self):
80     >>>         data = os.read(self.fileno, 100)
81     >>>         os.write(1, 'Received message: %r\n' % data)
82     >>>     def handle_signal(self, signum):
83     >>>         os.write(1, 'Signal %d received, stopping\n' % signum)
84     >>>         self.stop()
85     >>> p = Test(0, signals={signal.SIGTERM: None, signal.SIGINT: None})
86     >>> p.loop()
87
88     This example loops until the user enter interrupts the program (by
89     pressing Ctrl-C) or untile the program is terminated by a TERM signal
90     (kill) when stop() is called and the event loop is exited.
91     """
92
93     def __init__(self, file, handler=None, signals=None):
94         r"""Initialize the EventLoop object.
95
96         See EventLoop class documentation for more info.
97         """
98         self.poll = select.poll()
99         self._stop = False
100         self.__register(file)
101         self.handler = handler
102         self.signals = dict()
103         if signals is None:
104             signals = dict()
105         for (signum, sighandler) in signals.items():
106             self.set_signal(signum, sighandler)
107
108     def __register(self, file):
109         r"__register(file) -> None :: Register a new file for polling."
110         self._file = file
111         self.poll.register(self.fileno, POLLIN | POLLPRI | POLLERR)
112
113     def set_signal(self, signum, sighandler):
114         prev = self.signals.get(signum, None)
115         # If the signal was not already handled, handle it
116         if signum not in self.signals:
117             signal.signal(signum, signal_handler)
118         self.signals[signum] = sighandler
119         return prev
120
121     def get_signal_handler(self, signum):
122         return self.signals[signum]
123
124     def unset_signal(self, signum):
125         prev = self.signals[signum]
126         # Restore the default handler
127         signal.signal(signum, signal.SIG_DFL)
128         return prev
129
130     def set_file(self, file):
131         r"""set_file(file) -> None :: New file object to be monitored
132
133         Unregister the previous file object being monitored and register
134         a new one.
135         """
136         self.poll.unregister(self.fileno)
137         self.__register(file)
138
139     def get_file(self):
140         r"get_file() -> file object/int :: Get the current file object/fd."
141         return self._file
142
143     file = property(get_file, set_file, doc='File object (or descriptor)')
144
145     def get_fileno(self):
146         r"get_fileno() -> int :: Get the current file descriptor"
147         if hasattr(self.file, 'fileno'):
148             return self.file.fileno()
149         return self.file
150
151     fileno = property(get_fileno, doc='File descriptor (never a file object)')
152
153     def stop(self):
154         r"""stop() -> None :: Stop the event loop.
155
156         The event loop will be interrupted as soon as the current handler
157         finishes.
158         """
159         self._stop = True
160
161     def loop(self, once=False):
162         r"""loop([once]) -> None :: Wait for events.
163
164         Wait for events and handle then when they arrive. If once is True,
165         then only 1 event is processed and then this method returns.
166         """
167         # List of pending signals
168         global signals
169         while True:
170             try:
171                 res = self.poll.poll()
172             except select.error, e:
173                 # The error is not an interrupt caused by a signal, then raise
174                 if e.args[0] != errno.EINTR or not signals:
175                     raise LoopInterruptedError(e)
176             # If we have signals to process, we just do it
177             have_signals = bool(signals)
178             while signals:
179                 self.handle_signal(signals.pop(0))
180             # No signals to process, execute the regular handler
181             if not have_signals:
182                 self.handle()
183             import os
184             # Look if we have to stop
185             if self._stop or once:
186                 self._stop = False
187                 break
188
189     def handle(self):
190         r"handle() -> None :: Handle file descriptor events."
191         self.handler(self)
192
193     def handle_signal(self, signum):
194         r"handle_signal(signum) -> None :: Handles signals."
195         self.signals[signum](self, signum)
196
197 if __name__ == '__main__':
198
199     import os
200     import time
201
202     def handle(event_loop):
203         data = os.read(event_loop.fileno, 100)
204         os.write(1, 'Received message: %r\n' % data)
205
206     p = EventLoop(0, handle)
207
208     os.write(1, 'Say something once: ')
209     p.loop(once=True)
210     os.write(1, 'Great!\n')
211
212     class Test(EventLoop):
213         def handle(self):
214             data = os.read(self.fileno, 100)
215             os.write(1, 'Received message: %r\n' % data)
216         def handle_signal(self, signum):
217             os.write(1, 'Signal %d received, stopping\n' % signum)
218             self.stop()
219
220     p = Test(0, signals={signal.SIGTERM: None, signal.SIGINT: None})
221
222     os.write(1, 'Say a lot of things, then press Ctrl-C or kill me to stop: ')
223     p.loop()
224     os.write(1, 'Ok, bye!\n')
225