author | Tero Marttila <terom@fixme.fi> |
Sat, 26 Sep 2009 23:38:02 +0300 | |
changeset 56 | 07ed878c847b |
parent 51 | c6b4abfc21da |
permissions | -rw-r--r-- |
51 | 1 |
from qmsk.net.lib.event2.event cimport build_timeout, event |
2 |
from qmsk.net.socket.platform cimport AF_UNIX, SOCK_STREAM |
|
3 |
cimport qmsk.net.py as py |
|
39 | 4 |
|
41 | 5 |
class EventError (Exception) : |
6 |
""" |
|
7 |
Base class of errors raised by lib.event2 code |
|
8 |
""" |
|
9 |
||
10 |
pass |
|
11 |
||
12 |
class EventCallError (EventError) : |
|
13 |
""" |
|
14 |
Some libevent function returned an error code. |
|
15 |
""" |
|
16 |
||
17 |
def __init__ (self, func, msg=None) : |
|
18 |
super(EventCallError, self).__init__(func) |
|
19 |
||
20 |
self.func = func |
|
21 |
self.msg = msg |
|
22 |
||
51 | 23 |
cdef lib.event* build_pysignal_ev (lib.event_base *ev_base, lib._ev_callback_t cb_func, void *cb_arg) except NULL : |
24 |
""" |
|
25 |
This constructs an returns an event* which will activate the given callback when a Python signal handler runs. |
|
26 |
||
27 |
The event is constructed as a persistent internal event, which means that it *must* be removed before trying to |
|
28 |
release the event_base. |
|
29 |
""" |
|
30 |
||
31 |
cdef lib.evutil_socket_t fds[2] |
|
32 |
||
33 |
# construct the pipe/sockpair |
|
34 |
if lib.evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0 : |
|
35 |
raise EventCallError("evutil_socketpair(signals)") |
|
36 |
||
37 |
# prep: nonblocking |
|
38 |
# XXX: close-on-exec? |
|
39 |
lib.evutil_make_socket_nonblocking(fds[0]) |
|
40 |
lib.evutil_make_socket_nonblocking(fds[1]) |
|
41 |
||
42 |
# build event for one end |
|
43 |
cdef lib.event *ev = lib.event_new(ev_base, fds[1], lib.EV_READ | lib.EV_PERSIST, cb_func, cb_arg) |
|
44 |
||
45 |
# set magic flag to not count as active |
|
46 |
ev.ev_flags |= lib.EVLIST_INTERNAL |
|
47 |
||
48 |
if ev == NULL : |
|
49 |
raise EventCallError("event_new(signals)") |
|
50 |
||
51 |
# register wakeup fd |
|
52 |
py.PySignal_SetWakeupFd(fds[0]) |
|
53 |
||
54 |
return ev |
|
55 |
||
56 |
||
57 |
cdef void on_pysignal_wakeup (int sig, short what, void *arg) : |
|
58 |
""" |
|
59 |
Python's signal handler trapped a signal, we need to run the signal handlers. |
|
60 |
||
61 |
This is executed by libevent's event_base_loop as a normal event (using the wakeup fd built using |
|
62 |
build_pysignal_ev), and hence the signal handlers will be run from "inside" of event_base.loop(). Exceptions |
|
63 |
raised by the signal handlers should be propagated "out" of event_base.loop(). |
|
64 |
""" |
|
65 |
||
66 |
cdef event_base ev_base = <event_base> arg |
|
67 |
||
68 |
try : |
|
69 |
# run the real signal handlers for any pending signals |
|
70 |
py.PyErr_CheckSignals() |
|
71 |
||
72 |
except : |
|
73 |
# TODO: implement propagate-to-event_base.loop() |
|
74 |
# drop out of the event loop |
|
75 |
ev_base.loopbreak() |
|
76 |
||
77 |
# Cython will log it |
|
78 |
raise |
|
79 |
||
80 |
||
39 | 81 |
cdef class event_base : |
82 |
||
83 |
def __init__ (self) : |
|
56
07ed878c847b
doctweaks and some skeleton tests for event-aliveness
Tero Marttila <terom@fixme.fi>
parents:
51
diff
changeset
|
84 |
""" |
07ed878c847b
doctweaks and some skeleton tests for event-aliveness
Tero Marttila <terom@fixme.fi>
parents:
51
diff
changeset
|
85 |
Constructs a new event_base with default parameters. |
07ed878c847b
doctweaks and some skeleton tests for event-aliveness
Tero Marttila <terom@fixme.fi>
parents:
51
diff
changeset
|
86 |
""" |
39 | 87 |
|
88 |
# construct |
|
89 |
self.ev_base = lib.event_base_new() |
|
90 |
||
91 |
if self.ev_base == NULL : |
|
41 | 92 |
raise EventCallError("event_base_new") |
51 | 93 |
|
94 |
# internal signal wakeup handler |
|
95 |
# XXX: only the main event_base really needs one... |
|
96 |
self.ev_pysignal = build_pysignal_ev(self.ev_base, on_pysignal_wakeup, <void *> self) |
|
97 |
||
98 |
# XXX: this will keep the loop active? |
|
99 |
if lib.event_add(self.ev_pysignal, NULL) < 0 : |
|
100 |
raise EventCallError("event_add(signals)") |
|
101 |
||
39 | 102 |
|
103 |
def reinit (self) : |
|
104 |
""" |
|
105 |
Re-initialize the event_base following a fork(). This is required for some event mechanisms. |
|
106 |
||
107 |
Raises an error if some events could not be re-added. |
|
108 |
""" |
|
109 |
||
110 |
if lib.event_reinit(self.ev_base) < 0 : |
|
41 | 111 |
raise EventCallError("event_reinit", "could not re-add all events") |
39 | 112 |
|
51 | 113 |
|
39 | 114 |
property method : |
115 |
def __get__ (self) : |
|
116 |
""" |
|
117 |
Query the underlying method used by this event_base as a string ("kqueue", "epoll", etc.) |
|
118 |
""" |
|
119 |
||
120 |
return lib.event_base_get_method(self.ev_base) |
|
121 |
||
51 | 122 |
|
39 | 123 |
def loop (self, once = False, nonblock = False) : |
124 |
""" |
|
125 |
Run the event loop. |
|
126 |
||
127 |
once - only run the event loop once, at most |
|
128 |
nonblock - do not block waiting for events |
|
41 | 129 |
|
56
07ed878c847b
doctweaks and some skeleton tests for event-aliveness
Tero Marttila <terom@fixme.fi>
parents:
51
diff
changeset
|
130 |
Returns True if the event loop exited, False if there were no more events to process |
39 | 131 |
""" |
132 |
||
133 |
cdef int flags = 0 |
|
134 |
||
135 |
# build flags |
|
136 |
if once : |
|
137 |
flags |= lib.EVLOOP_ONCE |
|
138 |
||
139 |
if nonblock : |
|
140 |
flags |= lib.EVLOOP_NONBLOCK |
|
51 | 141 |
|
39 | 142 |
|
143 |
# event_base_loop() |
|
41 | 144 |
ret = lib.event_base_loop(self.ev_base, flags) |
145 |
||
51 | 146 |
|
147 |
# check return status |
|
41 | 148 |
if ret < 0 : |
149 |
raise EventCallError("event_base_loop") |
|
150 |
||
151 |
elif ret > 0 : |
|
152 |
return False |
|
153 |
||
154 |
else : |
|
155 |
return True |
|
39 | 156 |
|
51 | 157 |
|
39 | 158 |
def loopexit (self, timeout = None) : |
159 |
""" |
|
160 |
Exit the event loop normally. |
|
161 |
||
162 |
This will optionally wait until the given timeout expires, and then allow the current event loop iteration |
|
163 |
to complete normally, before causing .loop() to return. |
|
164 |
""" |
|
165 |
||
166 |
cdef lib.timeval tv |
|
167 |
||
168 |
if lib.event_base_loopexit(self.ev_base, build_timeout(&tv, timeout)) < 0 : |
|
41 | 169 |
raise EventCallError("event_base_loopexit") |
39 | 170 |
|
51 | 171 |
|
39 | 172 |
def loopbreak (self) : |
173 |
""" |
|
174 |
Abort the event immediately. |
|
175 |
||
176 |
The current event loop will end and .loop() return as soon as the currently running event has been |
|
177 |
processed. |
|
178 |
""" |
|
179 |
||
180 |
if lib.event_base_loopbreak(self.ev_base) < 0 : |
|
41 | 181 |
raise EventCallError("event_base_loopbreak") |
39 | 182 |
|
51 | 183 |
|
39 | 184 |
def __dealloc__ (self) : |
185 |
""" |
|
186 |
Release the event_base's resources. |
|
187 |
||
188 |
XXX: is this entirely safe re our events? |
|
189 |
""" |
|
51 | 190 |
|
191 |
if self.ev_pysignal : |
|
192 |
# remove the internal signal wakeup event |
|
193 |
lib.event_free(self.ev_pysignal) |
|
39 | 194 |
|
195 |
lib.event_base_free(self.ev_base) |
|
196 |