]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-2.4-20070212
authorWietse Venema <wietse@porcupine.org>
Mon, 12 Feb 2007 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <viktor@dukhovni.org>
Tue, 5 Feb 2013 06:32:55 +0000 (06:32 +0000)
17 files changed:
postfix/.indent.pro
postfix/HISTORY
postfix/RELEASE_NOTES
postfix/makedefs
postfix/src/global/mail_version.h
postfix/src/global/post_mail.c
postfix/src/master/multi_server.c
postfix/src/oqmgr/qmgr_transport.c
postfix/src/qmgr/qmgr_transport.c
postfix/src/smtpstone/smtp-sink.c
postfix/src/smtpstone/smtp-source.c
postfix/src/util/events.c
postfix/src/util/read_wait.c
postfix/src/util/readable.c
postfix/src/util/sys_defs.h
postfix/src/util/writable.c
postfix/src/util/write_wait.c

index bc5fa9c6363839182188d8b0804a261daec7515c..ea48c48d4296ac71ea39c815aca933f22f282bda 100644 (file)
@@ -94,6 +94,7 @@
 -TDSN_BUF
 -TDSN_SPLIT
 -TDSN_STAT
+-TEVENT_MASK
 -TEXPAND_ATTR
 -TFILE
 -TFORWARD_INFO
index 9557aef4a4f5628d634171256a9259ee38326ee7..613550be2e4fe78a2ee0f3adf464ba86e3487945 100644 (file)
@@ -13179,6 +13179,55 @@ Apologies for any names omitted.
        more quickly release unused file handles. Files:
        global/mail_params.h, proto/postconf.5.html
 
+20070202
+
+       Catch-up: FreeBSD kqueue support. File: util/events.c.
+
+20070205
+
+       System-V poll(2) support. This is now the preferred method
+       to test a single file descriptor on sufficiently recent
+       versions of FreeBSD, NetBSD, OpenBSD, Solaris and Linux;
+       other systems will be added as evidence becomes available
+       of usable poll(2) implementations. Files: util/read_wait.c,
+       util/write_wait.c, util/readble.c, util/writable.c.
+
+       Streamlined the event_enable_read/write implementation to
+       speed up smtp-source performance, by eliminating expensive
+       kqueue/devpoll/epoll system calls when only the application
+       call-back information changes. On FreeBSD, smtp-sink/source
+       tests now run 5% faster than with the old select(2) based
+       implementation.  File util/events.c.
+
+20070206
+
+       Catch-up: Solaris /dev/poll support. File: util/events.c.
+
+       Bugfix (introduced 20060823): initial state was not in state
+       machine, causing memory access outside the lookup table.
+       File: smtpstone/smtp-sink.c.
+
+20070210
+
+       Catch-up: Linux epoll support. File: util/events.c.
+
+20070211
+
+       Polished the kqueue/devpoll/epoll support; this is now
+       enabled by default on sufficiently recent versions of
+       FreeBSD, NetBSD, OpenBSD, Solaris and Linux; other systems
+       will be added as evidence becomes available of usable
+       implementations. File: util/events.c.
+
+20070212
+
+       Further polish: removed some typos from new code in the
+       events.c handler, undid some unnecessary changes to the
+       {read,write}{_wait,able}.c modules, and addressed Victor's
+       paranoia for multi-client servers with a thousand clients
+       while linked with third-party libraries that can't handle
+       file descriptors >= FD_SETSIZE.
+
 Wish list:
 
        Update message content length when adding/removing headers.
@@ -13199,10 +13248,6 @@ Wish list:
        Make postmap header/body aware so people can test multi-line
        header checks.
 
-       Eliminate Linux 1024 select() file handle bottleneck and
-       eliminate select()/poll() scaling problems by implementing
-       kqueue(2) and epoll(2) support.
-
        REDIRECT should override original recipient info, and
        probably override DSN as well.
 
index 31d0925a843aa9f3e58d70a9bd90599be99f25be..8eecb2154ba931680936f823779957361e514f17 100644 (file)
@@ -17,6 +17,18 @@ Incompatibility with Postfix 2.2 and earlier
 If you upgrade from Postfix 2.2 or earlier, read RELEASE_NOTES-2.3
 before proceeding.
 
+Major changes with Postfix snapshot 20070212-event
+==================================================
+
+Better support for systems that run thousands of Postfix processes.
+Postfix now supports FreeBSD kqueue(2), Solaris poll(7d) and Linux
+epoll(4) as more scalable alternatives to the traditional select(2)
+system call, and uses poll(2) when examining a single file descriptor
+for readability or writability. These features are supported on
+sufficiently recent versions of FreeBSD, NetBSD, OpenBSD, Solaris
+and Linux; support for other systems will be added as evidence
+becomes available that usable implementations exist.
+
 Incompatibility with Postfix snapshot 20070201
 ==============================================
 
index 724bea4532fb5e3b2fa6ba209d28f919959e765f..4e80525dc61592f0751410dda0d2481052d7648a 100644 (file)
 #      \fIinclude\fR directory.
 #      The following directives are special:
 # .RS
+# .IP \fB-DNO_DEVPOLL\fR
+#      Do not build with Solaris /dev/poll support.
+#      By default, /dev/poll support is compiled in on platforms that
+#      are known to support it.
+# .IP \fB-DNO_EPOLL\fR
+#      Do not build with Linux EPOLL support.
+#      By default, EPOLL support is compiled in on platforms that
+#      are known to support it.
 # .IP \fB-DNO_IPV6\fR
 #      Do not build with IPv6 support.
 #      By default, IPv6 support is compiled in on platforms that
@@ -158,9 +166,9 @@ case "$SYSTEM.$RELEASE" in
                case $RELEASE in
                    5.[0-4]) CCARGS="$CCARGS -DMISSING_USLEEP -DNO_POSIX_REGEXP";;
                esac
-               # Solaris 8 added IPv6
+               # Solaris 8 added IPv6 and /dev/poll
                case $RELEASE in
-                   5.[0-7]|5.[0-7].*) CCARGS="$CCARGS -DNO_IPV6";;
+                   5.[0-7]|5.[0-7].*) CCARGS="$CCARGS -DNO_IPV6 -DNO_DEVPOLL";;
                esac
                # Solaris 9 added closefrom(), futimesat() and /dev/*random
                case $RELEASE in
@@ -262,9 +270,14 @@ case "$SYSTEM.$RELEASE" in
                        }
                    done
                done
+               # Kernel 2.4 added IPv6
                case "$RELEASE" in
                2.[0-3].*) CCARGS="$CCARGS -DNO_IPV6";;
                esac
+               # Kernel 2.6 added EPOLL
+               case "$RELEASE" in
+               2.[0-5].*) CCARGS="$CCARGS -DNO_EPOLL";;
+               esac
                ;;
      GNU.0*|GNU/kFreeBSD.[56]*)
                SYSTYPE=GNU0
index 20e12eb067592d91d86f4d9e052659f08ffbb56d..8742a07dda553ef16ad1a3d954d0febcf2a5dcdb 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE      "20070202"
+#define MAIL_RELEASE_DATE      "20070212"
 #define MAIL_VERSION_NUMBER    "2.4"
 
 #ifdef SNAPSHOT
index 56a94654c6028331fa94ff994e3ee75ecb5eed3a..22deb4fdbe0e08eb16e10ba46077081aa3e74082 100644 (file)
@@ -270,21 +270,6 @@ static void post_mail_open_event(int event, char *context)
 
     switch (event) {
 
-       /*
-        * Connection established. Request notification when the server sends
-        * the initial response. This intermediate case is necessary for some
-        * versions of LINUX and perhaps Solaris, where UNIX-domain
-        * connect(2) blocks until the server performs an accept(2).
-        */
-    case EVENT_WRITE:
-       if (msg_verbose)
-           msg_info("%s: write event", myname);
-       event_disable_readwrite(vstream_fileno(state->stream));
-       non_blocking(vstream_fileno(state->stream), BLOCKING);
-       event_enable_read(vstream_fileno(state->stream),
-                         post_mail_open_event, (char *) state);
-       return;
-
        /*
         * Initial server reply. Stop the watchdog timer, disable further
         * read events that end up calling this function, and notify the
@@ -295,6 +280,7 @@ static void post_mail_open_event(int event, char *context)
            msg_info("%s: read event", myname);
        event_cancel_timer(post_mail_open_event, context);
        event_disable_readwrite(vstream_fileno(state->stream));
+       non_blocking(vstream_fileno(state->stream), BLOCKING);
        post_mail_init(state->stream, state->sender,
                       state->recipient, state->filter_class,
                       state->trace_flags, state->queue_id);
@@ -371,7 +357,7 @@ void    post_mail_fopen_async(const char *sender, const char *recipient,
      * same interface as all successes.
      */
     if (stream != 0) {
-       event_enable_write(vstream_fileno(stream), post_mail_open_event,
+       event_enable_read(vstream_fileno(stream), post_mail_open_event,
                           (void *) state);
        event_request_timer(post_mail_open_event, (void *) state,
                            var_daemon_timeout);
index 1330b23e619717cf9f632b6efe1524fe117ec98f..c54c8a19c46036148d6a05e4dddea5c53d3597aa 100644 (file)
 
 #include <sys_defs.h>
 #include <sys/socket.h>
+#include <sys/time.h>                  /* select() */
 #include <unistd.h>
 #include <signal.h>
 #include <syslog.h>
 #endif
 #include <time.h>
 
+#ifdef USE_SYS_SELECT_H
+#include <sys/select.h>                        /* select() */
+#endif
+
 /* Utility library. */
 
 #include <msg.h>
@@ -328,6 +333,20 @@ static void multi_server_wakeup(int fd)
     VSTREAM *stream;
     char   *tmp;
 
+#ifndef USE_SELECT_EVENTS
+    int     new_fd;
+
+    /*
+     * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely
+     * case of a multi-server with a thousand clients.
+     */
+    if (fd < FD_SETSIZE / 8) {
+       if ((new_fd = fcntl(fd, F_DUPFD, FD_SETSIZE / 8)) < 0)
+           msg_fatal("fcntl F_DUPFD: %m");
+       (void) close(fd);
+       fd = new_fd;
+    }
+#endif
     if (msg_verbose)
        msg_info("connection established fd %d", fd);
     non_blocking(fd, BLOCKING);
index 7fa50208c50aa53868c5bbe6659563f9326481da..7ff31c57fcc33167473c148d61819bc6bfa0a648 100644 (file)
@@ -236,9 +236,8 @@ static void qmgr_transport_event(int unused_event, char *context)
     event_cancel_timer(qmgr_transport_abort, context);
 
     /*
-     * Disable further read events that end up calling this function, turn
-     * off the Linux connect() workaround, and free up this pending
-     * connection pipeline slot.
+     * Disable further read events that end up calling this function, and
+     * free up this pending connection pipeline slot.
      */
     if (alloc->stream) {
        event_disable_readwrite(vstream_fileno(alloc->stream));
index edcd9b3849c4eb8a89d2b4bebf6d8d07ec41f79f..de04bd1925fbb54340516cf9b42e6ed604154338 100644 (file)
@@ -241,9 +241,8 @@ static void qmgr_transport_event(int unused_event, char *context)
     event_cancel_timer(qmgr_transport_abort, context);
 
     /*
-     * Disable further read events that end up calling this function, turn
-     * off the Linux connect() workaround, and free up this pending
-     * connection pipeline slot.
+     * Disable further read events that end up calling this function, and
+     * free up this pending connection pipeline slot.
      */
     if (alloc->stream) {
        event_disable_readwrite(vstream_fileno(alloc->stream));
index feb8e13bbc86236addd23cc5a96f2af17e709035..b18278fa91cfcf3e0ab7f6ded12f497f3cdafca8 100644 (file)
@@ -975,6 +975,7 @@ static int command_read(SINK_STATE *state)
     static struct cmd_trans cmd_trans[] = {
        ST_ANY, '\r', ST_CR,
        ST_CR, '\n', ST_CR_LF,
+       0, 0, 0,
     };
     struct cmd_trans *cp;
     char   *ptr;
@@ -986,6 +987,8 @@ static int command_read(SINK_STATE *state)
 #define NEXT_CHAR(state) \
     (PUSH_BACK_PEEK(state) ? PUSH_BACK_GET(state) : VSTREAM_GETC(state->stream))
 
+    if (state->data_state == ST_CR_LF)
+       state->data_state = ST_ANY;             /* XXX */
     for (;;) {
        if ((ch = NEXT_CHAR(state)) == VSTREAM_EOF)
            return (-1);
@@ -1005,7 +1008,8 @@ static int command_read(SINK_STATE *state)
         * first state.
         */
        for (cp = cmd_trans; cp->state != state->data_state; cp++)
-            /* void */ ;
+           if (cp->want == 0)
+               msg_panic("command_read: unknown state: %d", state->data_state);
        if (ch == cp->want)
            state->data_state = cp->next_state;
        else if (ch == cmd_trans[0].want)
index 23f5e3b7e0677683a3455e36fa04b7d91af4929d..9a736e14a311ed3613f9b6199974f1a8db8c5d45 100644 (file)
@@ -467,6 +467,7 @@ static void connect_done(int unused_event, char *context)
        fail_connect(session);
     } else {
        non_blocking(fd, BLOCKING);
+       /* Disable write events. */
        event_disable_readwrite(fd);
        event_enable_read(fd, read_banner, (char *) session);
        dequeue_connect(session);
@@ -520,7 +521,6 @@ static void send_helo(SESSION *session)
     /*
      * Prepare for the next event.
      */
-    event_disable_readwrite(vstream_fileno(session->stream));
     event_enable_read(vstream_fileno(session->stream), helo_done, (char *) session);
 }
 
@@ -562,7 +562,6 @@ static void send_mail(SESSION *session)
     /*
      * Prepare for the next event.
      */
-    event_disable_readwrite(vstream_fileno(session->stream));
     event_enable_read(vstream_fileno(session->stream), mail_done, (char *) session);
 }
 
@@ -613,7 +612,6 @@ static void send_rcpt(int unused_event, char *context)
     /*
      * Prepare for the next event.
      */
-    event_disable_readwrite(vstream_fileno(session->stream));
     event_enable_read(vstream_fileno(session->stream), rcpt_done, (char *) session);
 }
 
@@ -660,7 +658,6 @@ static void send_data(int unused_event, char *context)
     /*
      * Prepare for the next event.
      */
-    event_disable_readwrite(vstream_fileno(session->stream));
     event_enable_read(vstream_fileno(session->stream), data_done, (char *) session);
 }
 
@@ -736,7 +733,6 @@ static void data_done(int unused_event, char *context)
     /*
      * Prepare for the next event.
      */
-    event_disable_readwrite(vstream_fileno(session->stream));
     event_enable_read(vstream_fileno(session->stream), dot_done, (char *) session);
 }
 
@@ -775,7 +771,6 @@ static void dot_done(int unused_event, char *context)
 static void send_quit(SESSION *session)
 {
     command(session->stream, "QUIT");
-    event_disable_readwrite(vstream_fileno(session->stream));
     event_enable_read(vstream_fileno(session->stream), quit_done, (char *) session);
 }
 
index c7b741dfa2703a4ec3493c633ca95d87980b1a12..e12f4bcdadf99f43483446831ece67d3bb1d783d 100644 (file)
 /*     partial reads or writes.
 /*     An I/O channel cannot handle more than one request at the
 /*     same time. The application is allowed to enable an event that
-/*     is already enabled (same channel, callback and context).
+/*     is already enabled (same channel, same read or write operation,
+/*     but perhaps a different callback or context). On systems with
+/*     kernel-based event filters this is preferred usage, because
+/*     each disable and enable request would cost a system call.
 /*
 /*     The manifest constants EVENT_NULL_CONTEXT and EVENT_NULL_TYPE
 /*     provide convenient null values.
@@ -93,7 +96,7 @@
 /* .IP EVENT_WRITE
 /*     write event,
 /* .IP EVENT_XCPT
-/*     exception.
+/*     exception (actually, any event other than read or write).
 /* .RE
 /* .IP context
 /*     Application context given to event_enable_read() (event_enable_write()).
 #include <unistd.h>
 #include <stddef.h>                    /* offsetof() */
 #include <string.h>                    /* bzero() prototype for 44BSD */
+#include <limits.h>                    /* INT_MAX */
 
 #ifdef USE_SYS_SELECT_H
 #include <sys/select.h>
 #include "ring.h"
 #include "events.h"
 
+#if (defined(USE_KQUEUE_EVENTS) && defined(USE_DEVPOLL_EVENTS)) \
+    || (defined(USE_KQUEUE_EVENTS) && defined(USE_EPOLL_EVENTS)) \
+    || (defined(USE_KQUEUE_EVENTS) && defined(USE_SELECT_EVENTS)) \
+    || (defined(USE_DEVPOLL_EVENTS) && defined(USE_EPOLL_EVENTS)) \
+    || (defined(USE_DEVPOLL_EVENTS) && defined(USE_SELECT_EVENTS)) \
+    || (defined(USE_EPOLL_EVENTS) && defined(USE_SELECT_EVENTS))
+#error "don't define multiple USE_KQUEUE/DEVPOLL/EPOLL/SELECT_EVENTS"
+#endif
+
+#if !defined(USE_KQUEUE_EVENTS) && !defined(USE_DEVPOLL_EVENTS) \
+       && !defined(USE_EPOLL_EVENTS) && !defined(USE_SELECT_EVENTS)
+#error "define one of USE_KQUEUE/DEVPOLL/EPOLL/SELECT_EVENTS"
+#endif
+
+ /*
+  * Traditional BSD-style select(2). Works everywhere, but has a built-in
+  * upper bound on the number of file descriptors, and that limit is hard to
+  * change on Linux. Is sometimes emulated with SYSV-style poll(2) which
+  * doesn't have the file descriptor limit, but unfortunately does not help
+  * to improve the performance of servers with lots of connections.
+  */
+#define EVENT_ALLOC_INCR               10
+
+#ifdef USE_SELECT_EVENTS
+typedef fd_set EVENT_MASK;
+
+#define EVENT_MASK_BYTE_COUNT(mask)    sizeof(*(mask))
+#define EVENT_MASK_ZERO(mask)          FD_ZERO(mask)
+#define EVENT_MASK_SET(fd, mask)       FD_SET((fd), (mask))
+#define EVENT_MASK_ISSET(fd, mask)     FD_ISSET((fd), (mask))
+#define EVENT_MASK_CLR(fd, mask)       FD_CLR((fd), (mask))
+#else
+
  /*
-  * I/O events. We pre-allocate one data structure per file descriptor. XXX
-  * For now use FD_SETSIZE as defined along with the fd-set type.
+  * Kernel-based event filters (kqueue, /dev/poll, epoll). We use the
+  * following file descriptor mask structure which is expanded on the fly.
+  */
+typedef struct {
+    char   *data;                      /* bit mask */
+    size_t  data_len;                  /* data byte count */
+} EVENT_MASK;
+
+ /* Bits per byte, byte in vector, bit offset in byte, bytes per set. */
+#define EVENT_MASK_NBBY                (8)
+#define EVENT_MASK_FD_BYTE(fd, mask) \
+       (((unsigned char *) (mask)->data)[(fd) / EVENT_MASK_NBBY])
+#define EVENT_MASK_FD_BIT(fd)  (1 << ((fd) % EVENT_MASK_NBBY))
+#define EVENT_MASK_BYTES_NEEDED(len) \
+       (((len) + (EVENT_MASK_NBBY -1)) / EVENT_MASK_NBBY)
+#define EVENT_MASK_BYTE_COUNT(mask)    ((mask)->data_len)
+
+ /* Memory management. */
+#define EVENT_MASK_ALLOC(mask, bit_len) do { \
+       size_t _byte_len = EVENT_MASK_BYTES_NEEDED(bit_len); \
+       (mask)->data = mymalloc(_byte_len); \
+       memset((mask)->data, 0, _byte_len); \
+       (mask)->data_len = _byte_len; \
+    } while (0)
+#define EVENT_MASK_REALLOC(mask, bit_len) do { \
+       size_t _byte_len = EVENT_MASK_BYTES_NEEDED(bit_len); \
+       size_t _old_len = (mask)->data_len; \
+       (mask)->data = myrealloc((mask)->data, _byte_len); \
+       memset((mask)->data + _old_len, 0, _byte_len - _old_len); \
+       (mask)->data_len = _byte_len; \
+    } while (0)
+#define EVENT_MASK_FREE(mask)  myfree((mask)->data)
+
+ /* Set operations, modeled after FD_ZERO/SET/ISSET/CLR. */
+#define EVENT_MASK_ZERO(mask) \
+       memset((mask)->data, 0, (mask)->data_len)
+#define EVENT_MASK_SET(fd, mask) \
+       (EVENT_MASK_FD_BYTE((fd), (mask)) |= EVENT_MASK_FD_BIT(fd))
+#define EVENT_MASK_ISSET(fd, mask) \
+       (EVENT_MASK_FD_BYTE((fd), (mask)) & EVENT_MASK_FD_BIT(fd))
+#define EVENT_MASK_CLR(fd, mask) \
+       (EVENT_MASK_FD_BYTE((fd), (mask)) &= ~EVENT_MASK_FD_BIT(fd))
+#endif
+
+ /*
+  * I/O events.
   */
 typedef struct EVENT_FDTABLE EVENT_FDTABLE;
 
@@ -158,12 +239,242 @@ struct EVENT_FDTABLE {
     EVENT_NOTIFY_RDWR callback;
     char   *context;
 };
-static fd_set event_rmask;             /* enabled read events */
-static fd_set event_wmask;             /* enabled write events */
-static fd_set event_xmask;             /* for bad news mostly */
-static int event_fdsize;               /* number of file descriptors */
+static EVENT_MASK event_rmask;         /* enabled read events */
+static EVENT_MASK event_wmask;         /* enabled write events */
+static EVENT_MASK event_xmask;         /* for bad news mostly */
+static int event_fdlimit;              /* per-process open file limit */
 static EVENT_FDTABLE *event_fdtable;   /* one slot per file descriptor */
-static int event_max_fd;               /* highest fd number seen */
+static int event_fdslots;              /* number of file descriptor slots */
+static int event_max_fd = -1;          /* highest fd number seen */
+
+ /*
+  * FreeBSD kqueue supports no system call to find out what descriptors are
+  * registered in the kernel-based filter. To implement our own sanity checks
+  * we maintain our own descriptor bitmask.
+  * 
+  * FreeBSD kqueue does support application context pointers. Unfortunately,
+  * changing that information would cost a system call, and some of the
+  * competitors don't support application context. To keep the implementation
+  * simple we maintain our own table with call-back information.
+  * 
+  * FreeBSD kqueue silently unregisters a descriptor from its filter when the
+  * descriptor is closed, so our information could get out of sync with the
+  * kernel. But that will never happen, because we have to meticulously
+  * unregister a file descriptor before it is closed, to avoid errors on
+  * systems that are built with USE_SELECT_EVENTS.
+  */
+#ifdef USE_KQUEUE_EVENTS
+#include <sys/event.h>
+
+ /*
+  * Some early FreeBSD implementations don't have the EV_SET macro.
+  */
+#ifndef EV_SET
+#define EV_SET(kp, id, fi, fl, ffl, da, ud) do { \
+        (kp)->ident = (id); \
+        (kp)->filter = (fi); \
+        (kp)->flags = (fl); \
+        (kp)->fflags = (ffl); \
+        (kp)->data = (da); \
+        (kp)->udata = (ud); \
+    } while(0)
+#endif
+
+ /*
+  * Macros to initialize the kernel-based filter; see event_init().
+  */
+static int event_kq;                   /* handle to event filter */
+
+#define EVENT_REG_INIT_HANDLE(er, n) do { \
+       er = event_kq = kqueue(); \
+    } while (0)
+#define EVENT_REG_INIT_TEXT    "kqueue"
+
+ /*
+  * Macros to update the kernel-based filter; see event_enable_read(),
+  * event_enable_write() and event_disable_readwrite().
+  */
+#define EVENT_REG_FD_OP(er, fh, ev, op) do { \
+       struct kevent dummy; \
+       EV_SET(&dummy, (fh), (ev), (op), 0, 0, 0); \
+       (er) = kevent(event_kq, &dummy, 1, 0, 0, 0); \
+    } while (0)
+
+#define EVENT_REG_ADD_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EV_ADD)
+#define EVENT_REG_ADD_READ(e, f)   EVENT_REG_ADD_OP((e), (f), EVFILT_READ)
+#define EVENT_REG_ADD_WRITE(e, f)  EVENT_REG_ADD_OP((e), (f), EVFILT_WRITE)
+#define EVENT_REG_ADD_TEXT         "kevent EV_ADD"
+
+#define EVENT_REG_DEL_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EV_DELETE)
+#define EVENT_REG_DEL_READ(e, f)   EVENT_REG_DEL_OP((e), (f), EVFILT_READ)
+#define EVENT_REG_DEL_WRITE(e, f)  EVENT_REG_DEL_OP((e), (f), EVFILT_WRITE)
+#define EVENT_REG_DEL_TEXT         "kevent EV_DELETE"
+
+ /*
+  * Macros to retrieve event buffers from the kernel; see event_loop().
+  */
+typedef struct kevent EVENT_BUFFER;
+
+#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \
+       struct timespec ts; \
+       struct timespec *tsp; \
+       if ((delay) < 0) { \
+           tsp = 0; \
+       } else { \
+           tsp = &ts; \
+           ts.tv_nsec = 0; \
+           ts.tv_sec = (delay); \
+       } \
+       (event_count) = kevent(event_kq, (struct kevent *) 0, 0, (event_buf), \
+                         (buflen), (tsp)); \
+    } while (0)
+
+ /*
+  * Macros to process event buffers from the kernel; see event_loop().
+  */
+#define EVENT_GET_FD(bp)       ((bp)->ident)
+#define EVENT_GET_TYPE(bp)     ((bp)->filter)
+#define EVENT_TEST_READ(bp)    (EVENT_GET_TYPE(bp) == EVFILT_READ)
+#define EVENT_TEST_WRITE(bp)   (EVENT_GET_TYPE(bp) == EVFILT_WRITE)
+
+#endif
+
+ /*
+  * Solaris /dev/poll does not support application context, so we have to
+  * maintain our own. This has the benefit of avoiding an expensive system
+  * call just to change a call-back function or argument.
+  * 
+  * Solaris /dev/poll does have a way to query if a specific descriptor is
+  * registered. However, we maintain a descriptor mask anyway because a) it
+  * avoids having to make an expensive system call to find out if something
+  * is registered, b) some USE_MUMBLE_EVENTS implementations need a
+  * descriptor bitmask anyway and c) we use the bitmask already to implement
+  * sanity checks.
+  */
+#ifdef USE_DEVPOLL_EVENTS
+#include <sys/devpoll.h>
+#include <fcntl.h>
+
+ /*
+  * Macros to initialize the kernel-based filter; see event_init().
+  */
+static int event_pollfd;               /* handle to file descriptor set */
+
+#define EVENT_REG_INIT_HANDLE(er, n) do { \
+       er = event_pollfd = open("/dev/poll", O_RDWR); \
+    } while (0)
+#define EVENT_REG_INIT_TEXT    "open /dev/poll"
+
+ /*
+  * Macros to update the kernel-based filter; see event_enable_read(),
+  * event_enable_write() and event_disable_readwrite().
+  */
+#define EVENT_REG_FD_OP(er, fh, ev) do { \
+       struct pollfd dummy; \
+       dummy.fd = (fh); \
+       dummy.events = (ev); \
+       (er) = write(event_pollfd, (char *) &dummy, \
+           sizeof(dummy)) != sizeof(dummy) ? -1 : 0; \
+    } while (0)
+
+#define EVENT_REG_ADD_READ(e, f)  EVENT_REG_FD_OP((e), (f), POLLIN)
+#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_FD_OP((e), (f), POLLOUT)
+#define EVENT_REG_ADD_TEXT        "write /dev/poll"
+
+#define EVENT_REG_DEL_BOTH(e, f)  EVENT_REG_FD_OP((e), (f), POLLREMOVE)
+#define EVENT_REG_DEL_TEXT        "write /dev/poll"
+
+ /*
+  * Macros to retrieve event buffers from the kernel; see event_loop().
+  */
+typedef struct pollfd EVENT_BUFFER;
+
+#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \
+       struct dvpoll dvpoll; \
+       dvpoll.dp_fds = (event_buf); \
+       dvpoll.dp_nfds = (buflen); \
+       dvpoll.dp_timeout = (delay) < 0 ? -1 : (delay) * 1000; \
+       (event_count) = ioctl(event_pollfd, DP_POLL, &dvpoll); \
+    } while (0)
+
+ /*
+  * Macros to process event buffers from the kernel; see event_loop().
+  */
+#define EVENT_GET_FD(bp)       ((bp)->fd)
+#define EVENT_GET_TYPE(bp)     ((bp)->revents)
+#define EVENT_TEST_READ(bp)    (EVENT_GET_TYPE(bp) & POLLIN)
+#define EVENT_TEST_WRITE(bp)   (EVENT_GET_TYPE(bp) & POLLOUT)
+
+#endif
+
+ /*
+  * Linux epoll supports no system call to find out what descriptors are
+  * registered in the kernel-based filter. To implement our own sanity checks
+  * we maintain our own descriptor bitmask.
+  * 
+  * Linux epoll does support application context pointers. Unfortunately,
+  * changing that information would cost a system call, and some of the
+  * competitors don't support application context. To keep the implementation
+  * simple we maintain our own table with call-back information.
+  * 
+  * Linux epoll silently unregisters a descriptor from its filter when the
+  * descriptor is closed, so our information could get out of sync with the
+  * kernel. But that will never happen, because we have to meticulously
+  * unregister a file descriptor before it is closed, to avoid errors on
+  */
+#ifdef USE_EPOLL_EVENTS
+#include <sys/epoll.h>
+
+ /*
+  * Macros to initialize the kernel-based filter; see event_init().
+  */
+static int event_epollfd;              /* epoll handle */
+
+#define EVENT_REG_INIT_HANDLE(er, n) do { \
+       er = event_epollfd = epoll_create(n); \
+    } while (0)
+#define EVENT_REG_INIT_TEXT    "epoll_create"
+
+ /*
+  * Macros to update the kernel-based filter; see event_enable_read(),
+  * event_enable_write() and event_disable_readwrite().
+  */
+#define EVENT_REG_FD_OP(er, fh, ev, op) do { \
+       struct epoll_event dummy; \
+       dummy.events = (ev); \
+       dummy.data.fd = (fh); \
+       (er) = epoll_ctl(event_epollfd, (op), (fh), &dummy); \
+    } while (0)
+
+#define EVENT_REG_ADD_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EPOLL_CTL_ADD)
+#define EVENT_REG_ADD_READ(e, f)   EVENT_REG_ADD_OP((e), (f), EPOLLIN)
+#define EVENT_REG_ADD_WRITE(e, f)  EVENT_REG_ADD_OP((e), (f), EPOLLOUT)
+#define EVENT_REG_ADD_TEXT         "epoll_ctl EPOLL_CTL_ADD"
+
+#define EVENT_REG_DEL_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EPOLL_CTL_DEL)
+#define EVENT_REG_DEL_READ(e, f)   EVENT_REG_DEL_OP((e), (f), EPOLLIN)
+#define EVENT_REG_DEL_WRITE(e, f)  EVENT_REG_DEL_OP((e), (f), EPOLLOUT)
+#define EVENT_REG_DEL_TEXT         "epoll_ctl(EPOLL_CTL_DEL)"
+
+ /*
+  * Macros to retrieve event buffers from the kernel; see event_loop().
+  */
+typedef struct epoll_event EVENT_BUFFER;
+
+#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \
+       (event_count) = epoll_wait(event_epollfd, (event_buf), (buflen), \
+                                 (delay) < 0 ? -1 : (delay) * 1000); \
+    } while (0)
+
+ /*
+  * Macros to process event buffers from the kernel; see event_loop().
+  */
+#define EVENT_GET_FD(bp)       ((bp)->data.fd)
+#define EVENT_GET_TYPE(bp)     ((bp)->events)
+#define EVENT_TEST_READ(bp)    (EVENT_GET_TYPE(bp) & EPOLLIN)
+#define EVENT_TEST_WRITE(bp)   (EVENT_GET_TYPE(bp) & EPOLLOUT)
+
+#endif
 
  /*
   * Timer events. Timer requests are kept sorted, in a circular list. We use
@@ -201,21 +512,29 @@ static time_t event_present;              /* cached time of day */
 static void event_init(void)
 {
     EVENT_FDTABLE *fdp;
+    int     err;
 
     if (!EVENT_INIT_NEEDED())
        msg_panic("event_init: repeated call");
 
     /*
-     * Initialize the file descriptor table. XXX It should be possible to
-     * adjust (or at least extend) the table size on the fly.
+     * Initialize the file descriptor masks and the call-back table. Where
+     * possible we extend these data structures on the fly. With select(2)
+     * based implementations we can only handle FD_SETSIZE open files.
      */
-    if ((event_fdsize = open_limit(FD_SETSIZE)) < 0)
+#ifdef USE_SELECT_EVENTS
+    if ((event_fdlimit = open_limit(FD_SETSIZE)) < 0)
+       msg_fatal("unable to determine open file limit");
+#else
+    if ((event_fdlimit = open_limit(INT_MAX)) < 0)
        msg_fatal("unable to determine open file limit");
-    if (event_fdsize < FD_SETSIZE / 2 && event_fdsize < 256)
-       msg_warn("could allocate space for only %d open files", event_fdsize);
+#endif
+    if (event_fdlimit < FD_SETSIZE / 2 && event_fdlimit < 256)
+       msg_warn("could allocate space for only %d open files", event_fdlimit);
+    event_fdslots = EVENT_ALLOC_INCR;
     event_fdtable = (EVENT_FDTABLE *)
-       mymalloc(sizeof(EVENT_FDTABLE) * event_fdsize);
-    for (fdp = event_fdtable; fdp < event_fdtable + event_fdsize; fdp++) {
+       mymalloc(sizeof(EVENT_FDTABLE) * event_fdslots);
+    for (fdp = event_fdtable; fdp < event_fdtable + event_fdslots; fdp++) {
        fdp->callback = 0;
        fdp->context = 0;
     }
@@ -223,9 +542,22 @@ static void event_init(void)
     /*
      * Initialize the I/O event request masks.
      */
-    FD_ZERO(&event_rmask);
-    FD_ZERO(&event_wmask);
-    FD_ZERO(&event_xmask);
+#ifdef USE_SELECT_EVENTS
+    EVENT_MASK_ZERO(&event_rmask);
+    EVENT_MASK_ZERO(&event_wmask);
+    EVENT_MASK_ZERO(&event_xmask);
+#else
+    EVENT_MASK_ALLOC(&event_rmask, event_fdslots);
+    EVENT_MASK_ALLOC(&event_wmask, event_fdslots);
+    EVENT_MASK_ALLOC(&event_xmask, event_fdslots);
+
+    /*
+     * Initialize the kernel-based filter.
+     */
+    EVENT_REG_INIT_HANDLE(err, event_fdslots);
+    if (err < 0)
+       msg_fatal("%s: %m", EVENT_REG_INIT_TEXT);
+#endif
 
     /*
      * Initialize timer stuff.
@@ -240,6 +572,43 @@ static void event_init(void)
        msg_panic("event_init: unable to initialize");
 }
 
+/* event_extend - make room for more descriptor slots */
+
+static void event_extend(int fd)
+{
+    const char *myname = "event_extend";
+    int     old_slots = event_fdslots;
+    int     new_slots = (event_fdslots > fd / 2 ?
+                        2 * old_slots : fd + EVENT_ALLOC_INCR);
+    EVENT_FDTABLE *fdp;
+    int     err;
+
+    if (msg_verbose > 2)
+       msg_info("%s: fd %d", myname, fd);
+    event_fdtable = (EVENT_FDTABLE *)
+       myrealloc((char *) event_fdtable, sizeof(EVENT_FDTABLE) * new_slots);
+    event_fdslots = new_slots;
+    for (fdp = event_fdtable + old_slots;
+        fdp < event_fdtable + new_slots; fdp++) {
+       fdp->callback = 0;
+       fdp->context = 0;
+    }
+
+    /*
+     * Initialize the I/O event request masks.
+     */
+#ifndef USE_SELECT_EVENTS
+    EVENT_MASK_REALLOC(&event_rmask, new_slots);
+    EVENT_MASK_REALLOC(&event_wmask, new_slots);
+    EVENT_MASK_REALLOC(&event_xmask, new_slots);
+#endif
+#ifdef EVENT_REG_UPD_HANDLE
+    EVENT_REG_UPD_HANDLE(err, new_slots);
+    if (err < 0)
+       msg_fatal("%s: %s: %m", myname, EVENT_REG_UPD_TEXT);
+#endif
+}
+
 /* event_time - look up cached time of day */
 
 time_t  event_time(void)
@@ -254,18 +623,19 @@ time_t  event_time(void)
 
 void    event_drain(int time_limit)
 {
-    fd_set  zero_mask;
+    EVENT_MASK zero_mask;
     time_t  max_time;
 
     if (EVENT_INIT_NEEDED())
        return;
 
-    FD_ZERO(&zero_mask);
+    EVENT_MASK_ZERO(&zero_mask);
     (void) time(&event_present);
     max_time = event_present + time_limit;
     while (event_present < max_time
           && (event_timer_head.pred != &event_timer_head
-              || memcmp(&zero_mask, &event_xmask, sizeof(zero_mask)) != 0))
+              || memcmp(&zero_mask, &event_xmask,
+                        EVENT_MASK_BYTE_COUNT(&zero_mask)) != 0))
        event_loop(1);
 }
 
@@ -275,6 +645,7 @@ void    event_enable_read(int fd, EVENT_NOTIFY_RDWR callback, char *context)
 {
     const char *myname = "event_enable_read";
     EVENT_FDTABLE *fdp;
+    int     err;
 
     if (EVENT_INIT_NEEDED())
        event_init();
@@ -282,30 +653,45 @@ void    event_enable_read(int fd, EVENT_NOTIFY_RDWR callback, char *context)
     /*
      * Sanity checks.
      */
-    if (fd < 0 || fd >= event_fdsize)
+    if (fd < 0 || fd >= event_fdlimit)
        msg_panic("%s: bad file descriptor: %d", myname, fd);
 
     if (msg_verbose > 2)
        msg_info("%s: fd %d", myname, fd);
 
+    if (fd >= event_fdslots)
+       event_extend(fd);
+
     /*
-     * Disallow multiple requests on the same file descriptor. Allow
-     * duplicates of the same request.
+     * Disallow mixed (i.e. read and write) requests on the same descriptor.
      */
+    if (EVENT_MASK_ISSET(fd, &event_wmask))
+       msg_panic("%s: fd %d: read/write I/O request", myname, fd);
+
+    /*
+     * Postfix 2.4 allows multiple event_enable_read() calls on the same
+     * descriptor without requiring event_disable_readwrite() calls between
+     * them. With kernel-based filters (kqueue, /dev/poll, epoll) it's
+     * wasteful to make system calls when we change only application
+     * call-back information. It has a noticeable effect on smtp-source
+     * performance.
+     */
+    if (EVENT_MASK_ISSET(fd, &event_rmask) == 0) {
+       EVENT_MASK_SET(fd, &event_xmask);
+       EVENT_MASK_SET(fd, &event_rmask);
+       if (event_max_fd < fd)
+           event_max_fd = fd;
+#ifndef USE_SELECT_EVENTS
+       EVENT_REG_ADD_READ(err, fd);
+       if (err < 0)
+           msg_fatal("%s: %s: %m", myname, EVENT_REG_ADD_TEXT);
+#endif
+    }
     fdp = event_fdtable + fd;
-    if (FD_ISSET(fd, &event_xmask)) {
-       if (FD_ISSET(fd, &event_rmask)
-           && fdp->callback == callback
-           && fdp->context == context)
-           return;
-       msg_panic("%s: fd %d: multiple I/O request", myname, fd);
+    if (fdp->callback != callback || fdp->context != context) {
+       fdp->callback = callback;
+       fdp->context = context;
     }
-    FD_SET(fd, &event_xmask);
-    FD_SET(fd, &event_rmask);
-    fdp->callback = callback;
-    fdp->context = context;
-    if (event_max_fd < fd)
-       event_max_fd = fd;
 }
 
 /* event_enable_write - enable write events */
@@ -314,6 +700,7 @@ void    event_enable_write(int fd, EVENT_NOTIFY_RDWR callback, char *context)
 {
     const char *myname = "event_enable_write";
     EVENT_FDTABLE *fdp;
+    int     err;
 
     if (EVENT_INIT_NEEDED())
        event_init();
@@ -321,30 +708,45 @@ void    event_enable_write(int fd, EVENT_NOTIFY_RDWR callback, char *context)
     /*
      * Sanity checks.
      */
-    if (fd < 0 || fd >= event_fdsize)
+    if (fd < 0 || fd >= event_fdlimit)
        msg_panic("%s: bad file descriptor: %d", myname, fd);
 
     if (msg_verbose > 2)
        msg_info("%s: fd %d", myname, fd);
 
+    if (fd >= event_fdslots)
+       event_extend(fd);
+
     /*
-     * Disallow multiple requests on the same file descriptor. Allow
-     * duplicates of the same request.
+     * Disallow mixed (i.e. read and write) requests on the same descriptor.
      */
+    if (EVENT_MASK_ISSET(fd, &event_rmask))
+       msg_panic("%s: fd %d: read/write I/O request", myname, fd);
+
+    /*
+     * Postfix 2.4 allows multiple event_enable_write() calls on the same
+     * descriptor without requiring event_disable_readwrite() calls between
+     * them. With kernel-based filters (kqueue, /dev/poll, epoll) it's
+     * incredibly wasteful to make unregister and register system calls when
+     * we change only application call-back information. It has a noticeable
+     * effect on smtp-source performance.
+     */
+    if (EVENT_MASK_ISSET(fd, &event_wmask) == 0) {
+       EVENT_MASK_SET(fd, &event_xmask);
+       EVENT_MASK_SET(fd, &event_wmask);
+       if (event_max_fd < fd)
+           event_max_fd = fd;
+#ifndef USE_SELECT_EVENTS
+       EVENT_REG_ADD_WRITE(err, fd);
+       if (err < 0)
+           msg_fatal("%s: %s: %m", myname, EVENT_REG_ADD_TEXT);
+#endif
+    }
     fdp = event_fdtable + fd;
-    if (FD_ISSET(fd, &event_xmask)) {
-       if (FD_ISSET(fd, &event_wmask)
-           && fdp->callback == callback
-           && fdp->context == context)
-           return;
-       msg_panic("%s: fd %d: multiple I/O request", myname, fd);
+    if (fdp->callback != callback || fdp->context != context) {
+       fdp->callback = callback;
+       fdp->context = context;
     }
-    FD_SET(fd, &event_xmask);
-    FD_SET(fd, &event_wmask);
-    fdp->callback = callback;
-    fdp->context = context;
-    if (event_max_fd < fd)
-       event_max_fd = fd;
 }
 
 /* event_disable_readwrite - disable request for read or write events */
@@ -353,6 +755,7 @@ void    event_disable_readwrite(int fd)
 {
     const char *myname = "event_disable_readwrite";
     EVENT_FDTABLE *fdp;
+    int     err;
 
     if (EVENT_INIT_NEEDED())
        event_init();
@@ -360,7 +763,7 @@ void    event_disable_readwrite(int fd)
     /*
      * Sanity checks.
      */
-    if (fd < 0 || fd >= event_fdsize)
+    if (fd < 0 || fd >= event_fdlimit)
        msg_panic("%s: bad file descriptor: %d", myname, fd);
 
     if (msg_verbose > 2)
@@ -370,9 +773,32 @@ void    event_disable_readwrite(int fd)
      * Don't complain when there is nothing to cancel. The request may have
      * been canceled from another thread.
      */
-    FD_CLR(fd, &event_xmask);
-    FD_CLR(fd, &event_rmask);
-    FD_CLR(fd, &event_wmask);
+    if (fd >= event_fdslots)
+       return;
+#ifndef USE_SELECT_EVENTS
+#ifdef EVENT_REG_DEL_BOTH
+    /* XXX Can't seem to disable READ and WRITE events selectively. */
+    if (EVENT_MASK_ISSET(fd, &event_rmask)
+       || EVENT_MASK_ISSET(fd, &event_wmask)) {
+       EVENT_REG_DEL_BOTH(err, fd);
+       if (err < 0)
+           msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT);
+    }
+#else
+    if (EVENT_MASK_ISSET(fd, &event_rmask)) {
+       EVENT_REG_DEL_READ(err, fd);
+       if (err < 0)
+           msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT);
+    } else if (EVENT_MASK_ISSET(fd, &event_wmask)) {
+       EVENT_REG_DEL_WRITE(err, fd);
+       if (err < 0)
+           msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT);
+    }
+#endif                                         /* EVENT_REG_DEL_BOTH */
+#endif                                         /* USE_SELECT_EVENTS */
+    EVENT_MASK_CLR(fd, &event_xmask);
+    EVENT_MASK_CLR(fd, &event_rmask);
+    EVENT_MASK_CLR(fd, &event_wmask);
     fdp = event_fdtable + fd;
     fdp->callback = 0;
     fdp->context = 0;
@@ -481,11 +907,20 @@ void    event_loop(int delay)
 {
     const char *myname = "event_loop";
     static int nested;
+
+#ifdef USE_SELECT_EVENTS
     fd_set  rmask;
     fd_set  wmask;
     fd_set  xmask;
     struct timeval tv;
     struct timeval *tvp;
+
+#else
+    EVENT_BUFFER event_buf[100];
+    EVENT_BUFFER *bp;
+    int     event_count;
+
+#endif
     EVENT_TIMER *timer;
     int     fd;
     EVENT_FDTABLE *fdp;
@@ -529,6 +964,7 @@ void    event_loop(int delay)
      * Negative delay means: wait until something happens. Zero delay means:
      * poll. Positive delay means: wait at most this long.
      */
+#ifdef USE_SELECT_EVENTS
     if (select_delay < 0) {
        tvp = 0;
     } else {
@@ -551,6 +987,16 @@ void    event_loop(int delay)
            msg_fatal("event_loop: select: %m");
        return;
     }
+#else
+    EVENT_BUFFER_READ(event_count, event_buf,
+                     sizeof(event_buf) / sizeof(event_buf[0]),
+                     select_delay);
+    if (event_count < 0) {
+       if (errno != EINTR)
+           msg_fatal("event_loop: kevent: %m");
+       return;
+    }
+#endif
 
     /*
      * Before entering the application call-back routines, make sure we
@@ -587,26 +1033,56 @@ void    event_loop(int delay)
      * wanted. We do not change the event request masks. It is up to the
      * application to determine when a read or write is complete.
      */
-    for (fd = 0, fdp = event_fdtable; fd <= event_max_fd; fd++, fdp++) {
+#ifdef USE_SELECT_EVENTS
+    for (fd = 0; fd <= event_max_fd; fd++) {
        if (FD_ISSET(fd, &event_xmask)) {
+           /* In case event_fdtable is updated. */
+           fdp = event_fdtable + fd;
            if (FD_ISSET(fd, &xmask)) {
                if (msg_verbose > 2)
-                   msg_info("%s: exception %d 0x%lx 0x%lx", myname,
+                   msg_info("%s: exception fd=%d act=0x%lx 0x%lx", myname,
                             fd, (long) fdp->callback, (long) fdp->context);
                fdp->callback(EVENT_XCPT, fdp->context);
            } else if (FD_ISSET(fd, &wmask)) {
                if (msg_verbose > 2)
-                   msg_info("%s: write %d 0x%lx 0x%lx", myname,
+                   msg_info("%s: write fd=%d act=0x%lx 0x%lx", myname,
                             fd, (long) fdp->callback, (long) fdp->context);
                fdp->callback(EVENT_WRITE, fdp->context);
            } else if (FD_ISSET(fd, &rmask)) {
                if (msg_verbose > 2)
-                   msg_info("%s: read %d 0x%lx 0x%lx", myname,
+                   msg_info("%s: read fd=%d act=0x%lx 0x%lx", myname,
+                            fd, (long) fdp->callback, (long) fdp->context);
+               fdp->callback(EVENT_READ, fdp->context);
+           }
+       }
+    }
+#else
+    for (bp = event_buf; bp < event_buf + event_count; bp++) {
+       fd = EVENT_GET_FD(bp);
+       if (fd < 0 || fd > event_max_fd)
+           msg_panic("%s: bad file descriptor: %d", myname, fd);
+       if (EVENT_MASK_ISSET(fd, &event_xmask)) {
+           fdp = event_fdtable + fd;
+           if (EVENT_TEST_READ(bp)) {
+               if (msg_verbose > 2)
+                   msg_info("%s: read fd=%d act=0x%lx 0x%lx", myname,
                             fd, (long) fdp->callback, (long) fdp->context);
                fdp->callback(EVENT_READ, fdp->context);
+           } else if (EVENT_TEST_WRITE(bp)) {
+               if (msg_verbose > 2)
+                   msg_info("%s: write fd=%d act=0x%lx 0x%lx", myname,
+                            fd, (long) fdp->callback,
+                            (long) fdp->context);
+               fdp->callback(EVENT_WRITE, fdp->context);
+           } else {
+               if (msg_verbose > 2)
+                   msg_info("%s: other fd=%d act=0x%lx 0x%lx", myname,
+                            fd, (long) fdp->callback, (long) fdp->context);
+               fdp->callback(EVENT_XCPT, fdp->context);
            }
        }
     }
+#endif
     nested--;
 }
 
@@ -640,8 +1116,10 @@ static void echo(int unused_event, char *unused_context)
     printf("Result: %s", buf);
 }
 
-int     main(void)
+int     main(int argc, char **argv)
 {
+    if (argv[1])
+       msg_verbose = atoi(argv[1]);
     event_request_timer(timer_event, "3 first", 3);
     event_request_timer(timer_event, "3 second", 3);
     event_request_timer(timer_event, "4 first", 4);
index b1ec3528c2f6a6ce7d1b4ba8df78b7b63e7a3d7d..c94c1a06fd3e8e222ae54946eb2be8947b4b45cd 100644 (file)
 #include <unistd.h>
 #include <string.h>
 
+#ifdef USE_SYSV_POLL
+#include <poll.h>
+#endif
+
 #ifdef USE_SYS_SELECT_H
 #include <sys/select.h>
 #endif
@@ -57,6 +61,7 @@
 
 int     read_wait(int fd, int timeout)
 {
+#ifndef USE_SYSV_POLL
     fd_set  read_fds;
     fd_set  except_fds;
     struct timeval tv;
@@ -99,4 +104,32 @@ int     read_wait(int fd, int timeout)
            return (0);
        }
     }
+#else
+
+    /*
+     * System-V poll() is optimal for polling a few descriptors.
+     */
+    struct pollfd pollfd;
+
+#define WAIT_FOR_EVENT (-1)
+
+    pollfd.fd = fd;
+    pollfd.events = POLLIN;
+    for (;;) {
+       switch (poll(&pollfd, 1, timeout < 0 ?
+                    WAIT_FOR_EVENT : timeout * 1000)) {
+       case -1:
+           if (errno != EINTR)
+               msg_fatal("poll: %m");
+           continue;
+       case 0:
+           errno = ETIMEDOUT;
+           return (-1);
+       default:
+           if (pollfd.revents & POLLNVAL)
+               msg_fatal("poll: %m");
+           return (0);
+       }
+    }
+#endif
 }
index a3af0daf059714fdec687e44bb89b495bc15e6a4..7d297403b03a363f998cb5d5bb350fcec8aac5b9 100644 (file)
 #include <unistd.h>
 #include <string.h>
 
+#ifdef USE_SYSV_POLL
+#include <poll.h>
+#endif
+
 #ifdef USE_SYS_SELECT_H
 #include <sys/select.h>
 #endif
@@ -50,6 +54,7 @@
 
 int     readable(int fd)
 {
+#ifndef USE_SYSV_POLL
     struct timeval tv;
     fd_set  read_fds;
     fd_set  except_fds;
@@ -85,4 +90,30 @@ int     readable(int fd)
            return (0);
        }
     }
+#else
+
+    /*
+     * System-V poll() is optimal for polling a few descriptors.
+     */
+    struct pollfd pollfd;
+
+#define DONT_WAIT_FOR_EVENT    0
+
+    pollfd.fd = fd;
+    pollfd.events = POLLIN;
+    for (;;) {
+       switch (poll(&pollfd, 1, DONT_WAIT_FOR_EVENT)) {
+       case -1:
+           if (errno != EINTR)
+               msg_fatal("poll: %m");
+           continue;
+       case 0:
+           return (0);
+       default:
+           if (pollfd.revents & POLLNVAL)
+               msg_fatal("poll: %m");
+           return (1);
+       }
+    }
+#endif
 }
index 63e9e3c904679255ecb3cd9104a1604488f990b8..6d4b8e4c246619f953d055accfb80644a9f3ed81 100644 (file)
 # define HAVE_GETIFADDRS
 #endif
 
+#if (defined(__FreeBSD_version) && __FreeBSD_version >= 300000) \
+    || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 103000000) \
+    || (defined(OpenBSD) && OpenBSD >= 199700) /* OpenBSD 2.0?? */
+# define USE_SYSV_POLL
+#endif
+
+#if (defined(__FreeBSD_version) && __FreeBSD_version >= 410000) \
+    || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 200000000) \
+    || (defined(OpenBSD) && OpenBSD >= 200105) /* OpenBSD 2.9 */
+# define USE_KQUEUE_EVENTS
+#endif
+
 #endif
 
  /*
@@ -393,6 +405,10 @@ extern int opterr;
 #ifndef NO_FUTIMESAT
 # define HAS_FUTIMESAT
 #endif
+#define USE_SYSV_POLL
+#ifndef NO_DEVPOLL
+# define USE_DEVPOLL_EVENTS
+#endif
 
 /*
  * Allow build environment to override paths.
@@ -705,6 +721,10 @@ extern int initgroups(const char *, int);
 # define CANT_WRITE_BEFORE_SENDING_FD
 #endif
 #define HAS_DEV_URANDOM                        /* introduced in 1.1 */
+#ifndef NO_EPOLL
+# define USE_EPOLL_EVENTS              /* introduced in 2.5 */
+#endif
+#define USE_SYSV_POLL
 #endif
 
 #ifdef LINUX1
@@ -1213,6 +1233,15 @@ extern int dup2_pass_on_exec(int oldd, int newd);
 extern const char *inet_ntop(int, const void *, char *, size_t);
 extern int inet_pton(int, const char *, void *);
 
+#endif
+
+ /*
+  * Defaults for systems without kqueue, /dev/poll or epoll support.
+  * master/multi-server.c relies on this.
+  */
+#if !defined(USE_KQUEUE_EVENTS) && !defined(USE_DEVPOLL_EVENTS) \
+    && !defined(USE_EPOLL_EVENTS)
+#define USE_SELECT_EVENTS
 #endif
 
  /*
index 52c6d4c7ff392d14fb9241abfa102cb96f08042f..f37eb22296c4033542dffe2b6011d03f01b955f1 100644 (file)
 #include <unistd.h>
 #include <string.h>
 
+#ifdef USE_SYSV_POLL
+#include <poll.h>
+#endif
+
 #ifdef USE_SYS_SELECT_H
 #include <sys/select.h>
 #endif
@@ -50,6 +54,7 @@
 
 int     writable(int fd)
 {
+#ifndef USE_SYSV_POLL
     struct timeval tv;
     fd_set  write_fds;
     fd_set  except_fds;
@@ -85,4 +90,30 @@ int     writable(int fd)
            return (0);
        }
     }
+#else
+
+    /*
+     * System-V poll() is optimal for polling a few descriptors.
+     */
+    struct pollfd pollfd;
+
+#define DONT_WAIT_FOR_EVENT    0
+
+    pollfd.fd = fd;
+    pollfd.events = POLLOUT;
+    for (;;) {
+       switch (poll(&pollfd, 1, DONT_WAIT_FOR_EVENT)) {
+       case -1:
+           if (errno != EINTR)
+               msg_fatal("poll: %m");
+           continue;
+       case 0:
+           return (0);
+       default:
+           if (pollfd.revents & POLLNVAL)
+               msg_fatal("poll: %m");
+           return (1);
+       }
+    }
+#endif
 }
index a17c0e595e5597bcf3ba7677fdb6c0a62749f1b6..1a42c126ef7d0428e28fedee8245f69386f6ae6e 100644 (file)
 #include <unistd.h>
 #include <string.h>
 
+#ifdef USE_SYSV_POLL
+#include <poll.h>
+#endif
+
 #ifdef USE_SYS_SELECT_H
 #include <sys/select.h>
 #endif
@@ -57,6 +61,7 @@
 
 int     write_wait(int fd, int timeout)
 {
+#ifndef USE_SYSV_POLL
     fd_set  write_fds;
     fd_set  except_fds;
     struct timeval tv;
@@ -99,4 +104,32 @@ int     write_wait(int fd, int timeout)
            return (0);
        }
     }
+#else
+
+    /*
+     * System-V poll() is optimal for polling a few descriptors.
+     */
+    struct pollfd pollfd;
+
+#define WAIT_FOR_EVENT (-1)
+
+    pollfd.fd = fd;
+    pollfd.events = POLLOUT;
+    for (;;) {
+       switch (poll(&pollfd, 1, timeout < 0 ?
+                    WAIT_FOR_EVENT : timeout * 1000)) {
+       case -1:
+           if (errno != EINTR)
+               msg_fatal("poll: %m");
+           continue;
+       case 0:
+           errno = ETIMEDOUT;
+           return (-1);
+       default:
+           if (pollfd.revents & POLLNVAL)
+               msg_fatal("poll: %m");
+           return (0);
+       }
+    }
+#endif
 }