ソースを参照

migrating main parts of event loops

Christian Grothoff 8 年 前
コミット
78dc7915f8

+ 35 - 0
src/include/microhttpd2.h

@@ -343,6 +343,11 @@ enum MHD_StatusCode
    */
   MHD_SC_DAEMON_STARTED = 10000,
 
+  /**
+   * Informational event, there is no timeout.
+   */
+  MHD_SC_NO_TIMEOUT = 10001,
+
 
   /**
    * MHD does not support the requested combination of
@@ -563,6 +568,36 @@ enum MHD_StatusCode
    */
   MHD_SC_CONFIGURATION_MISSMATCH_FOR_GET_FDSET = 50037,
 
+  /**
+   * This daemon was not configured with options that
+   * would allow us to obtain a meaningful timeout.
+   */
+  MHD_SC_CONFIGURATION_MISSMATCH_FOR_GET_TIMEOUT = 50038,
+
+  /**
+   * This daemon was not configured with options that
+   * would allow us to run with select() data.
+   */
+  MHD_SC_CONFIGURATION_MISSMATCH_FOR_RUN_SELECT = 50039,
+
+  /**
+   * This daemon was not configured to run with an
+   * external event loop.
+   */
+  MHD_SC_CONFIGURATION_MISSMATCH_FOR_RUN_EXTERNAL = 50040,
+
+  /**
+   * Encountered an unexpected event loop style 
+   * (should never happen).
+   */
+  MHD_SC_CONFIGURATION_UNEXPECTED_ELS = 50041,
+
+  /**
+   * Encountered an unexpected error from select()
+   * (should never happen).
+   */
+  MHD_SC_CONFIGURATION_UNEXPECTED_SELECT_ERROR = 50042,
+
 };
 
 

+ 3 - 2
src/lib/Makefile.am

@@ -63,12 +63,13 @@ libmicrohttpd_la_SOURCES = \
   daemon_add_connection.c \
   daemon_create.c \
   daemon_destroy.c \
-  daemon_get_fdset.c \
+  daemon_epoll.c daemon_epoll.h \
   daemon_get_timeout.c \
   daemon_info.c \
   daemon_options.c \
+  daemon_poll.c daemon_poll.h \
   daemon_run.c \
-  daemon_run_from_select.c \
+  daemon_select.c daemon_select.h \
   daemon_start.c \
   daemon_quiesce.c \
   init.c init.h \

+ 504 - 0
src/lib/daemon_epoll.c

@@ -0,0 +1,504 @@
+/*
+  This file is part of libmicrohttpd
+  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+/**
+ * @file lib/daemon_epoll.c
+ * @brief functions to run epoll()-based event loop
+ * @author Christian Grothoff
+ */
+#include "internal.h"
+#include "daemon_epoll.h"
+
+#ifdef EPOLL_SUPPORT
+
+/**
+ * How many events to we process at most per epoll() call?  Trade-off
+ * between required stack-size and number of system calls we have to
+ * make; 128 should be way enough to avoid more than one system call
+ * for most scenarios, and still be moderate in stack size
+ * consumption.  Embedded systems might want to choose a smaller value
+ * --- but why use epoll() on such a system in the first place?
+ */
+#define MAX_EVENTS 128
+
+
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+
+/**
+ * Checks whether @a urh has some data to process.
+ *
+ * @param urh upgrade handler to analyse
+ * @return 'true' if @a urh has some data to process,
+ *         'false' otherwise
+ */
+static bool
+is_urh_ready (struct MHD_UpgradeResponseHandle * const urh)
+{
+  const struct MHD_Connection * const connection = urh->connection;
+
+  if ( (0 == urh->in_buffer_size) &&
+       (0 == urh->out_buffer_size) &&
+       (0 == urh->in_buffer_used) &&
+       (0 == urh->out_buffer_used) )
+    return false;
+
+  if (connection->daemon->shutdown)
+    return true;
+
+  if ( ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->app.celi)) ||
+         (connection->tls_read_ready) ) &&
+       (urh->in_buffer_used < urh->in_buffer_size) )
+    return true;
+
+  if ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->mhd.celi)) &&
+       (urh->out_buffer_used < urh->out_buffer_size) )
+    return true;
+
+  if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->app.celi)) &&
+       (urh->out_buffer_used > 0) )
+    return true;
+
+  if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi)) &&
+         (urh->in_buffer_used > 0) )
+    return true;
+
+  return false;
+}
+
+
+/**
+ * Do epoll()-based processing for TLS connections that have been
+ * upgraded.  This requires a separate epoll() invocation as we
+ * cannot use the `struct MHD_Connection` data structures for
+ * the `union epoll_data` in this case.
+ * @remark To be called only from thread that process
+ * daemon's select()/poll()/etc.
+ *
+ * @param daemon the daemmon for which we process connections
+ * @return #MHD_SC_OK on success
+ */
+static enum MHD_StatusCode
+run_epoll_for_upgrade (struct MHD_Daemon *daemon)
+{
+  struct epoll_event events[MAX_EVENTS];
+  int num_events;
+  struct MHD_UpgradeResponseHandle * pos;
+  struct MHD_UpgradeResponseHandle * prev;
+
+  num_events = MAX_EVENTS;
+  while (MAX_EVENTS == num_events)
+    {
+      unsigned int i;
+      
+      /* update event masks */
+      num_events = epoll_wait (daemon->epoll_upgrade_fd,
+			       events,
+                               MAX_EVENTS,
+                               0);
+      if (-1 == num_events)
+	{
+          const int err = MHD_socket_get_error_ ();
+          if (MHD_SCKT_ERR_IS_EINTR_ (err))
+	    return MHD_SC_OK;
+#ifdef HAVE_MESSAGES
+          MHD_DLOG (daemon,
+		    MHD_SC_UNEXPECTED_EPOLL_WAIT_ERROR,
+                    _("Call to epoll_wait failed: %s\n"),
+                    MHD_socket_strerr_ (err));
+#endif
+	  return MHD_SC_UNEXPECTED_EPOLL_WAIT_ERROR;
+	}
+      for (i = 0; i < (unsigned int) num_events; i++)
+	{
+          struct UpgradeEpollHandle * const ueh = events[i].data.ptr;
+          struct MHD_UpgradeResponseHandle * const urh = ueh->urh;
+          bool new_err_state = false;
+
+          if (urh->clean_ready)
+            continue;
+
+          /* Update ueh state based on what is ready according to epoll() */
+          if (0 != (events[i].events & EPOLLIN))
+            ueh->celi |= MHD_EPOLL_STATE_READ_READY;
+          if (0 != (events[i].events & EPOLLOUT))
+            ueh->celi |= MHD_EPOLL_STATE_WRITE_READY;
+          if (0 != (events[i].events & EPOLLHUP))
+            ueh->celi |= MHD_EPOLL_STATE_READ_READY | MHD_EPOLL_STATE_WRITE_READY;
+
+          if ( (0 == (ueh->celi & MHD_EPOLL_STATE_ERROR)) &&
+               (0 != (events[i].events & (EPOLLERR | EPOLLPRI))) )
+	    {
+              /* Process new error state only one time
+               * and avoid continuously marking this connection
+               * as 'ready'. */
+              ueh->celi |= MHD_EPOLL_STATE_ERROR;
+              new_err_state = true;
+	    }
+
+          if (! urh->in_eready_list)
+            {
+              if (new_err_state ||
+        	  is_urh_ready(urh))
+        	{
+        	  EDLL_insert (daemon->eready_urh_head,
+			       daemon->eready_urh_tail,
+			       urh);
+        	  urh->in_eready_list = true;
+        	}
+            }
+        }
+    }
+  prev = daemon->eready_urh_tail;
+  while (NULL != (pos = prev))
+    {
+      prev = pos->prevE;
+      process_urh (pos);
+      if (! is_urh_ready(pos))
+      	{
+      	  EDLL_remove (daemon->eready_urh_head,
+      		       daemon->eready_urh_tail,
+      		       pos);
+      	  pos->in_eready_list = false;
+      	}
+      /* Finished forwarding? */
+      if ( (0 == pos->in_buffer_size) &&
+           (0 == pos->out_buffer_size) &&
+           (0 == pos->in_buffer_used) &&
+           (0 == pos->out_buffer_used) )
+        {
+          MHD_connection_finish_forward_ (pos->connection);
+          pos->clean_ready = true;
+          /* If 'pos->was_closed' already was set to true, connection
+           * will be moved immediately to cleanup list. Otherwise
+           * connection will stay in suspended list until 'pos' will
+           * be marked with 'was_closed' by application. */
+          MHD_resume_connection (pos->connection);
+        }
+    }
+
+  return MHD_SC_OK;
+}
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+
+
+/**
+ * Do epoll()-based processing (this function is allowed to
+ * block if @a may_block is set to #MHD_YES).
+ *
+ * @param daemon daemon to run poll loop for
+ * @param may_block true if blocking, false if non-blocking
+ * @return #MHD_SC_OK on success
+ */
+enum MHD_StatusCode
+MHD_daemon_epoll_ (struct MHD_Daemon *daemon,
+		   bool may_block)
+{
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+  static const char * const upgrade_marker = "upgrade_ptr";
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+  struct MHD_Connection *pos;
+  struct MHD_Connection *prev;
+  struct epoll_event events[MAX_EVENTS];
+  struct epoll_event event;
+  int timeout_ms;
+  MHD_UNSIGNED_LONG_LONG timeout_ll;
+  int num_events;
+  unsigned int i;
+  MHD_socket ls;
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+  bool run_upgraded = false;
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+
+  if (-1 == daemon->epoll_fd)
+    return MHD_SC_EPOLL_FD_INVALID; /* we're down! */
+  if (daemon->shutdown)
+    return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
+  if ( (MHD_INVALID_SOCKET != (ls = daemon->listen_socket)) &&
+       (! daemon->was_quiesced) &&
+       (daemon->connections < daemon->global_connection_limit) &&
+       (! daemon->listen_socket_in_epoll) &&
+       (! daemon->at_limit) )
+    {
+      event.events = EPOLLIN;
+      event.data.ptr = daemon;
+      if (0 != epoll_ctl (daemon->epoll_fd,
+			  EPOLL_CTL_ADD,
+			  ls,
+			  &event))
+	{
+#ifdef HAVE_MESSAGES
+          MHD_DLOG (daemon,
+		    MHD_SC_EPOLL_CTL_ADD_FAILED,
+                    _("Call to epoll_ctl failed: %s\n"),
+                    MHD_socket_last_strerr_ ());
+#endif
+	  return MHD_SC_EPOLL_CTL_ADD_FAILED;
+	}
+      daemon->listen_socket_in_epoll = true;
+    }
+  if ( (daemon->was_quiesced) &&
+       (daemon->listen_socket_in_epoll) )
+  {
+    if (0 != epoll_ctl (daemon->epoll_fd,
+                        EPOLL_CTL_DEL,
+                        ls,
+                        NULL))
+      MHD_PANIC ("Failed to remove listen FD from epoll set\n");
+    daemon->listen_socket_in_epoll = false;
+  }
+
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+  if ( (! daemon->upgrade_fd_in_epoll) &&
+       (-1 != daemon->epoll_upgrade_fd) )
+    {
+      event.events = EPOLLIN | EPOLLOUT;
+      event.data.ptr = (void *) upgrade_marker;
+      if (0 != epoll_ctl (daemon->epoll_fd,
+			  EPOLL_CTL_ADD,
+			  daemon->epoll_upgrade_fd,
+			  &event))
+	{
+#ifdef HAVE_MESSAGES
+          MHD_DLOG (daemon,
+		    MHD_SC_EPOLL_CTL_ADD_FAILED,
+                    _("Call to epoll_ctl failed: %s\n"),
+                    MHD_socket_last_strerr_ ());
+#endif
+	  return MHD_SC_EPOLL_CTL_ADD_FAILED;
+	}
+      daemon->upgrade_fd_in_epoll = true;
+    }
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+  if ( (daemon->listen_socket_in_epoll) &&
+       ( (daemon->connections == daemon->global_connection_limit) ||
+         (daemon->at_limit) ||
+         (daemon->was_quiesced) ) )
+    {
+      /* we're at the connection limit, disable listen socket
+	 for event loop for now */
+      if (0 != epoll_ctl (daemon->epoll_fd,
+			  EPOLL_CTL_DEL,
+			  ls,
+			  NULL))
+	MHD_PANIC (_("Failed to remove listen FD from epoll set\n"));
+      daemon->listen_socket_in_epoll = false;
+    }
+
+  if ( (! daemon->disallow_suspend_resume) &&
+       (MHD_YES == resume_suspended_connections (daemon)) )
+    may_block = false;
+
+  if (may_block)
+    {
+      if (MHD_SC_OK == /* FIXME: distinguish between NO_TIMEOUT and errors */
+	  MHD_daemon_get_timeout (daemon,
+				  &timeout_ll))
+	{
+	  if (timeout_ll >= (MHD_UNSIGNED_LONG_LONG) INT_MAX)
+	    timeout_ms = INT_MAX;
+	  else
+	    timeout_ms = (int) timeout_ll;
+	}
+      else
+	timeout_ms = -1;
+    }
+  else
+    timeout_ms = 0;
+
+  /* Reset. New value will be set when connections are processed. */
+  /* Note: Used mostly for uniformity here as same situation is
+   * signaled in epoll mode by non-empty eready DLL. */
+  daemon->data_already_pending = false;
+
+  /* drain 'epoll' event queue; need to iterate as we get at most
+     MAX_EVENTS in one system call here; in practice this should
+     pretty much mean only one round, but better an extra loop here
+     than unfair behavior... */
+  num_events = MAX_EVENTS;
+  while (MAX_EVENTS == num_events)
+    {
+      /* update event masks */
+      num_events = epoll_wait (daemon->epoll_fd,
+			       events,
+                               MAX_EVENTS,
+                               timeout_ms);
+      if (-1 == num_events)
+	{
+          const int err = MHD_socket_get_error_ ();
+          if (MHD_SCKT_ERR_IS_EINTR_ (err))
+	    return MHD_SC_OK;
+#ifdef HAVE_MESSAGES
+          MHD_DLOG (daemon,
+		    MHD_SC_UNEXPECTED_EPOLL_WAIT_ERROR,
+                    _("Call to epoll_wait failed: %s\n"),
+                    MHD_socket_strerr_ (err));
+#endif
+	  return MHD_SC_UNEXPECTED_EPOLL_WAIT_ERROR;
+	}
+      for (i=0;i<(unsigned int) num_events;i++)
+	{
+          /* First, check for the values of `ptr` that would indicate
+             that this event is not about a normal connection. */
+	  if (NULL == events[i].data.ptr)
+	    continue; /* shutdown signal! */
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+          if (upgrade_marker == events[i].data.ptr)
+            {
+              /* activity on an upgraded connection, we process
+                 those in a separate epoll() */
+              run_upgraded = true;
+              continue;
+            }
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+          if (daemon->epoll_itc_marker == events[i].data.ptr)
+            {
+              /* It's OK to clear ITC here as all external
+                 conditions will be processed later. */
+              MHD_itc_clear_ (daemon->itc);
+              continue;
+            }
+	  if (daemon == events[i].data.ptr)
+	    {
+              /* Check for error conditions on listen socket. */
+              /* FIXME: Initiate MHD_quiesce_daemon() to prevent busy waiting? */
+              if (0 == (events[i].events & (EPOLLERR | EPOLLHUP)))
+                {
+                  unsigned int series_length = 0;
+                  /* Run 'accept' until it fails or daemon at limit of connections.
+                   * Do not accept more then 10 connections at once. The rest will
+                   * be accepted on next turn (level trigger is used for listen
+                   * socket). */
+                  while ( (MHD_YES ==
+			   MHD_accept_connection (daemon)) &&
+                          (series_length < 10) &&
+                          (daemon->connections < daemon->global_connection_limit) &&
+                          (! daemon->at_limit) )
+                    series_length++;
+	        }
+              continue;
+	    }
+          /* this is an event relating to a 'normal' connection,
+             remember the event and if appropriate mark the
+             connection as 'eready'. */
+          pos = events[i].data.ptr;
+          /* normal processing: update read/write data */
+          if (0 != (events[i].events & (EPOLLPRI | EPOLLERR | EPOLLHUP)))
+            {
+              pos->epoll_state |= MHD_EPOLL_STATE_ERROR;
+              if (0 == (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL))
+                {
+                  EDLL_insert (daemon->eready_head,
+                               daemon->eready_tail,
+                               pos);
+                  pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
+                }
+            }
+          else
+            {
+              if (0 != (events[i].events & EPOLLIN))
+                {
+                  pos->epoll_state |= MHD_EPOLL_STATE_READ_READY;
+                  if ( ( (MHD_EVENT_LOOP_INFO_READ == pos->request.event_loop_info) ||
+                         (pos->request.read_buffer_size > pos->request.read_buffer_offset) ) &&
+                       (0 == (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) ) )
+                    {
+                      EDLL_insert (daemon->eready_head,
+                                   daemon->eready_tail,
+                                   pos);
+                      pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
+                    }
+                }
+              if (0 != (events[i].events & EPOLLOUT))
+                {
+                  pos->epoll_state |= MHD_EPOLL_STATE_WRITE_READY;
+                  if ( (MHD_EVENT_LOOP_INFO_WRITE == pos->request.event_loop_info) &&
+                       (0 == (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) ) )
+                    {
+                      EDLL_insert (daemon->eready_head,
+                                   daemon->eready_tail,
+                                   pos);
+                      pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
+                    }
+                }
+            }
+        }
+    }
+
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+  if (run_upgraded)
+    run_epoll_for_upgrade (daemon); /* FIXME: return value? */
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+
+  /* process events for connections */
+  prev = daemon->eready_tail;
+  while (NULL != (pos = prev))
+    {
+      prev = pos->prevE;
+      call_handlers (pos,
+                     0 != (pos->epoll_state & MHD_EPOLL_STATE_READ_READY),
+                     0 != (pos->epoll_state & MHD_EPOLL_STATE_WRITE_READY),
+                     0 != (pos->epoll_state & MHD_EPOLL_STATE_ERROR));
+      if (MHD_EPOLL_STATE_IN_EREADY_EDLL ==
+            (pos->epoll_state & (MHD_EPOLL_STATE_SUSPENDED | MHD_EPOLL_STATE_IN_EREADY_EDLL)))
+        {
+          if ( (MHD_EVENT_LOOP_INFO_READ == pos->request.event_loop_info &&
+		0 == (pos->epoll_state & MHD_EPOLL_STATE_READ_READY) ) ||
+               (MHD_EVENT_LOOP_INFO_WRITE == pos->request.event_loop_info &&
+		0 == (pos->epoll_state & MHD_EPOLL_STATE_WRITE_READY) ) ||
+               MHD_EVENT_LOOP_INFO_CLEANUP == pos->request.event_loop_info)
+            {
+              EDLL_remove (daemon->eready_head,
+                           daemon->eready_tail,
+                           pos);
+              pos->epoll_state &= ~MHD_EPOLL_STATE_IN_EREADY_EDLL;
+            }
+        }
+    }
+
+  /* Finally, handle timed-out connections; we need to do this here
+     as the epoll mechanism won't call the 'MHD_connection_handle_idle()' on everything,
+     as the other event loops do.  As timeouts do not get an explicit
+     event, we need to find those connections that might have timed out
+     here.
+
+     Connections with custom timeouts must all be looked at, as we
+     do not bother to sort that (presumably very short) list. */
+  prev = daemon->manual_timeout_tail;
+  while (NULL != (pos = prev))
+    {
+      prev = pos->prevX;
+      MHD_connection_handle_idle (pos);
+    }
+  /* Connections with the default timeout are sorted by prepending
+     them to the head of the list whenever we touch the connection;
+     thus it suffices to iterate from the tail until the first
+     connection is NOT timed out */
+  prev = daemon->normal_timeout_tail;
+  while (NULL != (pos = prev))
+    {
+      prev = pos->prevX;
+      MHD_connection_handle_idle (pos);
+      if (MHD_REQUEST_CLOSED != pos->request.state)
+	break; /* sorted by timeout, no need to visit the rest! */
+    }
+  return MHD_SC_OK;
+}
+#endif
+
+/* end of daemon_epoll.c */

+ 46 - 0
src/lib/daemon_epoll.h

@@ -0,0 +1,46 @@
+/*
+  This file is part of libmicrohttpd
+  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+/**
+ * @file lib/daemon_epoll.h
+ * @brief  non-public functions provided by daemon_epoll.c
+ * @author Christian Grothoff
+ */
+
+#ifndef DAEMON_EPOLL_H
+#define DAEMON_EPOLL_H
+
+#ifdef EPOLL_SUPPORT
+
+/**
+ * Do epoll()-based processing (this function is allowed to
+ * block if @a may_block is set to #MHD_YES).
+ *
+ * @param daemon daemon to run poll loop for
+ * @param may_block true if blocking, false if non-blocking
+ * @return #MHD_SC_OK on success
+ */
+enum MHD_StatusCode
+MHD_daemon_epoll_ (struct MHD_Daemon *daemon,
+		   bool may_block)
+  MHD_NONNULL (1);
+
+#endif
+
+#endif

+ 0 - 269
src/lib/daemon_get_fdset.c

@@ -1,269 +0,0 @@
-/*
-  This file is part of libmicrohttpd
-  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
-
-  This library is free software; you can redistribute it and/or
-  modify it under the terms of the GNU Lesser General Public
-  License as published by the Free Software Foundation; either
-  version 2.1 of the License, or (at your option) any later version.
-
-  This library is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public
-  License along with this library; if not, write to the Free Software
-  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
-*/
-
-/**
- * @file lib/daemon_get_fdset.c
- * @brief function to get select() fdset of a daemon
- * @author Christian Grothoff
- */
-#include "internal.h"
-
-/**
- * We defined a macro with the same name as a function we
- * are implementing here. Need to undef the macro to avoid
- * causing a conflict.
- */
-#undef MHD_daemon_get_fdset
-
-/**
- * Obtain the `select()` sets for this daemon.  Daemon's FDs will be
- * added to fd_sets. To get only daemon FDs in fd_sets, call FD_ZERO
- * for each fd_set before calling this function. FD_SETSIZE is assumed
- * to be platform's default.
- *
- * This function should only be called in when MHD is configured to
- * use external select with 'select()' or with 'epoll'.  In the latter
- * case, it will only add the single 'epoll()' file descriptor used by
- * MHD to the sets.  It's necessary to use #MHD_get_timeout() in
- * combination with this function.
- *
- * This function must be called only for daemon started without
- * #MHD_USE_INTERNAL_POLLING_THREAD flag.
- *
- * @param daemon daemon to get sets from
- * @param read_fd_set read set
- * @param write_fd_set write set
- * @param except_fd_set except set
- * @param max_fd increased to largest FD added (if larger
- *               than existing value); can be NULL
- * @return #MHD_SC_OK on success, otherwise error code
- * @ingroup event
- */
-enum MHD_StatusCode
-MHD_daemon_get_fdset (struct MHD_Daemon *daemon,
-		      fd_set *read_fd_set,
-		      fd_set *write_fd_set,
-		      fd_set *except_fd_set,
-		      MHD_socket *max_fd)
-{
-  return MHD_daemon_get_fdset2 (daemon,
-				read_fd_set,
-				write_fd_set,
-				except_fd_set,
-				max_fd,
-				_MHD_SYS_DEFAULT_FD_SETSIZE);
-}
-
-
-/**
- * Internal version of #MHD_daemon_get_fdset2().
- *
- * @param daemon daemon to get sets from
- * @param read_fd_set read set
- * @param write_fd_set write set
- * @param except_fd_set except set
- * @param max_fd increased to largest FD added (if larger
- *               than existing value); can be NULL
- * @param fd_setsize value of FD_SETSIZE
- * @return #MHD_SC_OK on success
- * @ingroup event
- */
-static enum MHD_StatusCode
-internal_get_fdset2 (struct MHD_Daemon *daemon,
-                     fd_set *read_fd_set,
-                     fd_set *write_fd_set,
-                     fd_set *except_fd_set,
-                     MHD_socket *max_fd,
-                     unsigned int fd_setsize)
-
-{
-  struct MHD_Connection *pos;
-  struct MHD_Connection *posn;
-  int result = MHD_YES;
-  MHD_socket ls;
-
-  if (daemon->shutdown)
-    return MHD_NO;
-
-  ls = daemon->listen_socket;
-  if ( (MHD_INVALID_SOCKET != ls) &&
-       (! daemon->was_quiesced) &&
-       (! MHD_add_to_fd_set_ (ls,
-                              read_fd_set,
-                              max_fd,
-                              fd_setsize)) )
-    result = MHD_NO;
-
-  /* Add all sockets to 'except_fd_set' as well to watch for
-   * out-of-band data. However, ignore errors if INFO_READ
-   * or INFO_WRITE sockets will not fit 'except_fd_set'. */
-  /* Start from oldest connections. Make sense for W32 FDSETs. */
-  for (pos = daemon->connections_tail; NULL != pos; pos = posn)
-    {
-      posn = pos->prev;
-
-      switch (pos->request.event_loop_info)
-	{
-	case MHD_EVENT_LOOP_INFO_READ:
-	  if (! MHD_add_to_fd_set_ (pos->socket_fd,
-                                    read_fd_set,
-                                    max_fd,
-                                    fd_setsize))
-	    result = MHD_NO;
-#ifdef MHD_POSIX_SOCKETS
-          MHD_add_to_fd_set_ (pos->socket_fd,
-                              except_fd_set,
-                              max_fd,
-                              fd_setsize);
-#endif /* MHD_POSIX_SOCKETS */
-	  break;
-	case MHD_EVENT_LOOP_INFO_WRITE:
-	  if (! MHD_add_to_fd_set_ (pos->socket_fd,
-                                    write_fd_set,
-                                    max_fd,
-                                    fd_setsize))
-	    result = MHD_NO;
-#ifdef MHD_POSIX_SOCKETS
-          MHD_add_to_fd_set_ (pos->socket_fd,
-                              except_fd_set,
-                              max_fd,
-                              fd_setsize);
-#endif /* MHD_POSIX_SOCKETS */
-	  break;
-	case MHD_EVENT_LOOP_INFO_BLOCK:
-	  if ( (NULL == except_fd_set) ||
-	      ! MHD_add_to_fd_set_ (pos->socket_fd,
-	                            except_fd_set,
-                                    max_fd,
-                                    fd_setsize))
-            result = MHD_NO;
-	  break;
-	case MHD_EVENT_LOOP_INFO_CLEANUP:
-	  /* this should never happen */
-	  break;
-	}
-    }
-#ifdef MHD_WINSOCK_SOCKETS
-  /* W32 use limited array for fd_set so add INFO_READ/INFO_WRITE sockets
-   * only after INFO_BLOCK sockets to ensure that INFO_BLOCK sockets will
-   * not be pushed out. */
-  for (pos = daemon->connections_tail; NULL != pos; pos = posn)
-    {
-      posn = pos->prev;
-      MHD_add_to_fd_set_ (pos->socket_fd,
-                          except_fd_set,
-                          max_fd,
-                          fd_setsize);
-    }
-#endif /* MHD_WINSOCK_SOCKETS */
-#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
-  {
-    struct MHD_UpgradeResponseHandle *urh;
-
-    for (urh = daemon->urh_tail; NULL != urh; urh = urh->prev)
-      {
-        if (MHD_NO ==
-            urh_to_fdset (urh,
-                          read_fd_set,
-                          write_fd_set,
-                          except_fd_set,
-                          max_fd,
-                          fd_setsize))
-          result = MHD_NO;
-      }
-  }
-#endif
-#if DEBUG_CONNECT
-#ifdef HAVE_MESSAGES
-  if (NULL != max_fd)
-    MHD_DLOG (daemon,
-              _("Maximum socket in select set: %d\n"),
-              *max_fd);
-#endif
-#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
-  return result;
-}
-
-
-/**
- * Obtain the `select()` sets for this daemon.  Daemon's FDs will be
- * added to fd_sets. To get only daemon FDs in fd_sets, call FD_ZERO
- * for each fd_set before calling this function.
- *
- * Passing custom FD_SETSIZE as @a fd_setsize allow usage of
- * larger/smaller than platform's default fd_sets.
- *
- * This function should only be called in when MHD is configured to
- * use external select with 'select()' or with 'epoll'.  In the latter
- * case, it will only add the single 'epoll' file descriptor used by
- * MHD to the sets.  It's necessary to use #MHD_get_timeout() in
- * combination with this function.
- *
- * This function must be called only for daemon started
- * without #MHD_USE_INTERNAL_POLLING_THREAD flag.
- *
- * @param daemon daemon to get sets from
- * @param read_fd_set read set
- * @param write_fd_set write set
- * @param except_fd_set except set
- * @param max_fd increased to largest FD added (if larger
- *               than existing value); can be NULL
- * @param fd_setsize value of FD_SETSIZE
- * @return #MHD_SC_OK on success, otherwise error code
- * @ingroup event
- */
-enum MHD_StatusCode
-MHD_daemon_get_fdset2 (struct MHD_Daemon *daemon,
-		       fd_set *read_fd_set,
-		       fd_set *write_fd_set,
-		       fd_set *except_fd_set,
-		       MHD_socket *max_fd,
-		       unsigned int fd_setsize)
-{
-  if ( (MHD_TM_EXTERNAL_EVENT_LOOP != daemon->threading_model) ||
-       (MHD_ELS_POLL == daemon->event_loop_syscall) ) 
-    return MHD_SC_CONFIGURATION_MISSMATCH_FOR_GET_FDSET;
-
-#ifdef EPOLL_SUPPORT
-  if (MHD_ELS_EPOLL == daemon->event_loop_syscall)
-    {
-      if (daemon->shutdown)
-        return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
-
-      /* we're in epoll mode, use the epoll FD as a stand-in for
-         the entire event set */
-
-      return MHD_add_to_fd_set_ (daemon->epoll_fd,
-                                 read_fd_set,
-                                 max_fd,
-                                 fd_setsize)
-	? MHD_SC_OK
-	: MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE;
-    }
-#endif
-
-  return internal_get_fdset2 (daemon,
-			      read_fd_set,
-                              write_fd_set,
-			      except_fd_set,
-                              max_fd,
-			      fd_setsize);
-}
-
-/* end of daemon_get_fdset.c */

+ 73 - 1
src/lib/daemon_get_timeout.c

@@ -48,7 +48,79 @@ enum MHD_StatusCode
 MHD_daemon_get_timeout (struct MHD_Daemon *daemon,
 			MHD_UNSIGNED_LONG_LONG *timeout)
 {
-  return -1;
+  time_t earliest_deadline;
+  time_t now;
+  struct MHD_Connection *pos;
+  bool have_timeout;
+
+  if (MHD_TM_EXTERNAL_EVENT_LOOP != daemon->threading_model)
+    {
+#ifdef HAVE_MESSAGES
+      MHD_DLOG (daemon,
+		MHD_SC_CONFIGURATION_MISSMATCH_FOR_GET_TIMEOUT,
+                _("Illegal call to MHD_get_timeout\n"));
+#endif
+      return MHD_SC_CONFIGURATION_MISSMATCH_FOR_GET_TIMEOUT;
+    }
+
+  if (daemon->data_already_pending)
+    {
+      /* Some data already waiting to be processed. */
+      *timeout = 0;
+      return MHD_SC_OK;
+    }
+
+#ifdef EPOLL_SUPPORT
+  if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) &&
+       ((NULL != daemon->eready_head)
+#if defined(UPGRADE_SUPPORT) && defined(HTTPS_SUPPORT)
+	 || (NULL != daemon->eready_urh_head)
+#endif /* UPGRADE_SUPPORT && HTTPS_SUPPORT */
+	 ) )
+    {
+	  /* Some connection(s) already have some data pending. */
+      *timeout = 0;
+      return MHD_SC_OK;
+    }
+#endif /* EPOLL_SUPPORT */
+
+  have_timeout = false;
+  earliest_deadline = 0; /* avoid compiler warnings */
+  for (pos = daemon->manual_timeout_tail; NULL != pos; pos = pos->prevX)
+    {
+      if (0 != pos->connection_timeout)
+	{
+	  if ( (! have_timeout) ||
+	       (earliest_deadline - pos->last_activity > pos->connection_timeout) )
+	    earliest_deadline = pos->last_activity + pos->connection_timeout;
+	  have_timeout = true;
+	}
+    }
+  /* normal timeouts are sorted, so we only need to look at the 'tail' (oldest) */
+  pos = daemon->normal_timeout_tail;
+  if ( (NULL != pos) &&
+       (0 != pos->connection_timeout) )
+    {
+      if ( (! have_timeout) ||
+	   (earliest_deadline - pos->connection_timeout > pos->last_activity) )
+	earliest_deadline = pos->last_activity + pos->connection_timeout;
+      have_timeout = true;
+    }
+
+  if (! have_timeout)
+    return MHD_SC_NO_TIMEOUT;
+  now = MHD_monotonic_sec_counter();
+  if (earliest_deadline < now)
+    *timeout = 0;
+  else
+    {
+      const time_t second_left = earliest_deadline - now;
+      if (second_left > ULLONG_MAX / 1000) /* Ignore compiler warning: 'second_left' is always positive. */
+        *timeout = ULLONG_MAX;
+      else
+        *timeout = 1000LL * second_left;
+  }
+  return MHD_SC_OK;
 }
 
 /* end of daemon_get_timeout.c */

+ 453 - 0
src/lib/daemon_poll.c

@@ -0,0 +1,453 @@
+/*
+  This file is part of libmicrohttpd
+  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+/**
+ * @file lib/daemon_poll.c
+ * @brief functions to run poll-based event loop
+ * @author Christian Grothoff
+ */
+#include "internal.h"
+#include "daemon_poll.h"
+
+#ifdef HAVE_POLL
+
+
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+
+/**
+ * Set required 'event' members in 'pollfd' elements,
+ * assuming that @a p[0].fd is MHD side of socketpair
+ * and @a p[1].fd is TLS connected socket.
+ *
+ * @param urh upgrade handle to watch for
+ * @param p pollfd array to update
+ */
+static void
+urh_update_pollfd (struct MHD_UpgradeResponseHandle *urh,
+		   struct pollfd p[2])
+{
+  p[0].events = 0;
+  p[1].events = 0;
+
+  if (urh->in_buffer_used < urh->in_buffer_size)
+    p[0].events |= POLLIN;
+  if (0 != urh->out_buffer_used)
+    p[0].events |= POLLOUT;
+
+  /* Do not monitor again for errors if error was detected before as
+   * error state is remembered. */
+  if ((0 == (urh->app.celi & MHD_EPOLL_STATE_ERROR)) &&
+      ((0 != urh->in_buffer_size) ||
+       (0 != urh->out_buffer_size) ||
+       (0 != urh->out_buffer_used)))
+    p[0].events |= MHD_POLL_EVENTS_ERR_DISC;
+
+  if (urh->out_buffer_used < urh->out_buffer_size)
+    p[1].events |= POLLIN;
+  if (0 != urh->in_buffer_used)
+    p[1].events |= POLLOUT;
+
+  /* Do not monitor again for errors if error was detected before as
+   * error state is remembered. */
+  if ((0 == (urh->mhd.celi & MHD_EPOLL_STATE_ERROR)) &&
+      ((0 != urh->out_buffer_size) ||
+       (0 != urh->in_buffer_size) ||
+       (0 != urh->in_buffer_used)))
+    p[1].events |= MHD_POLL_EVENTS_ERR_DISC;
+}
+ 
+
+/**
+ * Set @a p to watch for @a urh.
+ *
+ * @param urh upgrade handle to watch for
+ * @param p pollfd array to set
+ */
+static void
+urh_to_pollfd (struct MHD_UpgradeResponseHandle *urh,
+	       struct pollfd p[2])
+{
+  p[0].fd = urh->connection->socket_fd;
+  p[1].fd = urh->mhd.socket;
+  urh_update_pollfd (urh,
+		     p);
+}
+
+
+/**
+ * Update ready state in @a urh based on pollfd.
+ * @param urh upgrade handle to update
+ * @param p 'poll()' processed pollfd.
+ */
+static void
+urh_from_pollfd (struct MHD_UpgradeResponseHandle *urh,
+		 struct pollfd p[2])
+{
+  /* Reset read/write ready, preserve error state. */
+  urh->app.celi &= (~MHD_EPOLL_STATE_READ_READY & ~MHD_EPOLL_STATE_WRITE_READY);
+  urh->mhd.celi &= (~MHD_EPOLL_STATE_READ_READY & ~MHD_EPOLL_STATE_WRITE_READY);
+
+  if (0 != (p[0].revents & POLLIN))
+    urh->app.celi |= MHD_EPOLL_STATE_READ_READY;
+  if (0 != (p[0].revents & POLLOUT))
+    urh->app.celi |= MHD_EPOLL_STATE_WRITE_READY;
+  if (0 != (p[0].revents & POLLHUP))
+    urh->app.celi |= MHD_EPOLL_STATE_READ_READY | MHD_EPOLL_STATE_WRITE_READY;
+  if (0 != (p[0].revents & MHD_POLL_REVENTS_ERRROR))
+    urh->app.celi |= MHD_EPOLL_STATE_ERROR;
+  if (0 != (p[1].revents & POLLIN))
+    urh->mhd.celi |= MHD_EPOLL_STATE_READ_READY;
+  if (0 != (p[1].revents & POLLOUT))
+    urh->mhd.celi |= MHD_EPOLL_STATE_WRITE_READY;
+  if (0 != (p[1].revents & POLLHUP))
+    urh->mhd.celi |= MHD_EPOLL_STATE_ERROR;
+  if (0 != (p[1].revents & MHD_POLL_REVENTS_ERRROR))
+    urh->mhd.celi |= MHD_EPOLL_STATE_READ_READY | MHD_EPOLL_STATE_WRITE_READY;
+}
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+
+
+/**
+ * Process all of our connections and possibly the server
+ * socket using poll().
+ *
+ * @param daemon daemon to run poll loop for
+ * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
+ * @return #MHD_SC_OK on success
+ */
+enum MHD_StatusCode
+MHD_daemon_poll_all_ (struct MHD_Daemon *daemon,
+		      bool may_block)
+{
+  unsigned int num_connections;
+  struct MHD_Connection *pos;
+  struct MHD_Connection *prev;
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+  struct MHD_UpgradeResponseHandle *urh;
+  struct MHD_UpgradeResponseHandle *urhn;
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+
+  if ( (! daemon->disallow_suspend_resume) &&
+       (MHD_YES == resume_suspended_connections (daemon)) )
+    may_block = false;
+
+  /* count number of connections and thus determine poll set size */
+  num_connections = 0;
+  for (pos = daemon->connections_head; NULL != pos; pos = pos->next)
+    num_connections++;
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+  for (urh = daemon->urh_head; NULL != urh; urh = urh->next)
+    num_connections += 2;
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+  {
+    MHD_UNSIGNED_LONG_LONG ltimeout;
+    unsigned int i;
+    int timeout;
+    unsigned int poll_server;
+    int poll_listen;
+    int poll_itc_idx;
+    struct pollfd *p;
+    MHD_socket ls;
+
+    p = MHD_calloc_ ((2 + num_connections),
+		     sizeof (struct pollfd));
+    if (NULL == p)
+      {
+#ifdef HAVE_MESSAGES
+        MHD_DLOG (daemon,
+		  MHD_SC_POLL_MALLOC_FAILURE,
+                  _("Error allocating memory: %s\n"),
+                  MHD_strerror_(errno));
+#endif
+        return MHD_SC_POLL_MALLOC_FAILURE;
+      }
+    poll_server = 0;
+    poll_listen = -1;
+    if ( (MHD_INVALID_SOCKET != (ls = daemon->listen_socket)) &&
+         (! daemon->was_quiesced) &&
+	 (daemon->connections < daemon->global_connection_limit) &&
+         (! daemon->at_limit) )
+      {
+	/* only listen if we are not at the connection limit */
+	p[poll_server].fd = ls;
+	p[poll_server].events = POLLIN;
+	p[poll_server].revents = 0;
+	poll_listen = (int) poll_server;
+	poll_server++;
+      }
+    poll_itc_idx = -1;
+    if (MHD_ITC_IS_VALID_(daemon->itc))
+      {
+	p[poll_server].fd = MHD_itc_r_fd_ (daemon->itc);
+	p[poll_server].events = POLLIN;
+	p[poll_server].revents = 0;
+        poll_itc_idx = (int) poll_server;
+	poll_server++;
+      }
+    if (! may_block)
+      timeout = 0;
+    else if ( (MHD_TM_THREAD_PER_CONNECTION == daemon->threading_model) ||
+	      (MHD_SC_OK != /* FIXME: distinguish between NO_TIMEOUT and errors! */
+	       MHD_daemon_get_timeout (daemon,
+				       &ltimeout)) )
+      timeout = -1;
+    else
+      timeout = (ltimeout > INT_MAX) ? INT_MAX : (int) ltimeout;
+
+    i = 0;
+    for (pos = daemon->connections_tail; NULL != pos; pos = pos->prev)
+      {
+	p[poll_server+i].fd = pos->socket_fd;
+	switch (pos->request.event_loop_info)
+	  {
+	  case MHD_EVENT_LOOP_INFO_READ:
+	    p[poll_server+i].events |= POLLIN | MHD_POLL_EVENTS_ERR_DISC;
+	    break;
+	  case MHD_EVENT_LOOP_INFO_WRITE:
+	    p[poll_server+i].events |= POLLOUT | MHD_POLL_EVENTS_ERR_DISC;
+	    break;
+	  case MHD_EVENT_LOOP_INFO_BLOCK:
+	    p[poll_server+i].events |=  MHD_POLL_EVENTS_ERR_DISC;
+	    break;
+	  case MHD_EVENT_LOOP_INFO_CLEANUP:
+	    timeout = 0; /* clean up "pos" immediately */
+	    break;
+	  }
+	i++;
+      }
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+    for (urh = daemon->urh_tail; NULL != urh; urh = urh->prev)
+      {
+        urh_to_pollfd (urh,
+		       &(p[poll_server+i]));
+        i += 2;
+      }
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+    if (0 == poll_server + num_connections)
+      {
+        free (p);
+        return MHD_SC_OK;
+      }
+    if (MHD_sys_poll_(p,
+                      poll_server + num_connections,
+                      timeout) < 0)
+      {
+        const int err = MHD_socket_get_error_ ();
+	if (MHD_SCKT_ERR_IS_EINTR_ (err))
+      {
+        free(p);
+        return MHD_SC_OK;
+      }
+#ifdef HAVE_MESSAGES
+	MHD_DLOG (daemon,
+		  MHD_SC_UNEXPECTED_POLL_ERROR,
+		  _("poll failed: %s\n"),
+		  MHD_socket_strerr_ (err));
+#endif
+        free(p);
+	return MHD_SC_UNEXPECTED_POLL_ERROR;
+      }
+
+    /* Reset. New value will be set when connections are processed. */
+    daemon->data_already_pending = false;
+
+    /* handle ITC FD */
+    /* do it before any other processing so
+       new signals will be processed in next loop */
+    if ( (-1 != poll_itc_idx) &&
+         (0 != (p[poll_itc_idx].revents & POLLIN)) )
+      MHD_itc_clear_ (daemon->itc);
+
+    /* handle shutdown */
+    if (daemon->shutdown)
+      {
+        free(p);
+        return MHD_NO;
+      }
+    i = 0;
+    prev = daemon->connections_tail;
+    while (NULL != (pos = prev))
+      {
+	prev = pos->prev;
+        /* first, sanity checks */
+        if (i >= num_connections)
+          break; /* connection list changed somehow, retry later ... */
+        if (p[poll_server+i].fd != pos->socket_fd)
+          continue; /* fd mismatch, something else happened, retry later ... */
+        call_handlers (pos,
+                       0 != (p[poll_server+i].revents & POLLIN),
+                       0 != (p[poll_server+i].revents & POLLOUT),
+                       0 != (p[poll_server+i].revents & MHD_POLL_REVENTS_ERR_DISC));
+        i++;
+      }
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+    for (urh = daemon->urh_tail; NULL != urh; urh = urhn)
+      {
+        if (i >= num_connections)
+          break; /* connection list changed somehow, retry later ... */
+
+        /* Get next connection here as connection can be removed
+         * from 'daemon->urh_head' list. */
+        urhn = urh->prev;
+        /* Check for fd mismatch. FIXME: required for safety? */
+        if ((p[poll_server+i].fd != urh->connection->socket_fd) ||
+            (p[poll_server+i+1].fd != urh->mhd.socket))
+          break;
+        urh_from_pollfd (urh,
+			 &(p[poll_server+i]));
+        i += 2;
+        process_urh (urh);
+        /* Finished forwarding? */
+        if ( (0 == urh->in_buffer_size) &&
+             (0 == urh->out_buffer_size) &&
+             (0 == urh->in_buffer_used) &&
+             (0 == urh->out_buffer_used) )
+          {
+            /* MHD_connection_finish_forward_() will remove connection from
+             * 'daemon->urh_head' list. */
+            MHD_connection_finish_forward_ (urh->connection);
+            urh->clean_ready = true;
+            /* If 'urh->was_closed' already was set to true, connection will be
+             * moved immediately to cleanup list. Otherwise connection
+             * will stay in suspended list until 'urh' will be marked
+             * with 'was_closed' by application. */
+            MHD_resume_connection(urh->connection);
+          }
+      }
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+    /* handle 'listen' FD */
+    if ( (-1 != poll_listen) &&
+	 (0 != (p[poll_listen].revents & POLLIN)) )
+      (void) MHD_accept_connection (daemon);
+
+    free(p);
+  }
+  return MHD_YES;
+}
+
+
+/**
+ * Process only the listen socket using poll().
+ *
+ * @param daemon daemon to run poll loop for
+ * @param may_block true if blocking, false if non-blocking
+ * @return #MHD_SC_OK on success
+ */
+enum MHD_StatusCode
+MHD_daemon_poll_listen_socket_ (struct MHD_Daemon *daemon,
+				bool may_block)
+{
+  struct pollfd p[2];
+  int timeout;
+  unsigned int poll_count;
+  int poll_listen;
+  int poll_itc_idx;
+  MHD_socket ls;
+
+  memset (&p,
+          0,
+          sizeof (p));
+  poll_count = 0;
+  poll_listen = -1;
+  poll_itc_idx = -1;
+  if ( (MHD_INVALID_SOCKET != (ls = daemon->listen_socket)) &&
+       (! daemon->was_quiesced) )
+
+    {
+      p[poll_count].fd = ls;
+      p[poll_count].events = POLLIN;
+      p[poll_count].revents = 0;
+      poll_listen = poll_count;
+      poll_count++;
+    }
+  if (MHD_ITC_IS_VALID_(daemon->itc))
+    {
+      p[poll_count].fd = MHD_itc_r_fd_ (daemon->itc);
+      p[poll_count].events = POLLIN;
+      p[poll_count].revents = 0;
+      poll_itc_idx = poll_count;
+      poll_count++;
+    }
+
+  if (! daemon->disallow_suspend_resume)
+    (void) resume_suspended_connections (daemon);
+
+  if (! may_block)
+    timeout = 0;
+  else
+    timeout = -1;
+  if (0 == poll_count)
+    return MHD_SC_OK;
+  if (MHD_sys_poll_(p,
+                    poll_count,
+                    timeout) < 0)
+    {
+      const int err = MHD_socket_get_error_ ();
+
+      if (MHD_SCKT_ERR_IS_EINTR_ (err))
+	return MHD_SC_OK;
+#ifdef HAVE_MESSAGES
+      MHD_DLOG (daemon,
+		MHD_SC_UNEXPECTED_POLL_ERROR,
+                _("poll failed: %s\n"),
+                MHD_socket_strerr_ (err));
+#endif
+      return MHD_SC_UNEXPECTED_POLL_ERROR;
+    }
+  if ( (-1 != poll_itc_idx) &&
+       (0 != (p[poll_itc_idx].revents & POLLIN)) )
+    MHD_itc_clear_ (daemon->itc);
+
+  /* handle shutdown */
+  if (daemon->shutdown)
+    return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
+  if ( (-1 != poll_listen) &&
+       (0 != (p[poll_listen].revents & POLLIN)) )
+    (void) MHD_accept_connection (daemon);
+  return MHD_SC_OK;
+}
+#endif
+
+
+/**
+ * Do poll()-based processing.
+ *
+ * @param daemon daemon to run poll()-loop for
+ * @param may_block true if blocking, false if non-blocking
+ * @return #MHD_SC_OK on success
+ */
+enum MHD_StatusCode
+MHD_daemon_poll_ (struct MHD_Daemon *daemon,
+		  bool may_block)
+{
+#ifdef HAVE_POLL
+  if (daemon->shutdown)
+    return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
+  if (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_model)
+    return MHD_daemon_poll_all_ (daemon,
+				 may_block);
+  return MHD_daemon_poll_listen_socket_ (daemon,
+					 may_block);
+#else
+  /* This code should be dead, as we should have checked
+     this earlier... */
+  return MHD_SC_POLL_NOT_SUPPORTED;
+#endif
+}
+
+/* end of daemon_poll.c */

+ 71 - 0
src/lib/daemon_poll.h

@@ -0,0 +1,71 @@
+/*
+  This file is part of libmicrohttpd
+  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+/**
+ * @file lib/daemon_poll.h
+ * @brief  non-public functions provided by daemon_poll.c
+ * @author Christian Grothoff
+ */
+#ifndef DAEMON_POLL_H
+#define DAEMON_POLL_H
+
+
+#ifdef HAVE_POLL
+/**
+ * Process all of our connections and possibly the server
+ * socket using poll().
+ *
+ * @param daemon daemon to run poll loop for
+ * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
+ * @return #MHD_SC_OK on success
+ */
+enum MHD_StatusCode
+MHD_daemon_poll_all_ (struct MHD_Daemon *daemon,
+		      bool may_block)
+  MHD_NONNULL(1);
+
+
+/**
+ * Process only the listen socket using poll().
+ *
+ * @param daemon daemon to run poll loop for
+ * @param may_block true if blocking, false if non-blocking
+ * @return #MHD_SC_OK on success
+ */
+enum MHD_StatusCode
+MHD_daemon_poll_listen_socket_ (struct MHD_Daemon *daemon,
+				bool may_block)
+  MHD_NONNULL (1);
+
+
+/**
+ * Do poll()-based processing.
+ *
+ * @param daemon daemon to run poll()-loop for
+ * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
+ * @return #MHD_SC_OK on success
+ */
+enum MHD_StatusCode
+MHD_daemon_poll_ (struct MHD_Daemon *daemon,
+		  bool may_block)
+  MHD_NONNULL (1);
+#endif
+
+
+#endif

+ 29 - 1
src/lib/daemon_run.c

@@ -23,6 +23,9 @@
  * @author Christian Grothoff
  */
 #include "internal.h"
+#include "daemon_epoll.h"
+#include "daemon_poll.h"
+#include "daemon_select.h"
 
 
 /**
@@ -46,7 +49,32 @@
 enum MHD_StatusCode
 MHD_daemon_run (struct MHD_Daemon *daemon)
 {
-  return -1;
+  if (daemon->shutdown)
+    return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
+  if (MHD_TM_EXTERNAL_EVENT_LOOP != daemon->threading_model)
+    return MHD_SC_CONFIGURATION_MISSMATCH_FOR_RUN_EXTERNAL;
+  switch (daemon->event_loop_syscall)
+    {
+    case MHD_ELS_POLL:
+      MHD_daemon_poll_ (daemon,
+	  	        MHD_NO);
+      MHD_cleanup_connections (daemon);
+      break;
+#ifdef EPOLL_SUPPORT
+    case MHD_ELS_EPOLL:
+      MHD_daemon_epoll_ (daemon,
+  		         MHD_NO);
+      MHD_cleanup_connections (daemon);
+      break;
+#endif
+    case MHD_ELS_SELECT:
+       MHD_daemon_select_ (daemon,
+			   MHD_NO);
+      /* MHD_select does MHD_cleanup_connections already */
+      break;
+    default:
+      return MHD_SC_CONFIGURATION_UNEXPECTED_ELS;
+    }
 }
 
 /* end of daemon_run.c */

+ 0 - 59
src/lib/daemon_run_from_select.c

@@ -1,59 +0,0 @@
-/*
-  This file is part of libmicrohttpd
-  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
-
-  This library is free software; you can redistribute it and/or
-  modify it under the terms of the GNU Lesser General Public
-  License as published by the Free Software Foundation; either
-  version 2.1 of the License, or (at your option) any later version.
-
-  This library is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public
-  License along with this library; if not, write to the Free Software
-  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
-*/
-
-/**
- * @file lib/daemon_run_from_select.c
- * @brief functions to run select-based event loop
- * @author Christian Grothoff
- */
-#include "internal.h"
-
-
-/**
- * Run webserver operations. This method should be called by clients
- * in combination with #MHD_get_fdset and #MHD_get_timeout() if the
- * client-controlled select method is used.
- *
- * You can use this function instead of #MHD_run if you called
- * `select()` on the result from #MHD_get_fdset.  File descriptors in
- * the sets that are not controlled by MHD will be ignored.  Calling
- * this function instead of #MHD_run is more efficient as MHD will not
- * have to call `select()` again to determine which operations are
- * ready.
- *
- * This function cannot be used with daemon started with
- * #MHD_USE_INTERNAL_POLLING_THREAD flag.
- *
- * @param daemon daemon to run select loop for
- * @param read_fd_set read set
- * @param write_fd_set write set
- * @param except_fd_set except set
- * @return #MHD_SC_OK on success
- * @ingroup event
- */
-enum MHD_StatusCode
-MHD_daemon_run_from_select (struct MHD_Daemon *daemon,
-			    const fd_set *read_fd_set,
-			    const fd_set *write_fd_set,
-			    const fd_set *except_fd_set)
-{
-  return -1;
-}
-
-/* end of daemon_run_from_select.c */

+ 711 - 0
src/lib/daemon_select.c

@@ -0,0 +1,711 @@
+/*
+  This file is part of libmicrohttpd
+  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+/**
+ * @file lib/daemon_select.c
+ * @brief function to run select()-based event loop of a daemon
+ * @author Christian Grothoff
+ */
+#include "internal.h"
+#include "daemon_select.h"
+
+/**
+ * We defined a macro with the same name as a function we
+ * are implementing here. Need to undef the macro to avoid
+ * causing a conflict.
+ */
+#undef MHD_daemon_get_fdset
+
+/**
+ * Obtain the `select()` sets for this daemon.  Daemon's FDs will be
+ * added to fd_sets. To get only daemon FDs in fd_sets, call FD_ZERO
+ * for each fd_set before calling this function. FD_SETSIZE is assumed
+ * to be platform's default.
+ *
+ * This function should only be called in when MHD is configured to
+ * use external select with 'select()' or with 'epoll'.  In the latter
+ * case, it will only add the single 'epoll()' file descriptor used by
+ * MHD to the sets.  It's necessary to use #MHD_daemon_get_timeout() in
+ * combination with this function.
+ *
+ * This function must be called only for daemon started without
+ * #MHD_USE_INTERNAL_POLLING_THREAD flag.
+ *
+ * @param daemon daemon to get sets from
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set
+ * @param max_fd increased to largest FD added (if larger
+ *               than existing value); can be NULL
+ * @return #MHD_SC_OK on success, otherwise error code
+ * @ingroup event
+ */
+enum MHD_StatusCode
+MHD_daemon_get_fdset (struct MHD_Daemon *daemon,
+		      fd_set *read_fd_set,
+		      fd_set *write_fd_set,
+		      fd_set *except_fd_set,
+		      MHD_socket *max_fd)
+{
+  return MHD_daemon_get_fdset2 (daemon,
+				read_fd_set,
+				write_fd_set,
+				except_fd_set,
+				max_fd,
+				_MHD_SYS_DEFAULT_FD_SETSIZE);
+}
+
+
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+/**
+ * Obtain the select() file descriptor sets for the
+ * given @a urh.
+ *
+ * @param urh upgrade handle to wait for
+ * @param[out] rs read set to initialize
+ * @param[out] ws write set to initialize
+ * @param[out] es except set to initialize
+ * @param[out] max_fd maximum FD to update
+ * @param fd_setsize value of FD_SETSIZE
+ * @return true on success, false on error
+ */
+static bool
+urh_to_fdset (struct MHD_UpgradeResponseHandle *urh,
+              fd_set *rs,
+              fd_set *ws,
+              fd_set *es,
+              MHD_socket *max_fd,
+              unsigned int fd_setsize)
+{
+  const MHD_socket conn_sckt = urh->connection->socket_fd;
+  const MHD_socket mhd_sckt = urh->mhd.socket;
+  bool res = true;
+
+  /* Do not add to 'es' only if socket is closed
+   * or not used anymore. */
+  if (MHD_INVALID_SOCKET != conn_sckt)
+    {
+      if ( (urh->in_buffer_used < urh->in_buffer_size) &&
+           (! MHD_add_to_fd_set_ (conn_sckt,
+                                  rs,
+                                  max_fd,
+                                  fd_setsize)) )
+        res = false;
+      if ( (0 != urh->out_buffer_used) &&
+           (! MHD_add_to_fd_set_ (conn_sckt,
+                                  ws,
+                                  max_fd,
+                                  fd_setsize)) )
+        res = false;
+      /* Do not monitor again for errors if error was detected before as
+       * error state is remembered. */
+      if ((0 == (urh->app.celi & MHD_EPOLL_STATE_ERROR)) &&
+          ((0 != urh->in_buffer_size) ||
+           (0 != urh->out_buffer_size) ||
+           (0 != urh->out_buffer_used)))
+        MHD_add_to_fd_set_ (conn_sckt,
+                            es,
+                            max_fd,
+                            fd_setsize);
+    }
+  if (MHD_INVALID_SOCKET != mhd_sckt)
+    {
+      if ( (urh->out_buffer_used < urh->out_buffer_size) &&
+           (! MHD_add_to_fd_set_ (mhd_sckt,
+                                  rs,
+                                  max_fd,
+                                  fd_setsize)) )
+        res = false;
+      if ( (0 != urh->in_buffer_used) &&
+           (! MHD_add_to_fd_set_ (mhd_sckt,
+                                  ws,
+                                  max_fd,
+                                  fd_setsize)) )
+        res = false;
+      /* Do not monitor again for errors if error was detected before as
+       * error state is remembered. */
+      if ((0 == (urh->mhd.celi & MHD_EPOLL_STATE_ERROR)) &&
+          ((0 != urh->out_buffer_size) ||
+           (0 != urh->in_buffer_size) ||
+           (0 != urh->in_buffer_used)))
+        MHD_add_to_fd_set_ (mhd_sckt,
+                            es,
+                            max_fd,
+                            fd_setsize);
+    }
+
+  return res;
+}
+#endif
+
+
+/**
+ * Internal version of #MHD_daemon_get_fdset2().
+ *
+ * @param daemon daemon to get sets from
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set
+ * @param max_fd increased to largest FD added (if larger
+ *               than existing value); can be NULL
+ * @param fd_setsize value of FD_SETSIZE
+ * @return #MHD_SC_OK on success
+ * @ingroup event
+ */
+static enum MHD_StatusCode
+internal_get_fdset2 (struct MHD_Daemon *daemon,
+                     fd_set *read_fd_set,
+                     fd_set *write_fd_set,
+                     fd_set *except_fd_set,
+                     MHD_socket *max_fd,
+                     unsigned int fd_setsize)
+
+{
+  struct MHD_Connection *pos;
+  struct MHD_Connection *posn;
+  enum MHD_StatusCode result = MHD_SC_OK;
+  MHD_socket ls;
+
+  if (daemon->shutdown)
+    return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
+
+  ls = daemon->listen_socket;
+  if ( (MHD_INVALID_SOCKET != ls) &&
+       (! daemon->was_quiesced) &&
+       (! MHD_add_to_fd_set_ (ls,
+                              read_fd_set,
+                              max_fd,
+                              fd_setsize)) )
+    result = MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE;
+
+  /* Add all sockets to 'except_fd_set' as well to watch for
+   * out-of-band data. However, ignore errors if INFO_READ
+   * or INFO_WRITE sockets will not fit 'except_fd_set'. */
+  /* Start from oldest connections. Make sense for W32 FDSETs. */
+  for (pos = daemon->connections_tail; NULL != pos; pos = posn)
+    {
+      posn = pos->prev;
+
+      switch (pos->request.event_loop_info)
+	{
+	case MHD_EVENT_LOOP_INFO_READ:
+	  if (! MHD_add_to_fd_set_ (pos->socket_fd,
+                                    read_fd_set,
+                                    max_fd,
+                                    fd_setsize))
+	    result = MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE;
+#ifdef MHD_POSIX_SOCKETS
+          MHD_add_to_fd_set_ (pos->socket_fd,
+                              except_fd_set,
+                              max_fd,
+                              fd_setsize);
+#endif /* MHD_POSIX_SOCKETS */
+	  break;
+	case MHD_EVENT_LOOP_INFO_WRITE:
+	  if (! MHD_add_to_fd_set_ (pos->socket_fd,
+                                    write_fd_set,
+                                    max_fd,
+                                    fd_setsize))
+	    result = MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE;
+#ifdef MHD_POSIX_SOCKETS
+          MHD_add_to_fd_set_ (pos->socket_fd,
+                              except_fd_set,
+                              max_fd,
+                              fd_setsize);
+#endif /* MHD_POSIX_SOCKETS */
+	  break;
+	case MHD_EVENT_LOOP_INFO_BLOCK:
+	  if ( (NULL == except_fd_set) ||
+	      ! MHD_add_to_fd_set_ (pos->socket_fd,
+	                            except_fd_set,
+                                    max_fd,
+                                    fd_setsize))
+            result = MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE;
+	  break;
+	case MHD_EVENT_LOOP_INFO_CLEANUP:
+	  /* this should never happen */
+	  break;
+	}
+    }
+#ifdef MHD_WINSOCK_SOCKETS
+  /* W32 use limited array for fd_set so add INFO_READ/INFO_WRITE sockets
+   * only after INFO_BLOCK sockets to ensure that INFO_BLOCK sockets will
+   * not be pushed out. */
+  for (pos = daemon->connections_tail; NULL != pos; pos = posn)
+    {
+      posn = pos->prev;
+      MHD_add_to_fd_set_ (pos->socket_fd,
+                          except_fd_set,
+                          max_fd,
+                          fd_setsize);
+    }
+#endif /* MHD_WINSOCK_SOCKETS */
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+  {
+    struct MHD_UpgradeResponseHandle *urh;
+
+    for (urh = daemon->urh_tail; NULL != urh; urh = urh->prev)
+      {
+        if (! urh_to_fdset (urh,
+			    read_fd_set,
+			    write_fd_set,
+			    except_fd_set,
+			    max_fd,
+			    fd_setsize))
+          result = MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE;
+      }
+  }
+#endif
+  return result;
+}
+
+
+/**
+ * Obtain the `select()` sets for this daemon.  Daemon's FDs will be
+ * added to fd_sets. To get only daemon FDs in fd_sets, call FD_ZERO
+ * for each fd_set before calling this function.
+ *
+ * Passing custom FD_SETSIZE as @a fd_setsize allow usage of
+ * larger/smaller than platform's default fd_sets.
+ *
+ * This function should only be called in when MHD is configured to
+ * use external select with 'select()' or with 'epoll'.  In the latter
+ * case, it will only add the single 'epoll' file descriptor used by
+ * MHD to the sets.  It's necessary to use #MHD_get_timeout() in
+ * combination with this function.
+ *
+ * This function must be called only for daemon started
+ * without #MHD_USE_INTERNAL_POLLING_THREAD flag.
+ *
+ * @param daemon daemon to get sets from
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set
+ * @param max_fd increased to largest FD added (if larger
+ *               than existing value); can be NULL
+ * @param fd_setsize value of FD_SETSIZE
+ * @return #MHD_SC_OK on success, otherwise error code
+ * @ingroup event
+ */
+enum MHD_StatusCode
+MHD_daemon_get_fdset2 (struct MHD_Daemon *daemon,
+		       fd_set *read_fd_set,
+		       fd_set *write_fd_set,
+		       fd_set *except_fd_set,
+		       MHD_socket *max_fd,
+		       unsigned int fd_setsize)
+{
+  if ( (MHD_TM_EXTERNAL_EVENT_LOOP != daemon->threading_model) ||
+       (MHD_ELS_POLL == daemon->event_loop_syscall) ) 
+    return MHD_SC_CONFIGURATION_MISSMATCH_FOR_GET_FDSET;
+
+#ifdef EPOLL_SUPPORT
+  if (MHD_ELS_EPOLL == daemon->event_loop_syscall)
+    {
+      if (daemon->shutdown)
+        return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
+
+      /* we're in epoll mode, use the epoll FD as a stand-in for
+         the entire event set */
+
+      return MHD_add_to_fd_set_ (daemon->epoll_fd,
+                                 read_fd_set,
+                                 max_fd,
+                                 fd_setsize)
+	? MHD_SC_OK
+	: MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE;
+    }
+#endif
+
+  return internal_get_fdset2 (daemon,
+			      read_fd_set,
+                              write_fd_set,
+			      except_fd_set,
+                              max_fd,
+			      fd_setsize);
+}
+
+
+/**
+ * Update the @a urh based on the ready FDs in
+ * the @a rs, @a ws, and @a es.
+ *
+ * @param urh upgrade handle to update
+ * @param rs read result from select()
+ * @param ws write result from select()
+ * @param es except result from select()
+ */
+static void
+urh_from_fdset (struct MHD_UpgradeResponseHandle *urh,
+                const fd_set *rs,
+                const fd_set *ws,
+                const fd_set *es)
+{
+  const MHD_socket conn_sckt = urh->connection->socket_fd;
+  const MHD_socket mhd_sckt = urh->mhd.socket;
+
+  /* Reset read/write ready, preserve error state. */
+  urh->app.celi &= (~MHD_EPOLL_STATE_READ_READY & ~MHD_EPOLL_STATE_WRITE_READY);
+  urh->mhd.celi &= (~MHD_EPOLL_STATE_READ_READY & ~MHD_EPOLL_STATE_WRITE_READY);
+
+  if (MHD_INVALID_SOCKET != conn_sckt)
+    {
+      if (FD_ISSET (conn_sckt, rs))
+        urh->app.celi |= MHD_EPOLL_STATE_READ_READY;
+      if (FD_ISSET (conn_sckt, ws))
+        urh->app.celi |= MHD_EPOLL_STATE_WRITE_READY;
+      if (FD_ISSET (conn_sckt, es))
+        urh->app.celi |= MHD_EPOLL_STATE_ERROR;
+    }
+  if ((MHD_INVALID_SOCKET != mhd_sckt))
+    {
+      if (FD_ISSET (mhd_sckt, rs))
+        urh->mhd.celi |= MHD_EPOLL_STATE_READ_READY;
+      if (FD_ISSET (mhd_sckt, ws))
+        urh->mhd.celi |= MHD_EPOLL_STATE_WRITE_READY;
+      if (FD_ISSET (mhd_sckt, es))
+        urh->mhd.celi |= MHD_EPOLL_STATE_ERROR;
+    }
+}
+
+
+/**
+ * Internal version of #MHD_run_from_select().
+ *
+ * @param daemon daemon to run select loop for
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set 
+ * @return #MHD_SC_OK on success
+ * @ingroup event
+ */
+static enum MHD_StatusCode
+internal_run_from_select (struct MHD_Daemon *daemon,
+                          const fd_set *read_fd_set,
+                          const fd_set *write_fd_set,
+                          const fd_set *except_fd_set)
+{
+  MHD_socket ds;
+  struct MHD_Connection *pos;
+  struct MHD_Connection *prev;
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+  struct MHD_UpgradeResponseHandle *urh;
+  struct MHD_UpgradeResponseHandle *urhn;
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+  /* Reset. New value will be set when connections are processed. */
+  /* Note: no-op for thread-per-connection as it is always false in that mode. */
+  daemon->data_already_pending = false;
+
+  /* Clear ITC to avoid spinning select */
+  /* Do it before any other processing so new signals
+     will trigger select again and will be processed */
+  if ( (MHD_ITC_IS_VALID_(daemon->itc)) &&
+       (FD_ISSET (MHD_itc_r_fd_ (daemon->itc),
+                  read_fd_set)) )
+    MHD_itc_clear_ (daemon->itc);
+
+  /* select connection thread handling type */
+  if ( (MHD_INVALID_SOCKET != (ds = daemon->listen_socket)) &&
+       (! daemon->was_quiesced) &&
+       (FD_ISSET (ds,
+                  read_fd_set)) )
+    (void) MHD_accept_connection (daemon);
+
+  if (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_model)
+    {
+      /* do not have a thread per connection, process all connections now */
+      prev = daemon->connections_tail;
+      while (NULL != (pos = prev))
+        {
+	  prev = pos->prev;
+          ds = pos->socket_fd;
+          if (MHD_INVALID_SOCKET == ds)
+	    continue;
+          call_handlers (pos,
+                         FD_ISSET (ds,
+                                   read_fd_set),
+                         FD_ISSET (ds,
+                                   write_fd_set),
+                         FD_ISSET (ds,
+                                   except_fd_set));
+        }
+    }
+
+#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
+  /* handle upgraded HTTPS connections */
+  for (urh = daemon->urh_tail; NULL != urh; urh = urhn)
+    {
+      urhn = urh->prev;
+      /* update urh state based on select() output */
+      urh_from_fdset (urh,
+                      read_fd_set,
+                      write_fd_set,
+                      except_fd_set);
+      /* call generic forwarding function for passing data */
+      process_urh (urh);
+      /* Finished forwarding? */
+      if ( (0 == urh->in_buffer_size) &&
+           (0 == urh->out_buffer_size) &&
+           (0 == urh->in_buffer_used) &&
+           (0 == urh->out_buffer_used) )
+        {
+          MHD_connection_finish_forward_ (urh->connection);
+          urh->clean_ready = true;
+          /* Resuming will move connection to cleanup list. */
+          MHD_resume_connection(urh->connection);
+        }
+    }
+#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
+  MHD_cleanup_connections (daemon);
+  return MHD_SC_OK;
+}
+
+
+/**
+ * Run webserver operations. This method should be called by clients
+ * in combination with #MHD_get_fdset and #MHD_get_timeout() if the
+ * client-controlled select method is used.
+ *
+ * You can use this function instead of #MHD_run if you called
+ * `select()` on the result from #MHD_get_fdset.  File descriptors in
+ * the sets that are not controlled by MHD will be ignored.  Calling
+ * this function instead of #MHD_run is more efficient as MHD will not
+ * have to call `select()` again to determine which operations are
+ * ready.
+ *
+ * This function cannot be used with daemon started with
+ * #MHD_USE_INTERNAL_POLLING_THREAD flag.
+ *
+ * @param daemon daemon to run select loop for
+ * @param read_fd_set read set
+ * @param write_fd_set write set
+ * @param except_fd_set except set
+ * @return #MHD_SC_OK on success
+ * @ingroup event
+ */
+enum MHD_StatusCode
+MHD_daemon_run_from_select (struct MHD_Daemon *daemon,
+			    const fd_set *read_fd_set,
+
+			    const fd_set *write_fd_set,
+			    const fd_set *except_fd_set)
+{
+  if ( (MHD_TM_EXTERNAL_EVENT_LOOP != daemon->threading_model) ||
+       (MHD_ELS_POLL == daemon->event_loop_syscall) )
+    return MHD_SC_CONFIGURATION_MISSMATCH_FOR_RUN_SELECT;
+  if (MHD_ELS_EPOLL == daemon->event_loop_syscall)
+    {
+#ifdef EPOLL_SUPPORT
+      enum MHD_StatusCode sc;
+
+      sc = MHD_daemon_epoll_ (daemon,
+			      MHD_NO);
+      MHD_cleanup_connections (daemon);
+      return sc;
+#else  /* ! EPOLL_SUPPORT */
+      return MHD_NO;
+#endif /* ! EPOLL_SUPPORT */
+    }
+
+  /* Resuming external connections when using an extern mainloop  */
+  if (! daemon->disallow_suspend_resume)
+    resume_suspended_connections (daemon);
+
+  return internal_run_from_select (daemon,
+				   read_fd_set,
+                                   write_fd_set,
+				   except_fd_set);
+}
+
+
+/**
+ * Main internal select() call.  Will compute select sets, call
+ * select() and then #internal_run_from_select() with the result.
+ *
+ * @param daemon daemon to run select() loop for
+ * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
+ * @return #MHD_SC_OK on success
+ */
+enum MHD_StatusCode
+MHD_daemon_select_ (struct MHD_Daemon *daemon,
+		    int may_block)
+{
+  int num_ready;
+  fd_set rs;
+  fd_set ws;
+  fd_set es;
+  MHD_socket maxsock;
+  struct timeval timeout;
+  struct timeval *tv;
+  MHD_UNSIGNED_LONG_LONG ltimeout;
+  int err_state;
+  MHD_socket ls;
+  enum MHD_StatusCode sc;
+  enum MHD_StatusCode sc2;
+
+  timeout.tv_sec = 0;
+  timeout.tv_usec = 0;
+  if (daemon->shutdown)
+    return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
+  FD_ZERO (&rs);
+  FD_ZERO (&ws);
+  FD_ZERO (&es);
+  maxsock = MHD_INVALID_SOCKET;
+  sc = MHD_SC_OK;
+  if ( (! daemon->disallow_suspend_resume) &&
+       (MHD_YES == resume_suspended_connections (daemon)) &&
+       (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_model) )
+    may_block = MHD_NO;
+
+  if (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_model)
+    {
+      
+      /* single-threaded, go over everything */
+      if (MHD_SC_OK !=
+	  (sc = internal_get_fdset2 (daemon,
+				     &rs,
+				     &ws,
+				     &es,
+				     &maxsock,
+				     FD_SETSIZE)))
+        {
+#ifdef HAVE_MESSAGES
+          MHD_DLOG (daemon,
+		    sc,
+		    _("Could not obtain daemon fdsets"));
+#endif
+        }
+    }
+  else
+    {
+      /* accept only, have one thread per connection */
+      if ( (MHD_INVALID_SOCKET != (ls = daemon->listen_socket)) &&
+           (! daemon->was_quiesced) &&
+           (! MHD_add_to_fd_set_ (ls,
+                                  &rs,
+                                  &maxsock,
+                                  FD_SETSIZE)) )
+        {
+#ifdef HAVE_MESSAGES
+          MHD_DLOG (daemon,
+		    MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE,
+                    _("Could not add listen socket to fdset"));
+#endif
+          return MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE;
+        }
+    }
+  if ( (MHD_ITC_IS_VALID_(daemon->itc)) &&
+       (! MHD_add_to_fd_set_ (MHD_itc_r_fd_ (daemon->itc),
+                              &rs,
+                              &maxsock,
+                              FD_SETSIZE)) )
+    {
+#if defined(MHD_WINSOCK_SOCKETS)
+      /* fdset limit reached, new connections
+         cannot be handled. Remove listen socket FD
+         from fdset and retry to add ITC FD. */
+      if ( (MHD_INVALID_SOCKET != (ls = daemon->listen_socket)) &&
+           (! daemon->was_quiesced) )
+        {
+          FD_CLR (ls,
+                  &rs);
+          if (! MHD_add_to_fd_set_ (MHD_itc_r_fd_(daemon->itc),
+                                    &rs,
+                                    &maxsock,
+                                    FD_SETSIZE))
+            {
+#endif /* MHD_WINSOCK_SOCKETS */
+              sc = MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE; 
+#ifdef HAVE_MESSAGES
+              MHD_DLOG (daemon,
+			sc,
+                        _("Could not add control inter-thread communication channel FD to fdset"));
+#endif
+#if defined(MHD_WINSOCK_SOCKETS)
+            }
+        }
+#endif /* MHD_WINSOCK_SOCKETS */
+    }
+  /* Stop listening if we are at the configured connection limit */
+  /* If we're at the connection limit, no point in really
+     accepting new connections; however, make sure we do not miss
+     the shutdown OR the termination of an existing connection; so
+     only do this optimization if we have a signaling ITC in
+     place. */
+  if ( (MHD_INVALID_SOCKET != (ls = daemon->listen_socket)) &&
+       (MHD_ITC_IS_VALID_(daemon->itc)) &&
+       ( (daemon->connections == daemon->global_connection_limit) ||
+         (daemon->at_limit) ) )
+    {
+      FD_CLR (ls,
+              &rs);
+    }
+  tv = NULL;
+  if (MHD_SC_OK != sc)
+    may_block = MHD_NO;
+  if (MHD_NO == may_block)
+    {
+      timeout.tv_usec = 0;
+      timeout.tv_sec = 0;
+      tv = &timeout;
+    }
+  else if ( (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_model) &&
+	    (MHD_SC_OK ==
+	     MHD_daemon_get_timeout (daemon,
+				     &ltimeout)) )
+    {
+      /* ltimeout is in ms */
+      timeout.tv_usec = (ltimeout % 1000) * 1000;
+      if (ltimeout / 1000 > TIMEVAL_TV_SEC_MAX)
+        timeout.tv_sec = TIMEVAL_TV_SEC_MAX;
+      else
+        timeout.tv_sec = (_MHD_TIMEVAL_TV_SEC_TYPE)(ltimeout / 1000);
+      tv = &timeout;
+    }
+  num_ready = MHD_SYS_select_ (maxsock + 1,
+                               &rs,
+                               &ws,
+                               &es,
+                               tv);
+  if (daemon->shutdown)
+    return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
+  if (num_ready < 0)
+    {
+      const int err = MHD_socket_get_error_ ();
+
+      if (MHD_SCKT_ERR_IS_EINTR_(err))
+        return sc;
+#ifdef HAVE_MESSAGES
+      MHD_DLOG (daemon,
+		MHD_SC_UNEXPECTED_SELECT_ERROR,
+                _("select failed: %s\n"),
+                MHD_socket_strerr_ (err));
+#endif
+      return MHD_SC_UNEXPECTED_SELECT_ERROR;
+    }
+  if (MHD_SC_OK !=
+      (sc2 = internal_run_from_select (daemon,
+				       &rs,
+				       &ws,
+				       &es)))
+    return sc2;
+  return sc;
+}
+
+/* end of daemon_select.c */

+ 43 - 0
src/lib/daemon_select.h

@@ -0,0 +1,43 @@
+/*
+  This file is part of libmicrohttpd
+  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+/**
+ * @file lib/daemon_select.h
+ * @brief  non-public functions provided by daemon_select.c
+ * @author Christian Grothoff
+ */
+
+#ifndef DAEMON_SELECT_H
+#define DAEMON_SELECT_H
+
+/**
+ * Main internal select() call.  Will compute select sets, call
+ * select() and then #internal_run_from_select() with the result.
+ *
+ * @param daemon daemon to run select() loop for
+ * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
+ * @return #MHD_SC_OK on success
+ */
+enum MHD_StatusCode
+MHD_daemon_select_ (struct MHD_Daemon *daemon,
+		    int may_block)
+  MHD_NONNULL(1);
+
+
+#endif

+ 7 - 15
src/lib/daemon_start.c

@@ -23,15 +23,7 @@
  * @author Christian Grothoff
  */
 #include "internal.h"
-
-/* ************************* event loops ********************** */
-
-
-
-/* TODO: migrate! */
-
-
-/* ************* Functions for MHD_daemon_start() ************ */
+#include "daemon_select.h"
 
 
 /**
@@ -626,17 +618,17 @@ MHD_polling_thread (void *cls)
 	MHD_PANIC ("MHD_ELS_AUTO should have been mapped to preferred style");
 	break;
       case MHD_ELS_SELECT:
-	MHD_select (daemon,
-		    MHD_YES);
+	MHD_daemon_select_ (daemon,
+			    MHD_YES);
 	break;
       case MHD_ELS_POLL:
-	MHD_poll (daemon,
-		  MHD_YES);
+	MHD_daemon_poll_ (daemon,
+			  MHD_YES);
 	break;
       case MHD_ELS_EPOLL:
 #ifdef EPOLL_SUPPORT	
-	MHD_epoll (daemon,
-		   MHD_YES);
+	MHD_daemon_epoll_ (daemon,
+			   MHD_YES);
 #else
 	MHD_PANIC ("MHD_ELS_EPOLL not supported, should have failed earlier");
 #endif

+ 25 - 1
src/lib/internal.h

@@ -18,7 +18,7 @@
 */
 
 /**
- * @file microhttpd/internal.h
+ * @file lib/internal.h
  * @brief  internal shared structures
  * @author Daniel Pittman
  * @author Christian Grothoff
@@ -800,6 +800,11 @@ struct MHD_Connection
    */
   bool suspended;
 
+  /**
+   * Are we ready to read from TLS for this connection?
+   */
+  bool tls_read_ready;
+
   /**
    * Is the connection wanting to resume?
    */
@@ -1500,6 +1505,15 @@ struct MHD_Daemon
    */ 
   bool disallow_upgrade;
 
+  /**
+   * Did we hit some system or process-wide resource limit while
+   * trying to accept() the last time? If so, we don't accept new
+   * connections until we close an existing one.  This effectively
+   * temporarily lowers the "connection_limit" to the current
+   * number of connections.
+   */
+  bool at_limit;
+
   /**
    * Disables optional calls to `shutdown()` and enables aggressive
    * non-blocking optimistic reads and other potentially unsafe
@@ -1507,6 +1521,16 @@ struct MHD_Daemon
    */
   bool enable_turbo;
 
+  /**
+   * 'True' if some data is already waiting to be processed.  If set
+   * to 'true' - zero timeout for select()/poll*() is used.  Should be
+   * reset each time before processing connections and raised by any
+   * connection which require additional immediately processing
+   * (application does not provide data for response, data waiting in
+   * TLS buffers etc.)
+   */
+  bool data_already_pending;
+
   /**
    * MHD_daemon_quiesce() was run against this daemon.
    */