terom@39: from qmsk.net.lib.event2.event cimport * terom@39: from qmsk.net.lib.event2.base cimport event_base terom@50: cimport qmsk.net.py as py terom@39: terom@39: cdef lib.timeval* build_timeout (lib.timeval *tv, object timeout = None) except? -1 : terom@39: """ terom@39: If a timeout is given, it is treated as a float and stored in *tv, which is returned. Otherwise, simply returns terom@39: NULL. terom@39: """ terom@39: terom@39: cdef double t terom@39: terom@41: if timeout is not None : terom@39: t = timeout terom@39: terom@39: tv.tv_sec = (t) terom@39: tv.tv_usec = ((t - t) * 100000) terom@39: terom@39: return tv terom@39: terom@39: else : terom@39: return NULL terom@39: terom@39: terom@39: # our shared callback handler terom@39: # this must aquire the GIL before invoking the python object terom@39: # this means that the libevent loop *must* be run with the GIL released! terom@39: cdef void ev_callback (lib.evutil_socket_t fd, short mask, void *arg) with gil : terom@39: """ terom@39: Proxy event back to event object passed as arg terom@39: """ terom@39: terom@39: # unpack opaque arg terom@39: cdef event ev = arg terom@39: terom@50: # drop self-ref if not pending anymore (keep the local ref) terom@50: # XXX: does the event show up as non-pending here? :/ terom@50: ev._unmark() terom@50: terom@39: # invoke terom@39: # XXX: logging? terom@39: ev(fd, mask) terom@50: terom@39: terom@39: cdef class event : terom@39: terom@42: def __init__ (self, event_base base not None, lib.evutil_socket_t fd, short events) : terom@57: # hold on to our event_base terom@57: self.base = base terom@57: terom@50: # we don't hold a self-reference yet terom@50: self.alive = False terom@42: terom@42: # libevent seems to segfault otherwise... terom@42: if events & (lib.EV_READ | lib.EV_WRITE) and fd < 0 : terom@42: raise ValueError(fd) terom@39: terom@39: # construct event using our internal callback, with self as the arg terom@52: self.ev = lib.event_new(base.ev_base, fd, events, ev_callback, self) terom@39: terom@39: if self.ev == NULL : terom@39: raise Exception("event_new") terom@39: terom@39: terom@42: def assign (self, event_base base not None, lib.evutil_socket_t fd, short events) : terom@39: """ terom@39: Re-assign our event parameters to the given ones. terom@39: terom@39: It is illegal to call this method after calling .add() but before del()/callback! terom@39: """ terom@50: terom@50: if self.alive : terom@50: raise RuntimeError("Cannot change event while active") terom@39: terom@39: # interesting... no error return code terom@52: lib.event_assign(self.ev, base.ev_base, fd, events, ev_callback, self) terom@39: terom@39: terom@39: def add (self, timeout = None) : terom@39: """ terom@39: Add this event to our event_base's set of monitored events. terom@39: terom@39: This event will be executed if any of the events specified in 'events' occurs, or the given timeout terom@39: expires. If no timeout is given, no timeout will occur. terom@39: terom@39: If this event already has a shceduled timeout, the old timeout will be replaced by the new one. terom@39: terom@39: timeout - a float representing the timeout length terom@39: terom@39: XXX: "The event in the ev argument must be already initialized by event_set() and may not be used in calls to event_set() until it has timed out or been removed with event_del()" -- enforce terom@39: """ terom@39: terom@39: cdef lib.timeval tv terom@39: terom@50: # aquire self-ref terom@50: self._mark() terom@50: terom@39: if lib.event_add(self.ev, build_timeout(&tv, timeout)) < 0 : terom@39: raise Exception("event_new") terom@39: terom@39: terom@39: def delete (self) : terom@39: """ terom@39: Remove this event from our event_base's set of monitored events. terom@39: terom@39: If this event has already executed or has never been added this call with have no effect. terom@39: """ terom@39: terom@50: terom@39: if lib.event_del(self.ev) < 0 : terom@39: raise Exception("event_del") terom@39: terom@50: # release self-ref terom@50: self._unmark() terom@50: terom@39: terom@39: def activate (self, int fd, short mask) : terom@39: """ terom@39: 'Fake' this event as active, triggering the callback. terom@39: """ terom@50: terom@50: # aquire self-ref terom@50: self._mark() terom@39: terom@39: lib.event_active(self.ev, fd, mask) terom@39: terom@39: terom@50: cpdef pending (self, short mask = 0xffff) : terom@39: """ terom@39: Returns a bool indicating if this event is pending (that is, has been .add()'d). terom@39: terom@42: For convenience, this defaults to testing all possible flags, and so will return true if any events are terom@42: pending. terom@42: terom@39: XXX: support returning timeout value? terom@39: """ terom@39: terom@39: return bool(lib.event_pending(self.ev, mask, NULL)) terom@39: terom@39: terom@50: cdef object _mark (self) : terom@50: """ terom@50: Refcount magic. Aquires an internal reference *if we don't have one yet*. terom@50: """ terom@50: terom@50: if not self.alive : terom@50: self.alive = True terom@50: terom@50: py.Py_INCREF(self) terom@50: terom@50: terom@50: cdef object _unmark (self) : terom@50: """ terom@50: Refcout magic. Releses the internal reference *if we have one* and libevent doesn't think this event is terom@50: pending anymore. terom@50: terom@50: XXX: this might cause ourselves to be destroyed... we can't break if that happens. terom@50: """ terom@50: terom@50: if self.alive and not self.pending() : terom@50: py.Py_DECREF(self) terom@50: terom@50: self.alive = False terom@50: terom@50: terom@39: property fd : terom@39: def __get__ (self) : terom@39: """ terom@39: Get the OS file descriptor associated with this event. terom@39: """ terom@39: terom@39: return lib.event_get_fd(self.ev) terom@39: terom@39: terom@39: def __call__ (self, lib.evutil_socket_t fd, short mask) : terom@39: """ terom@41: The method invoked by the internal libevent callback when the event becomes active. terom@41: terom@41: fd - OS file descriptor the event occured on, or -1 terom@41: mask - bitmask of EV_* flags that represents the triggered event terom@41: terom@41: terom@41: The default implementation of __call__ does nothing. The method's return value will be ignored, and should terom@41: be None. Any errors raised by the callback will be printed out as warning messages, and ignored. terom@39: """ terom@39: terom@39: pass terom@39: terom@39: terom@39: def __dealloc__ (self) : terom@39: """ terom@50: Release the event object usign event_free. This should be completely safe as regards our event_base. terom@39: terom@39: XXX: what happens if event_base's __dealloc__ is triggered, but there are still event objects alive? terom@39: """ terom@42: terom@42: if self.ev != NULL : terom@42: lib.event_free(self.ev) terom@39: terom@46: class CallbackEvent (event) : terom@46: """ terom@46: Extends the event type to take a callback and additional arguments to invoke from the __call__ method. terom@52: terom@52: This will pass the event object itself to the callback in place of the fd. terom@46: """ terom@46: terom@46: def __init__ (self, event_base base, lib.evutil_socket_t fd, short events, object callback, *args, **kwargs) : terom@46: # parent terom@46: super(CallbackEvent, self).__init__(base, fd, events) terom@46: terom@46: # store terom@46: self.callback = callback terom@46: self.args = args terom@46: self.kwargs = kwargs terom@46: terom@46: def __call__ (self, lib.evutil_socket_t fd, short mask) : terom@52: self.callback(self, mask, *self.args, **self.kwargs) terom@46: