commit 78dc7915f89198b472b9e10ee532f9971bcc0ada
parent 9914072c16b773a828709ecc61062c37c864e858
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 15 Feb 2018 05:42:11 +0100
migrating main parts of event loops
Diffstat:
14 files changed, 2000 insertions(+), 348 deletions(-)
diff --git a/src/include/microhttpd2.h b/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,
+
};
diff --git a/src/lib/Makefile.am b/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 \
diff --git a/src/lib/daemon_epoll.c b/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 */
diff --git a/src/lib/daemon_epoll.h b/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
diff --git a/src/lib/daemon_get_fdset.c b/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 */
diff --git a/src/lib/daemon_get_timeout.c b/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 */
diff --git a/src/lib/daemon_poll.c b/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,
+ <imeout)) )
+ 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 */
diff --git a/src/lib/daemon_poll.h b/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
diff --git a/src/lib/daemon_run.c b/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 */
diff --git a/src/lib/daemon_run_from_select.c b/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 */
diff --git a/src/lib/daemon_select.c b/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,
+ <imeout)) )
+ {
+ /* 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 */
diff --git a/src/lib/daemon_select.h b/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
diff --git a/src/lib/daemon_start.c b/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
diff --git a/src/lib/internal.h b/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
@@ -801,6 +801,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?
*/
bool resuming;
@@ -1501,6 +1506,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
* optimizations. See #MHD_daemon_enable_turbo().
@@ -1508,6 +1522,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.
*/
bool was_quiesced;