from qmsk.net.lib.event2.event cimport build_timeout, event
from qmsk.net.socket.platform cimport AF_UNIX, SOCK_STREAM
cimport qmsk.net.py as py
class EventError (Exception) :
"""
Base class of errors raised by lib.event2 code
"""
pass
class EventCallError (EventError) :
"""
Some libevent function returned an error code.
"""
def __init__ (self, func, msg=None) :
super(EventCallError, self).__init__(func)
self.func = func
self.msg = msg
cdef lib.event* build_pysignal_ev (lib.event_base *ev_base, lib._ev_callback_t cb_func, void *cb_arg) except NULL :
"""
This constructs an returns an event* which will activate the given callback when a Python signal handler runs.
The event is constructed as a persistent internal event, which means that it *must* be removed before trying to
release the event_base.
"""
cdef lib.evutil_socket_t fds[2]
# construct the pipe/sockpair
if lib.evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0 :
raise EventCallError("evutil_socketpair(signals)")
# prep: nonblocking
# XXX: close-on-exec?
lib.evutil_make_socket_nonblocking(fds[0])
lib.evutil_make_socket_nonblocking(fds[1])
# build event for one end
cdef lib.event *ev = lib.event_new(ev_base, fds[1], lib.EV_READ | lib.EV_PERSIST, cb_func, cb_arg)
# set magic flag to not count as active
ev.ev_flags |= lib.EVLIST_INTERNAL
if ev == NULL :
raise EventCallError("event_new(signals)")
# register wakeup fd
py.PySignal_SetWakeupFd(fds[0])
return ev
cdef void on_pysignal_wakeup (int sig, short what, void *arg) :
"""
Python's signal handler trapped a signal, we need to run the signal handlers.
This is executed by libevent's event_base_loop as a normal event (using the wakeup fd built using
build_pysignal_ev), and hence the signal handlers will be run from "inside" of event_base.loop(). Exceptions
raised by the signal handlers should be propagated "out" of event_base.loop().
"""
cdef event_base ev_base = <event_base> arg
try :
# run the real signal handlers for any pending signals
py.PyErr_CheckSignals()
except :
# TODO: implement propagate-to-event_base.loop()
# drop out of the event loop
ev_base.loopbreak()
# Cython will log it
raise
cdef class event_base :
def __init__ (self) :
"""
Constructs a new event_base with default parameters.
"""
# construct
self.ev_base = lib.event_base_new()
if self.ev_base == NULL :
raise EventCallError("event_base_new")
# internal signal wakeup handler
# XXX: only the main event_base really needs one...
self.ev_pysignal = build_pysignal_ev(self.ev_base, on_pysignal_wakeup, <void *> self)
# XXX: this will keep the loop active?
if lib.event_add(self.ev_pysignal, NULL) < 0 :
raise EventCallError("event_add(signals)")
def reinit (self) :
"""
Re-initialize the event_base following a fork(). This is required for some event mechanisms.
Raises an error if some events could not be re-added.
"""
if lib.event_reinit(self.ev_base) < 0 :
raise EventCallError("event_reinit", "could not re-add all events")
property method :
def __get__ (self) :
"""
Query the underlying method used by this event_base as a string ("kqueue", "epoll", etc.)
"""
return lib.event_base_get_method(self.ev_base)
def loop (self, once = False, nonblock = False) :
"""
Run the event loop.
once - only run the event loop once, at most
nonblock - do not block waiting for events
Returns True if the event loop exited, False if there were no more events to process
"""
cdef int flags = 0
# build flags
if once :
flags |= lib.EVLOOP_ONCE
if nonblock :
flags |= lib.EVLOOP_NONBLOCK
# event_base_loop()
ret = lib.event_base_loop(self.ev_base, flags)
# check return status
if ret < 0 :
raise EventCallError("event_base_loop")
elif ret > 0 :
return False
else :
return True
def loopexit (self, timeout = None) :
"""
Exit the event loop normally.
This will optionally wait until the given timeout expires, and then allow the current event loop iteration
to complete normally, before causing .loop() to return.
"""
cdef lib.timeval tv
if lib.event_base_loopexit(self.ev_base, build_timeout(&tv, timeout)) < 0 :
raise EventCallError("event_base_loopexit")
def loopbreak (self) :
"""
Abort the event immediately.
The current event loop will end and .loop() return as soon as the currently running event has been
processed.
"""
if lib.event_base_loopbreak(self.ev_base) < 0 :
raise EventCallError("event_base_loopbreak")
def __dealloc__ (self) :
"""
Release the event_base's resources.
XXX: is this entirely safe re our events?
"""
if self.ev_pysignal :
# remove the internal signal wakeup event
lib.event_free(self.ev_pysignal)
lib.event_base_free(self.ev_base)