qmsk/net/lib/event2/event.pyx
author Tero Marttila <terom@fixme.fi>
Mon, 31 Aug 2009 22:10:46 +0300
changeset 42 0ff56f7216ee
parent 41 02f7c0539843
child 47 b45a6648931c
permissions -rw-r--r--
fix some segfaults with event
from qmsk.net.lib.event2.event cimport *
from qmsk.net.lib.event2.base cimport event_base

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

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


cdef class event :

    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)

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

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


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

        lib.event_active(self.ev, fd, 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?
        """

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


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