libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

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:
Msrc/include/microhttpd2.h | 35+++++++++++++++++++++++++++++++++++
Msrc/lib/Makefile.am | 5+++--
Asrc/lib/daemon_epoll.c | 504+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/daemon_epoll.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/daemon_get_fdset.c | 269-------------------------------------------------------------------------------
Msrc/lib/daemon_get_timeout.c | 74+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Asrc/lib/daemon_poll.c | 453+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/daemon_poll.h | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/daemon_run.c | 30+++++++++++++++++++++++++++++-
Msrc/lib/daemon_run_from_select.c | 59-----------------------------------------------------------
Asrc/lib/daemon_select.c | 711+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/daemon_select.h | 43+++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/daemon_start.c | 22+++++++---------------
Msrc/lib/internal.h | 26+++++++++++++++++++++++++-
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, + &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 */ 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, + &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 */ 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;