qmsk/net/lib/event2/event.pyx
author Tero Marttila <terom@fixme.fi>
Sat, 26 Sep 2009 23:51:31 +0300
changeset 57 8c4032265c8c
parent 52 722fc70a197a
permissions -rw-r--r--
event keeps a ref to its event_base - needed for later error propagation, and to ensure method calls remain valid
from qmsk.net.lib.event2.event cimport *
from qmsk.net.lib.event2.base cimport event_base
cimport qmsk.net.py as py

cdef lib.timeval* build_timeout (lib.timeval *tv, object timeout = None) except? <lib.timeval *>-1 :
    """
        If a timeout is given, it is treated as a float and stored in *tv, which is returned. Otherwise, simply returns
        NULL.
    """

    cdef double t

    if timeout is not None :
        t = timeout

        tv.tv_sec = <int>(t)
        tv.tv_usec = <int>((t - <int> t) * 100000)
        
        return tv

    else :
        return NULL


# our shared callback handler
# this must aquire the GIL before invoking the python object
# this means that the libevent loop *must* be run with the GIL released!
cdef void ev_callback (lib.evutil_socket_t fd, short mask, void *arg) with gil :
    """
        Proxy event back to event object passed as arg
    """
    
    # unpack opaque arg
    cdef event ev = <event>arg

    # drop self-ref if not pending anymore (keep the local ref)
    # XXX: does the event show up as non-pending here? :/
    ev._unmark()

    # invoke
    # XXX: logging?
    ev(fd, mask)
    

cdef class event :

    def __init__ (self, event_base base not None, lib.evutil_socket_t fd, short events) :
        # hold on to our event_base
        self.base = base

        # we don't hold a self-reference yet
        self.alive = False

        # 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)

        if self.ev == NULL :
            raise Exception("event_new")
    

    def assign (self, event_base base not None, lib.evutil_socket_t fd, short events) :
        """
            Re-assign our event parameters to the given ones.

            It is illegal to call this method after calling .add() but before del()/callback!
        """

        if self.alive :
            raise RuntimeError("Cannot change event while active")
        
        # interesting... no error return code
        lib.event_assign(self.ev, base.ev_base, fd, events, ev_callback, <void *> self)


    def add (self, timeout = None) :
        """
            Add this event to our event_base's set of monitored events.

            This event will be executed if any of the events specified in 'events' occurs, or the given timeout
            expires. If no timeout is given, no timeout will occur.

            If this event already has a shceduled timeout, the old timeout will be replaced by the new one.

                timeout     - a float representing the timeout length

            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
        """

        cdef lib.timeval tv

        # aquire self-ref
        self._mark()

        if lib.event_add(self.ev, build_timeout(&tv, timeout)) < 0 :
            raise Exception("event_new")


    def delete (self) :
        """
            Remove this event from our event_base's set of monitored events.

            If this event has already executed or has never been added this call with have no effect.
        """


        if lib.event_del(self.ev) < 0 :
            raise Exception("event_del")

        # release self-ref
        self._unmark()


    def activate (self, int fd, short mask) :
        """
            'Fake' this event as active, triggering the callback.
        """
        
        # aquire self-ref
        self._mark()

        lib.event_active(self.ev, fd, mask)


    cpdef 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?
        """

        return bool(lib.event_pending(self.ev, mask, NULL))


    cdef object _mark (self) :
        """
            Refcount magic. Aquires an internal reference *if we don't have one yet*.
        """
        
        if not self.alive :
            self.alive = True

            py.Py_INCREF(self)


    cdef object _unmark (self) :
        """
            Refcout magic. Releses the internal reference *if we have one* and libevent doesn't think this event is
            pending anymore.

            XXX: this might cause ourselves to be destroyed... we can't break if that happens.
        """

        if self.alive and not self.pending() :
            py.Py_DECREF(self)

            self.alive = False
            

    property fd :
        def __get__ (self) :
            """
                Get the OS file descriptor associated with this event.
            """

            return lib.event_get_fd(self.ev)


    def __call__ (self, lib.evutil_socket_t fd, short mask) :
        """
            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


    def __dealloc__ (self) :
        """
            Release the event object usign event_free. This should be completely safe as regards our event_base.

            XXX: what happens if event_base's __dealloc__ is triggered, but there are still event objects alive?
        """
        
        if self.ev != NULL :
            lib.event_free(self.ev)

class CallbackEvent (event) :
    """
        Extends the event type to take a callback and additional arguments to invoke from the __call__ method.

        This will pass the event object itself to the callback in place of the fd.
    """

    def __init__ (self, event_base base, lib.evutil_socket_t fd, short events, object callback, *args, **kwargs) :
        # parent
        super(CallbackEvent, self).__init__(base, fd, events)

        # store
        self.callback = callback
        self.args = args
        self.kwargs = kwargs

    def __call__ (self, lib.evutil_socket_t fd, short mask) :
        self.callback(self, mask, *self.args, **self.kwargs)