]> mj.ucw.cz Git - libucw.git/commitdiff
Main: Unified support for poll and epoll
authorMartin Mares <mj@ucw.cz>
Fri, 30 Jul 2010 18:24:56 +0000 (20:24 +0200)
committerMartin Mares <mj@ucw.cz>
Wed, 18 Aug 2010 16:12:58 +0000 (18:12 +0200)
When the epoll mechanism is available in the system library, use it.

Rewritten processing of file events: when (e)poll completes, all pending
events are scanned and the corresponding files are relinked to another
list. Then this list is processed entry by entry and callbacks are
performed. When file_add() or file_del() is requested from the callback
function, the file is just relinked to the main file_list and the worst
thing which can happen is that an event will be postponed to the next
iteration of the main loop.

To achieve this, I had to decouple the order of entries in the poll_table
from the order of files in the file_list by introducing an auxiliary
array of pointers mapping poll_table entries to file structs.

All tricks with stopping the event scanning loop when poll_table_obsolete
becomes set are gone.

ucw/mainloop.c
ucw/mainloop.h
ucw/perl/UCW/Configure/LibUCW.pm

index 6bdf1e014203e160bd37fda173dbd15ecd0bcc07..5921368330b2ad5c0537553be025324b17fed8da 100644 (file)
 #define THREAD_SIGMASK sigprocmask
 #endif
 
+#ifdef CONFIG_UCW_EPOLL
+#include <sys/epoll.h>
+#endif
+
 #define MAIN_TIMER_LESS(x,y) ((x)->expires < (y)->expires)
 #define MAIN_TIMER_SWAP(heap,a,b,t) (t=heap[a], heap[a]=heap[b], heap[b]=t, heap[a]->index=(a), heap[b]->index=(b))
 
+#define EPOLL_BUF_SIZE 256
+
 static void
 do_main_get_time(struct main_context *m)
 {
@@ -53,11 +59,19 @@ main_new(void)
 
   DBG("MAIN: New context");
   clist_init(&m->file_list);
+  clist_init(&m->file_active_list);
   clist_init(&m->hook_list);
   clist_init(&m->hook_done_list);
   clist_init(&m->process_list);
   clist_init(&m->signal_list);
+#ifdef CONFIG_UCW_EPOLL
+  m->epoll_fd = epoll_create(64);
+  if (m->epoll_fd < 0)
+    die("epoll_create() failed: %m");
+  m->epoll_events = xmalloc(EPOLL_BUF_SIZE * sizeof(struct epoll_event *));
+#else
   m->poll_table_obsolete = 1;
+#endif
   do_main_get_time(m);
   sigemptyset(&m->want_signals);
   m->sig_pipe_recv = m->sig_pipe_send = -1;
@@ -78,12 +92,19 @@ main_delete(struct main_context *m)
       close(m->sig_pipe_send);
     }
   ASSERT(clist_empty(&m->file_list));
+  ASSERT(clist_empty(&m->file_active_list));
   ASSERT(clist_empty(&m->hook_list));
   ASSERT(clist_empty(&m->hook_done_list));
   ASSERT(clist_empty(&m->process_list));
   ASSERT(clist_empty(&m->signal_list));
   GARY_FREE(m->timer_table);
+#ifdef CONFIG_UCW_EPOLL
+  xfree(m->epoll_events);
+  close(m->epoll_fd);
+#else
   GARY_FREE(m->poll_table);
+  GARY_FREE(m->poll_file_table);
+#endif
   xfree(m);
   // FIXME: Some mechanism for cleaning up after fork()
 }
@@ -205,6 +226,17 @@ timer_del(struct main_timer *tm)
   timer_add(tm, 0);
 }
 
+static uns
+file_want_events(struct main_file *fi)
+{
+  uns events = 0;
+  if (fi->read_handler)
+    events |= POLLIN | POLLHUP | POLLERR;
+  if (fi->write_handler)
+    events |= POLLOUT | POLLERR;
+  return events;
+}
+
 void
 file_add(struct main_file *fi)
 {
@@ -214,7 +246,16 @@ file_add(struct main_file *fi)
   ASSERT(!clist_is_linked(&fi->n));
   clist_add_tail(&m->file_list, &fi->n);
   m->file_cnt++;
+#ifdef CONFIG_UCW_EPOLL
+  struct epoll_event evt = {
+    .events = file_want_events(fi),
+    .data.ptr = fi,
+  };
+  if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, fi->fd, &evt) < 0)
+    die("epoll_ctl() failed: %m");
+#else
   m->poll_table_obsolete = 1;
+#endif
   if (fcntl(fi->fd, F_SETFL, O_NONBLOCK) < 0)
     msg(L_ERROR, "Error setting fd %d to non-blocking mode: %m. Keep fingers crossed.", fi->fd);
 }
@@ -222,15 +263,18 @@ file_add(struct main_file *fi)
 void
 file_chg(struct main_file *fi)
 {
+#ifdef CONFIG_UCW_EPOLL
+  struct epoll_event evt = {
+    .events = file_want_events(fi),
+    .data.ptr = fi,
+  };
+  if (epoll_ctl(main_current()->epoll_fd, EPOLL_CTL_MOD, fi->fd, &evt) < 0)
+    die("epoll_ctl() failed: %m");
+#else
   struct pollfd *p = fi->pollfd;
   if (p)
-    {
-      p->events = 0;
-      if (fi->read_handler)
-       p->events |= POLLIN | POLLHUP | POLLERR;
-      if (fi->write_handler)
-       p->events |= POLLOUT | POLLERR;
-    }
+    p->events = file_want_events(fi);
+#endif
 }
 
 void
@@ -242,7 +286,12 @@ file_del(struct main_file *fi)
   ASSERT(clist_is_linked(&fi->n));
   clist_unlink(&fi->n);
   m->file_cnt--;
+#ifdef CONFIG_UCW_EPOLL
+  if (epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, fi->fd, NULL) < 0)
+    die("epoll_ctl() failed: %m");
+#else
   m->poll_table_obsolete = 1;
+#endif
 }
 
 void
@@ -484,6 +533,9 @@ main_debug_context(struct main_context *m UNUSED)
   CLIST_FOR_EACH(struct main_file *, fi, m->file_list)
     msg(L_DEBUG, "\t\t%p (fd %d, rh %p, wh %p, data %p)",
        fi, fi->fd, fi->read_handler, fi->write_handler, fi->data);
+  CLIST_FOR_EACH(struct main_file *, fi, m->file_list)
+    msg(L_DEBUG, "\t\t%p (fd %d, rh %p, wh %p, data %p) [pending events: %x]",
+       fi, fi->fd, fi->read_handler, fi->write_handler, fi->data, fi->events);
     // FIXME: Can we display status of block_io requests somehow?
   msg(L_DEBUG, "\tActive hooks:");
   CLIST_FOR_EACH(struct main_hook *, ho, m->hook_done_list)
@@ -502,22 +554,6 @@ main_debug_context(struct main_context *m UNUSED)
 #endif
 }
 
-static void
-main_rebuild_poll_table(struct main_context *m)
-{
-  GARY_INIT_OR_RESIZE(m->poll_table, m->file_cnt);
-  DBG("MAIN: Rebuilding poll table: %d entries", m->file_cnt);
-
-  struct pollfd *p = m->poll_table;
-  CLIST_FOR_EACH(struct main_file *, fi, m->file_list)
-    {
-      p->fd = fi->fd;
-      fi->pollfd = p++;
-      file_chg(fi);
-    }
-  m->poll_table_obsolete = 0;
-}
-
 static void
 process_timers(struct main_context *m)
 {
@@ -558,6 +594,29 @@ process_hooks(struct main_context *m)
     return HOOK_IDLE;
 }
 
+#ifndef CONFIG_UCW_EPOLL
+
+static void
+main_rebuild_poll_table(struct main_context *m)
+{
+  GARY_INIT_OR_RESIZE(m->poll_table, m->file_cnt);
+  GARY_INIT_OR_RESIZE(m->poll_file_table, m->file_cnt);
+  DBG("MAIN: Rebuilding poll table: %d entries", m->file_cnt);
+
+  struct pollfd *p = m->poll_table;
+  struct main_file **pf = m->poll_file_table;
+  CLIST_FOR_EACH(struct main_file *, fi, m->file_list)
+    {
+      p->fd = fi->fd;
+      fi->pollfd = p++;
+      file_chg(fi);
+      *pf++ = fi;
+    }
+  m->poll_table_obsolete = 0;
+}
+
+#endif
+
 void
 main_loop(void)
 {
@@ -578,39 +637,70 @@ main_loop(void)
          break;
        default: ;
        }
-      if (m->poll_table_obsolete)
-       main_rebuild_poll_table(m);
       if (count_timers(m))
        wake = MIN(wake, m->timer_table[1]->expires);
       do_main_get_time(m);
       int timeout = ((wake > m->now) ? wake - m->now : 0);
+
+#ifdef CONFIG_UCW_EPOLL
+      DBG("MAIN: Epoll for %d fds and timeout %d ms", m->file_cnt, timeout);
+      int n = epoll_wait(m->epoll_fd, m->epoll_events, EPOLL_BUF_SIZE, timeout);
+#else
+      if (m->poll_table_obsolete)
+       main_rebuild_poll_table(m);
       DBG("MAIN: Poll for %d fds and timeout %d ms", m->file_cnt, timeout);
-      int p = poll(m->poll_table, m->file_cnt, timeout);
+      int n = poll(m->poll_table, m->file_cnt, timeout);
+#endif
+
+      DBG("\t-> %d events", n);
+      if (n < 0 && errno != EAGAIN && errno != EINTR)
+       die("(e)poll failed: %m");
       timestamp_t old_now = m->now;
       do_main_get_time(m);
       m->idle_time += m->now - old_now;
-      if (p > 0)
+
+      if (n <= 0)
+       continue;
+
+      // Relink all files with a pending event to file_active_list
+#ifdef CONFIG_UCW_EPOLL
+      for (int i=0; i<n; i++)
        {
-         struct pollfd *p = m->poll_table;
-         CLIST_FOR_EACH(struct main_file *, fi, m->file_list)
+         struct epoll_event *e = &m->epoll_events[i];
+         struct main_file *fi = e->data.ptr;
+         clist_remove(&fi->n);
+         clist_add_tail(&m->file_active_list, &fi->n);
+         fi->events = e->events;
+       }
+#else
+      struct pollfd *p = m->poll_table;
+      struct main_file **pf = m->poll_file_table;
+      for (uns i=0; i < m->file_cnt; i++)
+       if (p[i].revents)
+         {
+           struct main_file *fi = pf[i];
+           clist_remove(&fi->n);
+           clist_add_tail(&m->file_active_list, &fi->n);
+           fi->events = p[i].revents;
+         }
+#endif
+
+      // Process the buffered file events
+      struct main_file *fi;
+      while (fi = clist_remove_head(&m->file_active_list))
+       {
+         clist_add_tail(&m->file_list, &fi->n);
+         if (fi->events & (POLLIN | POLLHUP | POLLERR))
+           {
+             do
+               DBG("MAIN: Read event on fd %d", fi->fd);
+             while (fi->read_handler && fi->read_handler(fi));
+           }
+         if (fi->events & (POLLOUT | POLLERR))
            {
-             if (p->revents & (POLLIN | POLLHUP | POLLERR))
-               {
-                 do
-                   DBG("MAIN: Read event on fd %d", p->fd);
-                 while (fi->read_handler && fi->read_handler(fi) && !m->poll_table_obsolete);
-                 if (m->poll_table_obsolete)   /* File entries have been inserted or deleted => better not risk continuing to nowhere */
-                   break;
-               }
-             if (p->revents & (POLLOUT | POLLERR))
-               {
-                 do
-                   DBG("MAIN: Write event on fd %d", p->fd);
-                 while (fi->write_handler && fi->write_handler(fi) && !m->poll_table_obsolete);
-                 if (m->poll_table_obsolete)
-                   break;
-               }
-             p++;
+             do
+               DBG("MAIN: Write event on fd %d", fi->fd);
+             while (fi->write_handler && fi->write_handler(fi));
            }
        }
     }
index b403c69d242f8f25e5390e17af3a3a50cfd8f131..d2b2d3406ff14b204e669d5447aa3ee75f612e45 100644 (file)
@@ -33,13 +33,20 @@ struct main_context {
   timestamp_t idle_time;               /** [*] Total time in milliseconds spent by waiting for events. **/
   uns shutdown;                                /** [*] Setting this to nonzero forces the @main_loop() function to terminate. **/
   clist file_list;
+  clist file_active_list;
   clist hook_list;
   clist hook_done_list;
   clist process_list;
   clist signal_list;
   uns file_cnt;
+#ifdef CONFIG_UCW_EPOLL
+  int epoll_fd;                                /* File descriptor used for epoll */
+  struct epoll_event *epoll_events;
+#else
   uns poll_table_obsolete;
   struct pollfd *poll_table;
+  struct main_file **poll_file_table;
+#endif
   struct main_timer **timer_table;     /* Growing array containing the heap of timers */
   sigset_t want_signals;
   int sig_pipe_send;
@@ -188,7 +195,10 @@ struct main_file {
   int (*read_handler)(struct main_file *fi);   /* [*] To be called when ready for reading/writing; must call file_chg() afterwards */
   int (*write_handler)(struct main_file *fi);
   void *data;                                  /* [*] Data for use by the handlers */
+  uns events;
+#ifndef CONFIG_UCW_EPOLL
   struct pollfd *pollfd;
+#endif
 };
 
 /**
index 5969e383fee6b52fcaaa4ede1eb99d55de2e2437..c40b1cac5e25d5a727c960daade29e6b24710d3e 100644 (file)
@@ -54,6 +54,18 @@ if (Get("CONFIG_UCW_THREADS")) {
        });
 }
 
+# Detect if we have the epoll() syscall
+TestBool("CONFIG_UCW_EPOLL", "Checking for epoll", sub {
+       return UCW::Configure::C::TestCompile("epoll", <<'FINIS' ) ? 1 : 0;
+#include <sys/epoll.h>
+int main(void)
+{
+       epoll_create(256);
+       return 0;
+}
+FINIS
+});
+
 PostConfig {
        AtWrite {
                UCW::Configure::C::ConfigHeader("ucw/autoconf.h", [