From: Niels Provos Date: Sat, 15 Jul 2006 02:55:57 +0000 (+0000) Subject: 1.2-rc1; Solaris' event port support from Dave Pacheco X-Git-Tag: release-2.0.1-alpha~731 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=00bc7e37fd8f9cc5c749fa83d4152c3395e14975;p=libevent 1.2-rc1; Solaris' event port support from Dave Pacheco svn:r216 --- diff --git a/Makefile.am b/Makefile.am index 625fb5d7..1951e810 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,7 +6,7 @@ bin_SCRIPTS = event_rpcgen.py EXTRA_DIST = acconfig.h event.h event-internal.h log.h evsignal.h event.3 \ kqueue.c epoll_sub.c epoll.c select.c rtsig.c poll.c signal.c \ - devpoll.c event_rpcgen.py \ + evport.c devpoll.c event_rpcgen.py \ sample/Makefile.am sample/Makefile.in sample/event-test.c \ sample/signal-test.c sample/time-test.c \ test/Makefile.am test/Makefile.in test/bench.c test/regress.c \ diff --git a/configure.in b/configure.in index 42e17a73..45ec39e6 100644 --- a/configure.in +++ b/configure.in @@ -2,7 +2,7 @@ dnl configure.in for libevent dnl Dug Song AC_INIT(event.c) -AM_INIT_AUTOMAKE(libevent,1.2) +AM_INIT_AUTOMAKE(libevent,1.2-rc1) AM_CONFIG_HEADER(config.h) AM_MAINTAINER_MODE @@ -39,7 +39,7 @@ AC_CHECK_LIB(socket, socket) dnl Checks for header files. AC_HEADER_STDC -AC_CHECK_HEADERS(fcntl.h stdarg.h inttypes.h stdint.h poll.h signal.h unistd.h sys/epoll.h sys/time.h sys/queue.h sys/event.h sys/ioctl.h sys/devpoll.h) +AC_CHECK_HEADERS(fcntl.h stdarg.h inttypes.h stdint.h poll.h signal.h unistd.h sys/epoll.h sys/time.h sys/queue.h sys/event.h sys/ioctl.h sys/devpoll.h port.h) if test "x$ac_cv_header_sys_queue_h" = "xyes"; then AC_MSG_CHECKING(for TAILQ_FOREACH in sys/queue.h) AC_EGREP_CPP(yes, @@ -302,6 +302,14 @@ main(int argc, char **argv) fi fi +haveeventports=no +AC_CHECK_FUNCS(port_create, [haveeventports=yes], ) +if test "x$haveeventports" = "xyes" ; then + AC_DEFINE(HAVE_EVENT_PORTS, 1, + [Define if your system supports event ports]) + AC_LIBOBJ(evport) + needsignal=yes +fi if test "x$needsignal" = "xyes" ; then AC_LIBOBJ(signal) fi @@ -339,4 +347,5 @@ AC_TRY_COMPILE([], AC_DEFINE(__func__, __FILE__, [Define to appropriate substitue if compiler doesnt have __func__]))) + AC_OUTPUT(Makefile test/Makefile sample/Makefile) diff --git a/event.c b/event.c index 8621f6c2..c7ece90f 100644 --- a/event.c +++ b/event.c @@ -56,6 +56,9 @@ #include "event-internal.h" #include "log.h" +#ifdef HAVE_EVENT_PORTS +extern const struct eventop evportops; +#endif #ifdef HAVE_SELECT extern const struct eventop selectops; #endif @@ -80,6 +83,9 @@ extern const struct eventop win32ops; /* In order of preference */ const struct eventop *eventops[] = { +#ifdef HAVE_EVENT_PORTS + &evportops, +#endif #ifdef HAVE_WORKING_KQUEUE &kqops, #endif diff --git a/evport.c b/evport.c new file mode 100644 index 00000000..b35229f9 --- /dev/null +++ b/evport.c @@ -0,0 +1,506 @@ +/* + * Copyright (c) 2006 Sun Microsystems. + * All rights reserved. + * Submitted by David Pacheco (dp.spambait@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * evport.c: event backend using Solaris 10 event ports. See port_create(3C). + * This implementation is loosely modeled after the one used for select(2) (in + * select.c). + * + * The outstanding events are tracked in a data structure called evport_data. + * Each entry in the ed_fds array corresponds to a file descriptor, and contains + * pointers to the read and write events that correspond to that fd. (That is, + * when the file is readable, the "read" event should handle it, etc.) + * + * evport_add and evport_del update this data structure. evport_dispatch uses it + * to determine where to callback when an event occurs (which it gets from + * port_getn). + * + * Helper functions are used: grow() grows the file descriptor array as + * necessary when large fd's come in. reassociate() takes care of maintaining + * the proper file-descriptor/event-port associations. + * + * As in the select(2) implementation, signals are handled by evsignal, and + * evport_recalc does almost nothing. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CHECK_INVARIANTS +#include +#endif + +#include "event.h" +#include "event-internal.h" +#include "log.h" +#include "evsignal.h" + +extern volatile sig_atomic_t evsignal_caught; + + +/* + * Default value for ed_nevents, which is the maximum file descriptor number we + * can handle. If an event comes in for a file descriptor F > nevents, we will + * grow the array of file descriptors, doubling its size. + */ +#define DEFAULT_NFDS 16 + + +/* + * EVENTS_PER_GETN is the maximum number of events to retrieve from port_getn on + * any particular call. You can speed things up by increasing this, but it will + * (obviously) require more memory. + */ +#define EVENTS_PER_GETN 8 + +/* + * Per-file-descriptor information about what events we're subscribed to. These + * fields are NULL if no event is subscribed to either of them. + */ + +struct fd_info { + struct event* fdi_revt; /* the event responsible for the "read" */ + struct event* fdi_wevt; /* the event responsible for the "write" */ +}; + +#define FDI_HAS_READ(fdi) ((fdi)->fdi_revt != NULL) +#define FDI_HAS_WRITE(fdi) ((fdi)->fdi_wevt != NULL) +#define FDI_HAS_EVENTS(fdi) (FDI_HAS_READ(fdi) || FDI_HAS_WRITE(fdi)) +#define FDI_TO_SYSEVENTS(fdi) (FDI_HAS_READ(fdi) ? POLLIN : 0) | \ + (FDI_HAS_WRITE(fdi) ? POLLOUT : 0) + +struct evport_data { + int ed_port; /* event port for system events */ + sigset_t ed_sigmask; /* for evsignal */ + int ed_nevents; /* number of allocated fdi's */ + struct fd_info *ed_fds; /* allocated fdi table */ + /* fdi's that we need to reassoc */ + struct fd_info *ed_pending[EVENTS_PER_GETN]; +}; + +static void* evport_init (void); +static int evport_add (void *, struct event *); +static int evport_del (void *, struct event *); +static int evport_recalc (struct event_base *, void *, int); +static int evport_dispatch (struct event_base *, void *, struct timeval *); + +const struct eventop evportops = { + "event ports", + evport_init, + evport_add, + evport_del, + evport_recalc, + evport_dispatch +}; + +/* + * Initialize the event port implementation. + */ + +static void* +evport_init(void) +{ + struct evport_data *evpd; + /* + * Disable event ports when this environment variable is set + */ + if (getenv("EVENT_NOEVPORT")) + return (NULL); + + if (!(evpd = calloc(1, sizeof(struct evport_data)))) + return (NULL); + + if ((evpd->ed_port = port_create()) == -1) { + free(evpd); + return (NULL); + } + + /* + * Initialize file descriptor structure + */ + evpd->ed_fds = calloc(DEFAULT_NFDS, sizeof(struct fd_info)); + if (evpd->ed_fds == NULL) { + close(evpd->ed_port); + free(evpd); + return (NULL); + } + evpd->ed_nevents = DEFAULT_NFDS; + memset(&evpd->ed_pending, 0, EVENTS_PER_GETN * sizeof(struct fd_info*)); + + evsignal_init(&evpd->ed_sigmask); + + return (evpd); +} + +#ifdef CHECK_INVARIANTS +/* + * Checks some basic properties about the evport_data structure. Because it + * checks all file descriptors, this function can be expensive when the maximum + * file descriptor ever used is rather large. + */ + +static void +check_evportop(struct evport_data *evpd) +{ + assert(evpd); + assert(evpd->ed_nevents > 0); + assert(evpd->ed_port > 0); + assert(evpd->ed_fds > 0); + + /* + * Verify the integrity of the fd_info struct as well as the events to + * which it points (at least, that they're valid references and correct + * for their position in the structure). + */ + int i; + for (i = 0; i < evpd->ed_nevents; ++i) { + struct event *ev; + struct fd_info *fdi; + + fdi = &evpd->ed_fds[i]; + if ((ev = fdi->fdi_revt) != NULL) { + assert(ev->ev_fd == i); + } + if ((ev = fdi->fdi_wevt) != NULL) { + assert(ev->ev_fd == i); + } + } +} + +/* + * Verifies very basic integrity of a given port_event. + */ +static void +check_event(port_event_t* pevt) +{ + /* + * We've only registered for PORT_SOURCE_FD events. The only + * other thing we can legitimately receive is PORT_SOURCE_ALERT, + * but since we're not using port_alert either, we can assume + * PORT_SOURCE_FD. + */ + assert(pevt->portev_source == PORT_SOURCE_FD); + assert(pevt->portev_user == NULL); +} + +#else +#define check_evportop(epop) +#define check_event(pevt) +#endif /* CHECK_INVARIANTS */ + +/* + * Doubles the size of the allocated file descriptor array. + */ +static int +grow(struct evport_data *epdp, int factor) +{ + struct fd_info *tmp; + int oldsize = epdp->ed_nevents; + int newsize = factor * oldsize; + assert(factor > 1); + + check_evportop(epdp); + + tmp = realloc(epdp->ed_fds, sizeof(struct fd_info) * newsize); + if (NULL == tmp) + return -1; + epdp->ed_fds = tmp; + memset((char*) (epdp->ed_fds + oldsize), 0, + (newsize - oldsize)*sizeof(struct fd_info)); + epdp->ed_nevents = newsize; + + check_evportop(epdp); + + return 0; +} + + +/* + * (Re)associates the given file descriptor with the event port. The OS events + * are specified (implicitly) from the fd_info struct. + */ +static int +reassociate(struct evport_data *epdp, struct fd_info *fdip, int fd) +{ + int sysevents = FDI_TO_SYSEVENTS(fdip); + + if (sysevents != 0) { + if ((-1 == port_associate(epdp->ed_port, PORT_SOURCE_FD, + fd, sysevents, NULL))) { + perror("port_associate"); + return (-1); + } + } else { + if (-1 == port_dissociate(epdp->ed_port, PORT_SOURCE_FD, fd)) { + perror("port_dissociate"); + return (-1); + } + } + + check_evportop(epdp); + + return (0); +} + +/* + * Main event loop - polls port_getn for some number of events, and processes + * them. + */ + +static int +evport_dispatch(struct event_base *base, void *arg, struct timeval *tv) +{ + int i, res; + struct evport_data *epdp = arg; + port_event_t pevtlist[EVENTS_PER_GETN]; + + /* + * port_getn will block until it has at least nevents events. It will + * also return how many it's given us (which may be more than we asked + * for, as long as it's less than our maximum (EVENTS_PER_GETN)) in + * nevents. + */ + int nevents = 1; + + /* + * We have to convert a struct timeval to a struct timespec + * (only difference is nanoseconds vs. microseconds) + */ + struct timespec ts = {tv->tv_sec, tv->tv_usec * 1000}; + + /* + * Before doing anything else, we need to reassociate the events we hit + * last time which need reassociation. See comment at the end of the + * loop below. + */ + for (i = 0; i < EVENTS_PER_GETN; ++i) { + struct fd_info *fdi = epdp->ed_pending[i]; + + if (fdi != NULL && FDI_HAS_EVENTS(fdi)) { + int fd = FDI_HAS_READ(fdi) ? fdi->fdi_revt->ev_fd : + fdi->fdi_wevt->ev_fd; + reassociate(epdp, fdi, fd); + epdp->ed_pending[i] = NULL; + } + } + + + + if (evsignal_deliver(&epdp->ed_sigmask) == -1) + return (-1); + + if ((res = port_getn(epdp->ed_port, pevtlist, EVENTS_PER_GETN, + &nevents, &ts)) == -1) { + if (errno == EINTR) { + evsignal_process(); + return (0); + } else if (errno == ETIME) { + if (nevents == 0) + return (0); + } else { + perror("port_getn"); + return (-1); + } + } else if (evsignal_caught) { + evsignal_process(); + } + + event_debug(("%s: port_getn reports %d events", __func__, nevents)); + + for (i = 0; i < nevents; ++i) { + struct event *ev; + struct fd_info *fdi; + port_event_t *pevt = &pevtlist[i]; + int fd = (int) pevt->portev_object; + + check_evportop(epdp); + check_event(pevt); + + /* + * Figure out what kind of event it was + * (because we have to pass this to the callback) + */ + res = 0; + if (pevt->portev_events & POLLIN) + res |= EV_READ; + if (pevt->portev_events & POLLOUT) + res |= EV_WRITE; + + assert(epdp->ed_nevents > fd); + fdi = &(epdp->ed_fds[fd]); + + /* + * We now check for each of the possible events (READ or WRITE). + * If the event is not persistent, then we delete it. Then, we + * activate the event (which will cause its callback to be + * executed). + */ + + if ((res & EV_READ) && ((ev = fdi->fdi_revt) != NULL)) { + if (!(ev->ev_events & EV_PERSIST)) + event_del(ev); + event_active(ev, res, 1); + } + + if ((res & EV_WRITE) && ((ev = fdi->fdi_wevt) != NULL)) { + if (!(ev->ev_events & EV_PERSIST)) + event_del(ev); + event_active(ev, res, 1); + } + + /* + * If there are still events (they haven't been deleted), then + * we must reassociate the port, since the event port interface + * dissociates them automatically. + * + * But we can't do it right away, because the event hasn't + * handled this event yet, so of course there's still data + * waiting! + */ + if(FDI_HAS_EVENTS(fdi)) { + epdp->ed_pending[i] = fdi; + } + } /* end of all events gotten */ + + check_evportop(epdp); + + if (evsignal_recalc(&epdp->ed_sigmask) == -1) + return (-1); + + return (0); +} + + +/* + * Copied from the version in select.c + */ + +static int +evport_recalc(struct event_base *base, void *arg, int max) +{ + struct evport_data *evpd = arg; + check_evportop(evpd); + return (evsignal_recalc(&evpd->ed_sigmask)); +} + + +/* + * Adds the given event (so that you will be notified when it happens via + * the callback function). + */ + +static int +evport_add(void *arg, struct event *ev) +{ + struct evport_data *evpd = arg; + struct fd_info *fdi; + int factor; + + check_evportop(evpd); + + /* + * Delegate, if it's not ours to handle. + */ + if (ev->ev_events & EV_SIGNAL) + return (evsignal_add(&evpd->ed_sigmask, ev)); + + /* + * If necessary, grow the file descriptor info table + */ + + factor = 1; + while (ev->ev_fd >= factor * evpd->ed_nevents) + factor *= 2; + + if (factor > 1) { + if (-1 == grow(evpd, factor)) { + return (-1); + } + } + + fdi = &evpd->ed_fds[ev->ev_fd]; + if (ev->ev_events & EV_READ) + fdi->fdi_revt = ev; + if (ev->ev_events & EV_WRITE) + fdi->fdi_wevt = ev; + + return reassociate(evpd, fdi, ev->ev_fd); +} + +/* + * Removes the given event from the list of events to wait for. + */ + +static int +evport_del(void *arg, struct event *ev) +{ + struct evport_data *evpd = arg; + struct fd_info *fdi; + + check_evportop(evpd); + + /* + * Delegate, if it's not ours to handle + */ + if (ev->ev_events & EV_SIGNAL) { + return (evsignal_del(&evpd->ed_sigmask, ev)); + } + + if (evpd->ed_nevents < ev->ev_fd) { + return (-1); + } + + + fdi = &evpd->ed_fds[ev->ev_fd]; + if (ev->ev_events & EV_READ) + fdi->fdi_revt = NULL; + if (ev->ev_events & EV_WRITE) + fdi->fdi_wevt = NULL; + + return reassociate(evpd, fdi, ev->ev_fd); +} + +