/* * 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. */ /* * Copyright (c) 2006 Sun Microsystems. All rights reserved. * Use is subject to license terms. */ /* * 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 */ 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(); 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 ((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); 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 (0); } /* * 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(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(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); }