--- a/qmsk/net/lib/event2/base.pyx Tue Sep 01 00:04:26 2009 +0300
+++ b/qmsk/net/lib/event2/base.pyx Tue Sep 01 00:04:54 2009 +0300
@@ -1,6 +1,24 @@
from qmsk.net.lib.event2.base cimport *
from qmsk.net.lib.event2.event cimport build_timeout
+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 class event_base :
def __init__ (self) :
@@ -9,7 +27,7 @@
self.ev_base = lib.event_base_new()
if self.ev_base == NULL :
- raise Exception("event_base_new")
+ raise EventCallError("event_base_new")
def reinit (self) :
"""
@@ -19,7 +37,7 @@
"""
if lib.event_reinit(self.ev_base) < 0 :
- raise Exception("event_reinit: could not re-add all events")
+ raise EventCallError("event_reinit", "could not re-add all events")
property method :
def __get__ (self) :
@@ -29,13 +47,14 @@
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 succesfull, False if no events were registered
"""
cdef int flags = 0
@@ -48,8 +67,16 @@
flags |= lib.EVLOOP_NONBLOCK
# event_base_loop()
- if lib.event_base_loop(self.ev_base, flags) < 0 :
- raise Exception("event_base_loop")
+ ret = lib.event_base_loop(self.ev_base, flags)
+
+ if ret < 0 :
+ raise EventCallError("event_base_loop")
+
+ elif ret > 0 :
+ return False
+
+ else :
+ return True
def loopexit (self, timeout = None) :
"""
@@ -62,7 +89,7 @@
cdef lib.timeval tv
if lib.event_base_loopexit(self.ev_base, build_timeout(&tv, timeout)) < 0 :
- raise Exception("event_base_loopexit")
+ raise EventCallError("event_base_loopexit")
def loopbreak (self) :
"""
@@ -73,7 +100,7 @@
"""
if lib.event_base_loopbreak(self.ev_base) < 0 :
- raise Exception("event_base_loopbreak")
+ raise EventCallError("event_base_loopbreak")
def __dealloc__ (self) :
"""
--- a/qmsk/net/lib/event2/event.pxd Tue Sep 01 00:04:26 2009 +0300
+++ b/qmsk/net/lib/event2/event.pxd Tue Sep 01 00:04:54 2009 +0300
@@ -20,12 +20,7 @@
fd - OS file descriptor to watch, or -1
events - bitmask of EV_* flags that represents the events to wait for
- When the event fires, it will "call" this event object, so __call__ gets the following parameters:
- fd - OS file descriptor the event occured on, or -1
- mask - bitmask of EV_* flags that represents the triggered event
-
- The default implementation of __call__ does nothing. The method's return value will be ignored, and should be
- None. Any errors raised by the callback will be printed out as warning messages, and ignored.
+ When the event fires, it will "call" this event object. See __call__().
"""
# the underlying event object
--- a/qmsk/net/lib/event2/event.pyx Tue Sep 01 00:04:26 2009 +0300
+++ b/qmsk/net/lib/event2/event.pyx Tue Sep 01 00:04:54 2009 +0300
@@ -9,7 +9,7 @@
cdef double t
- if timeout :
+ if timeout is not None :
t = timeout
tv.tv_sec = <int>(t)
@@ -39,7 +39,11 @@
cdef class event :
- def __init__ (self, event_base base, lib.evutil_socket_t fd, short events) :
+ def __init__ (self, event_base base not None, lib.evutil_socket_t fd, short events) :
+
+ # libevent seems to segfault otherwise...
+ if events & (lib.EV_READ | lib.EV_WRITE) and fd < 0 :
+ raise ValueError(fd)
# construct event using our internal callback, with self as the arg
self.ev = lib.event_new(base.ev_base, fd, events, ev_callback, <void *>self)
@@ -48,7 +52,7 @@
raise Exception("event_new")
- def assign (self, event_base base, lib.evutil_socket_t fd, short events) :
+ def assign (self, event_base base not None, lib.evutil_socket_t fd, short events) :
"""
Re-assign our event parameters to the given ones.
@@ -98,10 +102,13 @@
lib.event_active(self.ev, fd, mask)
- def pending (self, short mask) :
+ def pending (self, short mask = 0xffff) :
"""
Returns a bool indicating if this event is pending (that is, has been .add()'d).
+ For convenience, this defaults to testing all possible flags, and so will return true if any events are
+ pending.
+
XXX: support returning timeout value?
"""
@@ -119,7 +126,14 @@
def __call__ (self, lib.evutil_socket_t fd, short mask) :
"""
- The method invoked by the internal libevent callback.
+ The method invoked by the internal libevent callback when the event becomes active.
+
+ fd - OS file descriptor the event occured on, or -1
+ mask - bitmask of EV_* flags that represents the triggered event
+
+
+ The default implementation of __call__ does nothing. The method's return value will be ignored, and should
+ be None. Any errors raised by the callback will be printed out as warning messages, and ignored.
"""
pass
@@ -131,8 +145,9 @@
XXX: what happens if event_base's __dealloc__ is triggered, but there are still event objects alive?
"""
-
- lib.event_free(self.ev)
+
+ if self.ev != NULL :
+ lib.event_free(self.ev)
class CallbackEvent (event) :
"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/lib_event.py Tue Sep 01 00:04:54 2009 +0300
@@ -0,0 +1,192 @@
+import unittest
+
+from qmsk.net.lib.event2.base import event_base
+from qmsk.net.lib.event2.event import event
+from qmsk.net.lib.event2.constants import *
+
+import os
+
+class MyEvent (event) :
+ def __init__ (self, base, fd, ev) :
+ event.__init__(self, base, fd, ev)
+
+ self.ev_in = ev
+ self.ev_out = None
+
+ self.res = None
+
+ def __call__ (self, fd, mask) :
+ self.ev_out = mask
+
+ self.res = (mask == self.ev_in)
+
+class TestEventBase (unittest.TestCase) :
+ """
+ Simple event_base bits
+ """
+
+ def setUp (self) :
+ self.ev_base = event_base()
+
+ def test_loop_empty (self) :
+ """
+ loop() on an empty event_base returns False and doesn't block
+ """
+
+ self.assertFalse(self.ev_base.loop(nonblock=False, once=False))
+
+ def test_loop_empty_nb (self) :
+ """
+ loop(nonblock=False) on an empty event_base returns False
+ """
+
+ self.assertFalse(self.ev_base.loop(nonblock=True))
+
+class TestEvent (unittest.TestCase) :
+ """
+ Simple event bits.
+ """
+
+ def setUp (self) :
+ self.ev_base = event_base()
+ self.ev = event(self.ev_base, 0, EV_READ)
+
+ def test_add (self) :
+ """
+ .add() works.
+ """
+
+ self.assertFalse(self.ev.pending(EV_READ))
+
+ self.ev.add()
+
+ self.assertTrue(self.ev.pending(EV_READ))
+
+ def test_add_timeout (self) :
+ """
+ .add() with timeout works
+ """
+
+ self.ev.add(1.1)
+ self.assertTrue(self.ev.pending())
+
+ def test_add_del (self) :
+ """
+ .add() and then .delete() works
+ """
+
+ self.assertFalse(self.ev.pending())
+
+ # add it in
+ self.ev.add()
+
+ self.assertTrue(self.ev.pending())
+
+ # ensure that the ev_base is indeed not empty
+ self.assertTrue(self.ev_base.loop(nonblock=True))
+
+
+ # remove it again
+ self.ev.delete()
+
+ self.assertFalse(self.ev.pending())
+
+ # ensure that the ev_base is indeed empty
+ self.assertFalse(self.ev_base.loop(nonblock=True))
+
+ def test_invalid_base (self) :
+ """
+ Building an event with invalid base (None) fails
+ """
+
+ # XXX: this doesn't actually seem to check against None, just the type..?
+ self.assertRaises(TypeError, event, None, 0, EV_READ)
+
+ def test_illegal_fd (self) :
+ """
+ Building an event with an illegal fd (-1) fails
+ """
+
+ self.assertRaises(ValueError, event, self.ev_base, -1, EV_READ)
+
+ def test_invalid_fd (self) :
+ """
+ Building an event with an invalid fd (>= 0) fails when calling .add()
+ """
+
+ ev = event(self.ev_base, 666, EV_READ)
+
+ # XXX: be more specific about Exception
+ self.assertRaises(Exception, ev.add)
+
+ def test_invalid_mask_zero (self) :
+ """
+ Building an event with an invalid mask (0)... XXX: works?
+ """
+
+ ev = event(self.ev_base, 0, 0)
+ ev.add()
+
+class TestEventPipe (unittest.TestCase) :
+ """
+ Test basic event stuff using a pipe.
+ """
+
+ def setUp (self) :
+ self.ev_base = event_base()
+ self.fd_read, self.fd_write = os.pipe()
+
+ def test_event (self) :
+ """
+ Constructing an event works
+ """
+
+ self.assertTrue(event(self.ev_base, self.fd_read, EV_READ))
+
+ def test_read (self) :
+ """
+ Read event on pipe with data fires
+ """
+
+ ev = MyEvent(self.ev_base, self.fd_read, EV_READ)
+ ev.add(0.1) # 100ms
+
+ # trigger
+ os.write(self.fd_write, "foo")
+
+ # loop
+ self.ev_base.loop(nonblock=True)
+
+ # test
+ self.assertEquals(ev.ev_out, EV_READ)
+
+ def test_read_timeout (self) :
+ """
+ Read event on empty pipe timeouts
+ """
+
+ ev = MyEvent(self.ev_base, self.fd_read, EV_READ)
+ ev.add(0.0)
+
+ # loop
+ self.ev_base.loop(once=True)
+
+ # test
+ self.assertEquals(ev.ev_out, EV_TIMEOUT)
+
+ def test_read_nonblock (self) :
+ """
+ Read event on empty pipe doesn't block loop with nonblock=True
+ """
+
+ ev = MyEvent(self.ev_base, self.fd_read, EV_READ)
+ ev.add()
+
+ # loop once
+ self.ev_base.loop(nonblock=True)
+
+ # test
+ self.assertEquals(ev.ev_out, None)
+
+if __name__ == '__main__' :
+ unittest.main()