qmsk/net/lib/event2/base.pyx
changeset 51 c6b4abfc21da
parent 41 02f7c0539843
child 56 07ed878c847b
--- a/qmsk/net/lib/event2/base.pyx	Sat Sep 26 16:39:20 2009 +0300
+++ b/qmsk/net/lib/event2/base.pyx	Sat Sep 26 21:46:36 2009 +0300
@@ -1,5 +1,6 @@
-from qmsk.net.lib.event2.base cimport *
-from qmsk.net.lib.event2.event cimport build_timeout
+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) :
     """
@@ -19,6 +20,64 @@
         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) :
@@ -28,6 +87,15 @@
 
         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) :
         """
@@ -39,6 +107,7 @@
         if lib.event_reinit(self.ev_base) < 0 :
             raise EventCallError("event_reinit", "could not re-add all events")
 
+
     property method :
         def __get__ (self) :
             """
@@ -47,6 +116,7 @@
 
             return lib.event_base_get_method(self.ev_base)
 
+
     def loop (self, once = False, nonblock = False) :
         """
             Run the event loop.
@@ -65,10 +135,13 @@
 
         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")
 
@@ -78,6 +151,7 @@
         else :
             return True
 
+
     def loopexit (self, timeout = None) :
         """
             Exit the event loop normally.
@@ -91,6 +165,7 @@
         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.
@@ -102,12 +177,17 @@
         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)