libmicrohttpd

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

commit 9fe0e9d41112f2a75ee4d9461f1b93b59131029d
parent 21f9a70d53a472e0c3894099308ca040d401209a
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu,  4 Jul 2013 15:29:56 +0000

Adding support for using epoll for the MHD event loop

Diffstat:
MChangeLog | 29+++++++++++++++++++++++++++++
Msrc/include/microhttpd.h | 14+++++++++++---
Msrc/microhttpd/connection.c | 297++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/microhttpd/connection.h | 32--------------------------------
Msrc/microhttpd/daemon.c | 1149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/microhttpd/internal.h | 285++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/microhttpd/memorypool.c | 9++++++---
Msrc/testcurl/Makefile.am | 4+++-
Msrc/testcurl/perf_get.c | 13++++++++++---
Msrc/testcurl/perf_get_concurrent.c | 4++++
Msrc/testcurl/test_get.c | 9+++++++++
Msrc/testcurl/test_quiesce.c | 76+++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msrc/testcurl/test_start_stop.c | 4++++
13 files changed, 1439 insertions(+), 486 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -6,6 +6,35 @@ Fri Jun 28 14:05:15 CEST 2013 because the application queued a response immediately --- reserve that behavior for PUT/POST. -CG +Tue Jun 25 15:08:30 CEST 2013 + Added option 'MHD_USE_DUAL_STACK' to support a single + daemon for IPv4 and IPv6 without the application having + to do the binding. -CG + +Mon Jun 24 22:33:34 CEST 2013 + Finished integration with epoll, including benchmarking and + documentation. -CG + +Sun Jun 23 15:28:13 CEST 2013 + Added option 'MHD_USE_PIPE_FOR_SHUTDOWN' to cleanly support + 'MHD_quiesce_daemon' with thread pools and per-connection + threads (we then need a pipe for shutdown, but if + 'MHD_quiesce_daemon' is not used, we do not want to + require the use of a pipe; introducing the pipe after + the threads have been started can also fail, so the + application needs to tell us early on). -CG + +Sat Jun 22 20:24:17 CEST 2013 + Removed locking calls for thread modes that do not need them. + Reorganized way to obtain connection's event loop state. + Added sorted XDLL for connections with default timeout to + avoid having to loop over all connections to determine current + timeout (custom per-connection timeouts are in another list + which is iterated each time). -CG + +Fri Jun 21 20:55:48 CEST 2013 + Preparing build system and tests for epoll support. -CG + Tue May 21 14:34:36 CEST 2013 Improving configure tests for OpenSSL and spdylay to avoid build errors in libmicrospdy code if those libraries diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -356,8 +356,11 @@ enum MHD_FLAG MHD_USE_SELECT_INTERNALLY = 8, /** - * Run using the IPv6 protocol (otherwise, MHD will - * just support IPv4). + * Run using the IPv6 protocol (otherwise, MHD will just support + * IPv4). If you want MHD to support IPv4 and IPv6 using a single + * socket, pass MHD_USE_DUAL_STACK, otherwise, if you only pass + * this option, MHD will try to bind to IPv6-only (resulting in + * no IPv4 support). */ MHD_USE_IPv6 = 16, @@ -428,7 +431,12 @@ enum MHD_FLAG * specify it), if 'MHD_USE_NO_LISTEN_SOCKET' is specified. In * "external" select mode, this option is always simply ignored. */ - MHD_USE_PIPE_FOR_SHUTDOWN = 1024 + MHD_USE_PIPE_FOR_SHUTDOWN = 1024, + + /** + * Use a single socket for IPv4 and IPv6. + */ + MHD_USE_DUAL_STACK = MHD_USE_IPv6 | 2048 }; diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c @@ -318,8 +318,9 @@ MHD_connection_close (struct MHD_Connection *connection, daemon = connection->daemon; SHUTDOWN (connection->socket_fd, - (connection->read_closed == MHD_YES) ? SHUT_WR : SHUT_RDWR); + (MHD_YES == connection->read_closed) ? SHUT_WR : SHUT_RDWR); connection->state = MHD_CONNECTION_CLOSED; + connection->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; if ( (NULL != daemon->notify_completed) && (MHD_YES == connection->client_aware) ) daemon->notify_completed (daemon->notify_completed_cls, @@ -745,8 +746,8 @@ build_header_response (struct MHD_Connection *connection) kind = MHD_FOOTER_KIND; off = 0; } - must_add_close = ( (connection->state == MHD_CONNECTION_FOOTERS_RECEIVED) && - (connection->read_closed == MHD_YES) && + must_add_close = ( (MHD_CONNECTION_FOOTERS_RECEIVED == connection->state) && + (MHD_YES == connection->read_closed) && (0 == strcasecmp (connection->version, MHD_HTTP_VERSION_1_1)) && (NULL == MHD_get_response_header (connection->response, @@ -853,87 +854,16 @@ transmit_error_response (struct MHD_Connection *connection, /** - * Add "fd" to the "fd_set". If "fd" is - * greater than "*max", set "*max" to fd. - * - * @param fd file descriptor to add to the set - * @param set set to modify - * @param max_fd maximum value to potentially update - */ -static void -add_to_fd_set (int fd, - fd_set *set, - int *max_fd) -{ - FD_SET (fd, set); - if ( (NULL != max_fd) && - (fd > *max_fd) ) - *max_fd = fd; -} - - -/** - * Obtain the select sets for this connection. The given - * sets (and the maximum) are updated and must have - * already been initialized. - * - * @param connection connetion to get select sets for - * @param read_fd_set read set to initialize - * @param write_fd_set write set to initialize - * @param except_fd_set except set to initialize (never changed) - * @param max_fd where to store largest FD put into any set - * @return MHD_YES on success - */ -int -MHD_connection_get_fdset (struct MHD_Connection *connection, - fd_set *read_fd_set, - fd_set *write_fd_set, - fd_set *except_fd_set, - int *max_fd) -{ - int ret; - struct MHD_Pollfd p; - - /* we use the 'poll fd' as a convenient way to re-use code - when determining the select sets */ - memset (&p, 0, sizeof(struct MHD_Pollfd)); - ret = MHD_connection_get_pollfd (connection, &p); - if ( (MHD_YES == ret) && (p.fd >= 0) ) { - if (0 != (p.events & MHD_POLL_ACTION_IN)) - add_to_fd_set(p.fd, read_fd_set, max_fd); - if (0 != (p.events & MHD_POLL_ACTION_OUT)) - add_to_fd_set(p.fd, write_fd_set, max_fd); - } - return ret; -} - - -/** - * Obtain the pollfd for this connection + * Update the 'event_loop_info' field of this connection based on the state + * that the connection is now in. May also close the connection or + * perform other updates to the connection if needed to prepare for + * the next round of the event loop. * * @param connection connetion to get poll set for - * @param p where to store the polling information - * @return MHD_YES on success. If return MHD_YES and p->fd < 0, this - * connection is not waiting for any read or write events */ -int -MHD_connection_get_pollfd (struct MHD_Connection *connection, - struct MHD_Pollfd *p) +static void +MHD_connection_update_event_loop_info (struct MHD_Connection *connection) { - int fd; - - if (NULL == connection->pool) - connection->pool = MHD_pool_create (connection->daemon->pool_size); - if (NULL == connection->pool) - { - CONNECTION_CLOSE_ERROR (connection, - "Failed to create memory pool!\n"); - return MHD_YES; - } - fd = connection->socket_fd; - p->fd = fd; - if (-1 == fd) - return MHD_YES; while (1) { #if DEBUG_STATES @@ -945,9 +875,9 @@ MHD_connection_get_pollfd (struct MHD_Connection *connection, #if HTTPS_SUPPORT case MHD_TLS_CONNECTION_INIT: if (0 == gnutls_record_get_direction (connection->tls_session)) - p->events |= MHD_POLL_ACTION_IN; + connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; else - p->events |= MHD_POLL_ACTION_OUT; + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; break; #endif case MHD_CONNECTION_INIT: @@ -955,15 +885,8 @@ MHD_connection_get_pollfd (struct MHD_Connection *connection, case MHD_CONNECTION_HEADER_PART_RECEIVED: /* while reading headers, we always grow the read buffer if needed, no size-check required */ - if ((connection->read_closed) && - (0 == connection->read_buffer_offset)) - { - CONNECTION_CLOSE_ERROR (connection, - "Connection buffer to small for request\n"); - continue; - } - if ((connection->read_buffer_offset == connection->read_buffer_size) - && (MHD_NO == try_grow_read_buffer (connection))) + if ( (connection->read_buffer_offset == connection->read_buffer_size) && + (MHD_NO == try_grow_read_buffer (connection)) ) { transmit_error_response (connection, (connection->url != NULL) @@ -972,18 +895,19 @@ MHD_connection_get_pollfd (struct MHD_Connection *connection, REQUEST_TOO_BIG); continue; } - if (MHD_NO == connection->read_closed) - p->events |= MHD_POLL_ACTION_IN; + if (MHD_NO == connection->read_closed) + connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + else + connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; break; case MHD_CONNECTION_HEADERS_RECEIVED: - /* we should never get here */ EXTRA_CHECK (0); break; case MHD_CONNECTION_HEADERS_PROCESSED: EXTRA_CHECK (0); break; case MHD_CONNECTION_CONTINUE_SENDING: - p->events |= MHD_POLL_ACTION_OUT; + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; break; case MHD_CONNECTION_CONTINUE_SENT: if (connection->read_buffer_offset == connection->read_buffer_size) @@ -1010,9 +934,11 @@ MHD_connection_get_pollfd (struct MHD_Connection *connection, continue; } } - if ((connection->read_buffer_offset < connection->read_buffer_size) - && (MHD_NO == connection->read_closed)) - p->events |= MHD_POLL_ACTION_IN; + if ( (connection->read_buffer_offset < connection->read_buffer_size) && + (MHD_NO == connection->read_closed) ) + connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + else + connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; break; case MHD_CONNECTION_BODY_RECEIVED: case MHD_CONNECTION_FOOTER_PART_RECEIVED: @@ -1024,50 +950,49 @@ MHD_connection_get_pollfd (struct MHD_Connection *connection, NULL); continue; } - p->events |= MHD_POLL_ACTION_IN; + connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; /* transition to FOOTERS_RECEIVED happens in read handler */ break; case MHD_CONNECTION_FOOTERS_RECEIVED: - /* no socket action, wait for client - to provide response */ + connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; break; case MHD_CONNECTION_HEADERS_SENDING: /* headers in buffer, keep writing */ - p->events |= MHD_POLL_ACTION_OUT; + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; break; case MHD_CONNECTION_HEADERS_SENT: EXTRA_CHECK (0); break; case MHD_CONNECTION_NORMAL_BODY_READY: - p->events |= MHD_POLL_ACTION_OUT; + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; break; case MHD_CONNECTION_NORMAL_BODY_UNREADY: - /* not ready, no socket action */ + connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; break; case MHD_CONNECTION_CHUNKED_BODY_READY: - p->events |= MHD_POLL_ACTION_OUT; + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; break; case MHD_CONNECTION_CHUNKED_BODY_UNREADY: - /* not ready, no socket action */ + connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; break; case MHD_CONNECTION_BODY_SENT: EXTRA_CHECK (0); break; case MHD_CONNECTION_FOOTERS_SENDING: - p->events |= MHD_POLL_ACTION_OUT; + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; break; case MHD_CONNECTION_FOOTERS_SENT: EXTRA_CHECK (0); break; case MHD_CONNECTION_CLOSED: - return MHD_YES; /* do nothing, not even reading */ + connection->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; + return; /* do nothing, not even reading */ default: EXTRA_CHECK (0); } break; } - return MHD_YES; } @@ -1888,10 +1813,41 @@ parse_connection_headers (struct MHD_Connection *connection) /** + * Update the 'last_activity' field of the connection to the current time + * and move the connection to the head of the 'normal_timeout' list if + * the timeout for the connection uses the default value. + * + * @param connection the connection that saw some activity + */ +static void +update_last_activity (struct MHD_Connection *connection) +{ + struct MHD_Daemon *daemon = connection->daemon; + + connection->last_activity = MHD_monotonic_time(); + if (connection->connection_timeout != daemon->connection_timeout) + return; /* custom timeout, no need to move it in DLL */ + + /* move connection to head of timeout list (by remove + add operation) */ + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_lock (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + XDLL_remove (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + XDLL_insert (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_unlock (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); +} + + + +/** * This function handles a particular connection when it has been - * determined that there is data to be read off a socket. All - * implementations (multithreaded, external select, internal select) - * call this function to handle reads. + * determined that there is data to be read off a socket. * * @param connection connection to handle * @return always MHD_YES (we should continue to process the @@ -1900,7 +1856,7 @@ parse_connection_headers (struct MHD_Connection *connection) int MHD_connection_handle_read (struct MHD_Connection *connection) { - connection->last_activity = MHD_monotonic_time(); + update_last_activity (connection); if (connection->state == MHD_CONNECTION_CLOSED) return MHD_YES; /* make sure "read" has a reasonable number of bytes @@ -1953,9 +1909,7 @@ MHD_connection_handle_read (struct MHD_Connection *connection) /** * This function was created to handle writes to sockets when it has - * been determined that the socket can be written to. All - * implementations (multithreaded, external select, internal select) - * call this function + * been determined that the socket can be written to. * * @param connection connection to handle * @return always MHD_YES (we should continue to process the @@ -1966,7 +1920,8 @@ MHD_connection_handle_write (struct MHD_Connection *connection) { struct MHD_Response *response; int ret; - connection->last_activity = MHD_monotonic_time(); + + update_last_activity (connection); while (1) { #if DEBUG_STATES @@ -2111,9 +2066,7 @@ MHD_connection_handle_write (struct MHD_Connection *connection) /** * This function was created to handle per-connection processing that - * has to happen even if the socket cannot be read or written to. All - * implementations (multithreaded, external select, internal select) - * call this function. + * has to happen even if the socket cannot be read or written to. * * @param connection connection to handle * @return MHD_YES if we should continue to process the @@ -2122,7 +2075,7 @@ MHD_connection_handle_write (struct MHD_Connection *connection) int MHD_connection_handle_idle (struct MHD_Connection *connection) { - struct MHD_Daemon *daemon; + struct MHD_Daemon *daemon = connection->daemon; unsigned int timeout; const char *end; int rend; @@ -2131,18 +2084,18 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) while (1) { #if DEBUG_STATES - MHD_DLOG (connection->daemon, "%s: state: %s\n", + MHD_DLOG (daemon, "%s: state: %s\n", __FUNCTION__, MHD_state_to_string (connection->state)); #endif switch (connection->state) { case MHD_CONNECTION_INIT: line = get_next_header_line (connection); - if (line == NULL) + if (NULL == line) { - if (connection->state != MHD_CONNECTION_INIT) + if (MHD_CONNECTION_INIT != connection->state) continue; - if (connection->read_closed) + if (MHD_YES == connection->read_closed) { CONNECTION_CLOSE_ERROR (connection, NULL); @@ -2159,9 +2112,9 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) line = get_next_header_line (connection); if (line == NULL) { - if (connection->state != MHD_CONNECTION_URL_RECEIVED) + if (MHD_CONNECTION_URL_RECEIVED != connection->state) continue; - if (connection->read_closed) + if (MHD_YES == connection->read_closed) { CONNECTION_CLOSE_ERROR (connection, NULL); @@ -2189,7 +2142,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) { if (connection->state != MHD_CONNECTION_HEADER_PART_RECEIVED) continue; - if (connection->read_closed) + if (MHD_YES == connection->read_closed) { CONNECTION_CLOSE_ERROR (connection, NULL); @@ -2232,7 +2185,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) /* force close, in case client still tries to upload... */ connection->read_closed = MHD_YES; } - connection->state = (connection->remaining_upload_size == 0) + connection->state = (0 == connection->remaining_upload_size) ? MHD_CONNECTION_FOOTERS_RECEIVED : MHD_CONNECTION_CONTINUE_SENT; continue; case MHD_CONNECTION_CONTINUE_SENDING: @@ -2269,7 +2222,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) { if (connection->state != MHD_CONNECTION_BODY_RECEIVED) continue; - if (connection->read_closed) + if (MHD_YES == connection->read_closed) { CONNECTION_CLOSE_ERROR (connection, NULL); @@ -2297,7 +2250,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) { if (connection->state != MHD_CONNECTION_FOOTER_PART_RECEIVED) continue; - if (connection->read_closed) + if (MHD_YES == connection->read_closed) { CONNECTION_CLOSE_ERROR (connection, NULL); @@ -2421,11 +2374,10 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) ( (end != NULL) && (0 == strcasecmp (end, "close")) ) ); MHD_destroy_response (connection->response); connection->response = NULL; - if (connection->daemon->notify_completed != NULL) - connection->daemon->notify_completed (connection->daemon-> - notify_completed_cls, - connection, - &connection->client_context, + if (daemon->notify_completed != NULL) + daemon->notify_completed (daemon->notify_completed_cls, + connection, + &connection->client_context, MHD_REQUEST_TERMINATED_COMPLETED_OK); connection->client_aware = MHD_NO; end = @@ -2478,21 +2430,26 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) MHD_destroy_response (connection->response); connection->response = NULL; } - daemon = connection->daemon; - if (0 != pthread_mutex_lock(&daemon->cleanup_connection_mutex)) - { - MHD_PANIC ("Failed to acquire cleanup mutex\n"); - } + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_lock (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + if (connection->connection_timeout == daemon->connection_timeout) + XDLL_remove (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + else + XDLL_remove (daemon->manual_timeout_head, + daemon->manual_timeout_tail, + connection); DLL_remove (daemon->connections_head, daemon->connections_tail, connection); DLL_insert (daemon->cleanup_head, daemon->cleanup_tail, connection); - if (0 != pthread_mutex_unlock(&daemon->cleanup_connection_mutex)) - { - MHD_PANIC ("Failed to release cleanup mutex\n"); - } + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_unlock(&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); return MHD_NO; default: EXTRA_CHECK (0); @@ -2507,6 +2464,32 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) MHD_connection_close (connection, MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); return MHD_YES; } + MHD_connection_update_event_loop_info (connection); + switch (connection->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + if (0 != (connection->epoll_state & MHD_EPOLL_STATE_READ_READY)) + EDLL_insert (daemon->eready_head, + daemon->eready_tail, + connection); + break; + case MHD_EVENT_LOOP_INFO_WRITE: + if (0 != (connection->epoll_state & MHD_EPOLL_STATE_WRITE_READY)) + EDLL_insert (daemon->eready_head, + daemon->eready_tail, + connection); + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + /* we should look at this connection again in the next iteration + of the event loop, as we're waiting on the application */ + EDLL_insert (daemon->eready_head, + daemon->eready_tail, + connection); + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* This connection is finished, nothing left to do */ + break; + } return MHD_YES; } @@ -2582,13 +2565,37 @@ MHD_set_connection_option (struct MHD_Connection *connection, ...) { va_list ap; + struct MHD_Daemon *daemon; + daemon = connection->daemon; switch (option) { case MHD_CONNECTION_OPTION_TIMEOUT: + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_lock (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + if (connection->connection_timeout == daemon->connection_timeout) + XDLL_remove (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + else + XDLL_remove (daemon->manual_timeout_head, + daemon->manual_timeout_tail, + connection); va_start (ap, option); connection->connection_timeout = va_arg (ap, unsigned int); va_end (ap); + if (connection->connection_timeout == daemon->connection_timeout) + XDLL_insert (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + else + XDLL_insert (daemon->manual_timeout_head, + daemon->manual_timeout_tail, + connection); + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_unlock (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); return MHD_YES; default: return MHD_NO; diff --git a/src/microhttpd/connection.h b/src/microhttpd/connection.h @@ -31,38 +31,6 @@ /** - * Obtain the select sets for this connection. The given - * sets (and the maximum) are updated and must have - * already been initialized. - * - * @param connection connetion to get select sets for - * @param read_fd_set read set to initialize - * @param write_fd_set write set to initialize - * @param except_fd_set except set to initialize (never changed) - * @param max_fd where to store largest FD put into any set - * @return MHD_YES on success - */ -int -MHD_connection_get_fdset (struct MHD_Connection *connection, - fd_set * read_fd_set, - fd_set * write_fd_set, - fd_set * except_fd_set, int *max_fd); - - -/** - * Obtain the pollfd for this connection. The poll interface allows large - * file descriptors. Select goes stupid when the fd overflows fdset (which - * is fixed). - * - * @param connection connetion to get poll set for - * @param p where to store the polling information - */ -int -MHD_connection_get_pollfd (struct MHD_Connection *connection, - struct MHD_Pollfd *p); - - -/** * Set callbacks for this connection to those for HTTP. * * @param connection connection to initialize diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c @@ -1,6 +1,6 @@ /* This file is part of libmicrohttpd - (C) 2007, 2008, 2009, 2010, 2011, 2012 Daniel Pittman and Christian Grothoff + (C) 2007-2013 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 @@ -39,7 +39,6 @@ #if HTTPS_SUPPORT #include "connection_https.h" -#include <gnutls/gnutls.h> #include <gcrypt.h> #endif @@ -178,7 +177,7 @@ struct MHD_IPCount * @param daemon handle to daemon where lock is */ static void -MHD_ip_count_lock(struct MHD_Daemon *daemon) +MHD_ip_count_lock (struct MHD_Daemon *daemon) { if (0 != pthread_mutex_lock(&daemon->per_ip_connection_mutex)) { @@ -193,7 +192,7 @@ MHD_ip_count_lock(struct MHD_Daemon *daemon) * @param daemon handle to daemon where lock is */ static void -MHD_ip_count_unlock(struct MHD_Daemon *daemon) +MHD_ip_count_unlock (struct MHD_Daemon *daemon) { if (0 != pthread_mutex_unlock(&daemon->per_ip_connection_mutex)) { @@ -212,7 +211,7 @@ MHD_ip_count_unlock(struct MHD_Daemon *daemon) * @return -1, 0 or 1 depending on result of compare */ static int -MHD_ip_addr_compare(const void *a1, const void *a2) +MHD_ip_addr_compare (const void *a1, const void *a2) { return memcmp (a1, a2, offsetof (struct MHD_IPCount, count)); } @@ -227,9 +226,9 @@ MHD_ip_addr_compare(const void *a1, const void *a2) * @return MHD_YES on success and MHD_NO otherwise (e.g., invalid address type) */ static int -MHD_ip_addr_to_key(const struct sockaddr *addr, - socklen_t addrlen, - struct MHD_IPCount *key) +MHD_ip_addr_to_key (const struct sockaddr *addr, + socklen_t addrlen, + struct MHD_IPCount *key) { memset(key, 0, sizeof(*key)); @@ -268,9 +267,9 @@ MHD_ip_addr_to_key(const struct sockaddr *addr, * Also returns MHD_NO if fails to allocate memory. */ static int -MHD_ip_limit_add(struct MHD_Daemon *daemon, - const struct sockaddr *addr, - socklen_t addrlen) +MHD_ip_limit_add (struct MHD_Daemon *daemon, + const struct sockaddr *addr, + socklen_t addrlen) { struct MHD_IPCount *key; void **nodep; @@ -332,9 +331,9 @@ MHD_ip_limit_add(struct MHD_Daemon *daemon, * @param addrlen number of bytes in addr */ static void -MHD_ip_limit_del(struct MHD_Daemon *daemon, - const struct sockaddr *addr, - socklen_t addrlen) +MHD_ip_limit_del (struct MHD_Daemon *daemon, + const struct sockaddr *addr, + socklen_t addrlen) { struct MHD_IPCount search_key; struct MHD_IPCount *found_key; @@ -392,7 +391,11 @@ recv_tls_adapter (struct MHD_Connection *connection, void *other, size_t i) { int res; - connection->tls_read_ready = MHD_NO; + if (MHD_YES == connection->tls_read_ready) + { + connection->daemon->num_tls_read_ready--; + connection->tls_read_ready = MHD_NO; + } res = gnutls_record_recv (connection->tls_session, other, i); if ( (GNUTLS_E_AGAIN == res) || (GNUTLS_E_INTERRUPTED == res) ) @@ -404,12 +407,15 @@ recv_tls_adapter (struct MHD_Connection *connection, void *other, size_t i) { /* Likely 'GNUTLS_E_INVALID_SESSION' (client communication disrupted); set errno to something caller will interpret - correctly as a hard error*/ + correctly as a hard error */ errno = EPIPE; return res; } if (res == i) - connection->tls_read_ready = MHD_YES; + { + connection->tls_read_ready = MHD_YES; + connection->daemon->num_tls_read_ready++; + } return res; } @@ -515,6 +521,26 @@ MHD_TLS_init (struct MHD_Daemon *daemon) /** + * Add "fd" to the "fd_set". If "fd" is + * greater than "*max", set "*max" to fd. + * + * @param fd file descriptor to add to the set + * @param set set to modify + * @param max_fd maximum value to potentially update + */ +static void +add_to_fd_set (int fd, + fd_set *set, + int *max_fd) +{ + FD_SET (fd, set); + if ( (NULL != max_fd) && + (fd > *max_fd) ) + *max_fd = fd; +} + + +/** * Obtain the select sets for this daemon. * * @param daemon daemon to get sets from @@ -546,6 +572,20 @@ MHD_get_fdset (struct MHD_Daemon *daemon, || (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) || (0 != (daemon->options & MHD_USE_POLL))) return MHD_NO; +#if EPOLL_SUPPORT + if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + { + /* we're in epoll mode, use the epoll FD as a stand-in for + the entire event set */ + + if (daemon->epoll_fd >= FD_SETSIZE) + return MHD_NO; /* poll fd too big, fail hard */ + FD_SET (daemon->epoll_fd, read_fd_set); + if ((*max_fd) < daemon->epoll_fd) + *max_fd = daemon->epoll_fd; + return MHD_YES; + } +#endif fd = daemon->socket_fd; if (-1 != fd) { @@ -555,11 +595,23 @@ MHD_get_fdset (struct MHD_Daemon *daemon, *max_fd = fd; } for (pos = daemon->connections_head; NULL != pos; pos = pos->next) - if (MHD_YES != MHD_connection_get_fdset (pos, - read_fd_set, - write_fd_set, - except_fd_set, max_fd)) - return MHD_NO; + { + switch (pos->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + add_to_fd_set (pos->socket_fd, read_fd_set, max_fd); + break; + case MHD_EVENT_LOOP_INFO_WRITE: + add_to_fd_set (pos->socket_fd, write_fd_set, max_fd); + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + /* not in any FD set */ + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* this should never happen */ + break; + } + } #if DEBUG_CONNECT MHD_DLOG (daemon, "Maximum socket in select set: %d\n", *max_fd); #endif @@ -581,14 +633,12 @@ MHD_handle_connection (void *data) int num_ready; fd_set rs; fd_set ws; - fd_set es; int max; struct timeval tv; struct timeval *tvp; unsigned int timeout; time_t now; #ifdef HAVE_POLL_H - struct MHD_Pollfd mp; struct pollfd p[1]; #endif @@ -607,14 +657,6 @@ MHD_handle_connection (void *data) tv.tv_usec = 0; tvp = &tv; } - if ( (MHD_CONNECTION_NORMAL_BODY_UNREADY == con->state) || - (MHD_CONNECTION_CHUNKED_BODY_UNREADY == con->state) ) - { - /* do not block (we're waiting for our callback to succeed) */ - tv.tv_sec = 0; - tv.tv_usec = 0; - tvp = &tv; - } #if HTTPS_SUPPORT if (MHD_YES == con->tls_read_ready) { @@ -629,10 +671,25 @@ MHD_handle_connection (void *data) /* use select */ FD_ZERO (&rs); FD_ZERO (&ws); - FD_ZERO (&es); max = 0; - MHD_connection_get_fdset (con, &rs, &ws, &es, &max); - num_ready = SELECT (max + 1, &rs, &ws, &es, tvp); + switch (con->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + add_to_fd_set (con->socket_fd, &rs, &max); + break; + case MHD_EVENT_LOOP_INFO_WRITE: + add_to_fd_set (con->socket_fd, &ws, &max); + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + tv.tv_sec = 0; + tv.tv_usec = 0; + tvp = &tv; + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* how did we get here!? */ + goto exit; + } + num_ready = SELECT (max + 1, &rs, &ws, NULL, tvp); if (num_ready < 0) { if (EINTR == errno) @@ -648,7 +705,7 @@ MHD_handle_connection (void *data) /* call appropriate connection handler if necessary */ if ( (FD_ISSET (con->socket_fd, &rs)) #if HTTPS_SUPPORT - || (MHD_YES == con->tls_read_ready) + || (MHD_YES == con->tls_read_ready) #endif ) con->read_handler (con); @@ -661,16 +718,26 @@ MHD_handle_connection (void *data) else { /* use poll */ - memset(&mp, 0, sizeof (struct MHD_Pollfd)); - MHD_connection_get_pollfd(con, &mp); - memset(&p, 0, sizeof (p)); - p[0].fd = mp.fd; - if (mp.events & MHD_POLL_ACTION_IN) - p[0].events |= POLLIN; - if (mp.events & MHD_POLL_ACTION_OUT) - p[0].events |= POLLOUT; - if (poll (p, - 1, + memset (&p, 0, sizeof (p)); + p[0].fd = con->socket_fd; + switch (con->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + p[0].events |= POLLIN; + break; + case MHD_EVENT_LOOP_INFO_WRITE: + p[0].events |= POLLOUT; + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + tv.tv_sec = 0; + tv.tv_usec = 0; + tvp = &tv; + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* how did we get here!? */ + goto exit; + } + if (poll (p, 1, (NULL == tvp) ? -1 : tv.tv_sec * 1000) < 0) { if (EINTR == errno) @@ -732,15 +799,23 @@ recv_param_adapter (struct MHD_Connection *connection, void *other, size_t i) { + ssize_t ret; + if ( (-1 == connection->socket_fd) || (MHD_CONNECTION_CLOSED == connection->state) ) { errno = ENOTCONN; return -1; } - if (0 != (connection->daemon->options & MHD_USE_SSL)) - return RECV (connection->socket_fd, other, i, MSG_NOSIGNAL); - return RECV (connection->socket_fd, other, i, MSG_NOSIGNAL); + ret = RECV (connection->socket_fd, other, i, MSG_NOSIGNAL); +#if EPOLL_SUPPORT + if (ret < i) + { + /* partial read --- no longer read-ready */ + connection->epoll_state &= ~MHD_EPOLL_STATE_READ_READY; + } +#endif + return ret; } @@ -757,12 +832,13 @@ send_param_adapter (struct MHD_Connection *connection, const void *other, size_t i) { + ssize_t ret; #if LINUX int fd; off_t offset; off_t left; - ssize_t ret; #endif + if ( (-1 == connection->socket_fd) || (MHD_CONNECTION_CLOSED == connection->state) ) { @@ -786,7 +862,16 @@ send_param_adapter (struct MHD_Connection *connection, fd, &offset, (size_t) left))) - return ret; + { +#if EPOLL_SUPPORT + if (ret < left) + { + /* partial write --- no longer write-ready */ + connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY; + } +#endif + return ret; + } if ( (EINTR == errno) || (EAGAIN == errno) ) return 0; if ( (EINVAL == errno) || (EBADF == errno) ) @@ -797,7 +882,15 @@ send_param_adapter (struct MHD_Connection *connection, http://lists.gnu.org/archive/html/libmicrohttpd/2011-02/msg00015.html */ } #endif - return SEND (connection->socket_fd, other, i, MSG_NOSIGNAL); + ret = SEND (connection->socket_fd, other, i, MSG_NOSIGNAL); +#if EPOLL_SUPPORT + if (ret < i) + { + /* partial write --- no longer write-ready */ + connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY; + } +#endif + return ret; } @@ -890,13 +983,31 @@ MHD_add_connection (struct MHD_Daemon *daemon, { struct MHD_Connection *connection; int res_thread_create; + unsigned int i; #if OSX static int on = 1; -#endif +#endif + + if (NULL != daemon->worker_pool) + { + /* have a pool, try to find a pool with capacity; we use the + socket as the initial offset into the pool for load + balancing */ + for (i=0;i<daemon->worker_pool_size;i++) + if (0 < daemon->worker_pool[(i + client_socket) % daemon->worker_pool_size].max_connections) + return MHD_add_connection (&daemon->worker_pool[(i + client_socket) % daemon->worker_pool_size], + client_socket, + addr, addrlen); + /* all pools are at their connection limit, must refuse */ + SHUTDOWN (client_socket, SHUT_RDWR); + if (0 != CLOSE (client_socket)) + MHD_PANIC ("close failed\n"); + return MHD_NO; + } #ifndef WINDOWS if ( (client_socket >= FD_SETSIZE) && - (0 == (daemon->options & MHD_USE_POLL)) ) + (0 == (daemon->options & (MHD_USE_POLL | MHD_USE_EPOLL_LINUX_ONLY))) ) { #if HAVE_MESSAGES MHD_DLOG (daemon, @@ -905,7 +1016,8 @@ MHD_add_connection (struct MHD_Daemon *daemon, FD_SETSIZE); #endif SHUTDOWN (client_socket, SHUT_RDWR); - CLOSE (client_socket); + if (0 != CLOSE (client_socket)) + MHD_PANIC ("close failed\n"); return MHD_NO; } #endif @@ -925,7 +1037,8 @@ MHD_add_connection (struct MHD_Daemon *daemon, "Server reached connection limit (closing inbound connection)\n"); #endif SHUTDOWN (client_socket, SHUT_RDWR); - CLOSE (client_socket); + if (0 != CLOSE (client_socket)) + MHD_PANIC ("close failed\n"); return MHD_NO; } @@ -940,9 +1053,10 @@ MHD_add_connection (struct MHD_Daemon *daemon, #endif #endif SHUTDOWN (client_socket, SHUT_RDWR); - CLOSE (client_socket); + if (0 != CLOSE (client_socket)) + MHD_PANIC ("close failed\n"); MHD_ip_limit_del (daemon, addr, addrlen); - return MHD_YES; + return MHD_NO; } #if OSX @@ -963,13 +1077,29 @@ MHD_add_connection (struct MHD_Daemon *daemon, STRERROR (errno)); #endif SHUTDOWN (client_socket, SHUT_RDWR); - CLOSE (client_socket); + if (0 != CLOSE (client_socket)) + MHD_PANIC ("close failed\n"); MHD_ip_limit_del (daemon, addr, addrlen); return MHD_NO; } memset (connection, 0, sizeof (struct MHD_Connection)); + connection->pool = MHD_pool_create (daemon->pool_size); + if (NULL == connection->pool) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Error allocating memory: %s\n", + STRERROR (errno)); +#endif + SHUTDOWN (client_socket, SHUT_RDWR); + if (0 != CLOSE (client_socket)) + MHD_PANIC ("close failed\n"); + MHD_ip_limit_del (daemon, addr, addrlen); + free (connection); + return MHD_NO; + } + connection->connection_timeout = daemon->connection_timeout; - connection->pool = NULL; if (NULL == (connection->addr = malloc (addrlen))) { #if HAVE_MESSAGES @@ -978,8 +1108,10 @@ MHD_add_connection (struct MHD_Daemon *daemon, STRERROR (errno)); #endif SHUTDOWN (client_socket, SHUT_RDWR); - CLOSE (client_socket); + if (0 != CLOSE (client_socket)) + MHD_PANIC ("close failed\n"); MHD_ip_limit_del (daemon, addr, addrlen); + MHD_pool_destroy (connection->pool); free (connection); return MHD_NO; } @@ -988,7 +1120,7 @@ MHD_add_connection (struct MHD_Daemon *daemon, connection->socket_fd = client_socket; connection->daemon = daemon; connection->last_activity = MHD_monotonic_time(); - + /* set default connection handlers */ MHD_set_http_callbacks_ (connection); connection->recv_cls = &recv_param_adapter; @@ -1050,7 +1182,8 @@ MHD_add_connection (struct MHD_Daemon *daemon, daemon->cred_type); #endif SHUTDOWN (client_socket, SHUT_RDWR); - CLOSE (client_socket); + if (0 != CLOSE (client_socket)) + MHD_PANIC ("close failed\n"); MHD_ip_limit_del (daemon, addr, addrlen); free (connection->addr); free (connection); @@ -1065,16 +1198,22 @@ MHD_add_connection (struct MHD_Daemon *daemon, (gnutls_push_func) &send_param_adapter); if (daemon->https_mem_trust) - gnutls_certificate_server_set_request(connection->tls_session, GNUTLS_CERT_REQUEST); + gnutls_certificate_server_set_request (connection->tls_session, + GNUTLS_CERT_REQUEST); } #endif - if (0 != pthread_mutex_lock(&daemon->cleanup_connection_mutex)) + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_lock (&daemon->cleanup_connection_mutex)) ) MHD_PANIC ("Failed to acquire cleanup mutex\n"); + XDLL_insert (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); DLL_insert (daemon->connections_head, daemon->connections_tail, connection); - if (0 != pthread_mutex_unlock(&daemon->cleanup_connection_mutex)) + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_unlock (&daemon->cleanup_connection_mutex)) ) MHD_PANIC ("Failed to release cleanup mutex\n"); /* attempt to create handler thread */ @@ -1088,27 +1227,58 @@ MHD_add_connection (struct MHD_Daemon *daemon, MHD_DLOG (daemon, "Failed to create a thread: %s\n", STRERROR (res_thread_create)); #endif - SHUTDOWN (client_socket, SHUT_RDWR); - CLOSE (client_socket); - MHD_ip_limit_del (daemon, addr, addrlen); - if (0 != pthread_mutex_lock(&daemon->cleanup_connection_mutex)) - { - MHD_PANIC ("Failed to acquire cleanup mutex\n"); - } - DLL_remove (daemon->connections_head, - daemon->connections_tail, - connection); - if (0 != pthread_mutex_unlock(&daemon->cleanup_connection_mutex)) - { - MHD_PANIC ("Failed to release cleanup mutex\n"); - } - free (connection->addr); - free (connection); - return MHD_NO; + goto cleanup; } } - daemon->max_connections--; +#if EPOLL_SUPPORT + if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + { + struct epoll_event event; + + event.events = EPOLLIN | EPOLLOUT | EPOLLET; + event.data.ptr = connection; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + client_socket, + &event)) + { +#if HAVE_MESSAGES + if (0 != (daemon->options & MHD_USE_DEBUG)) + MHD_DLOG (daemon, + "Call to epoll_ctl failed: %s\n", + STRERROR (errno)); +#endif + goto cleanup; + } + daemon->listen_socket_in_epoll = MHD_YES; + + } +#endif + daemon->max_connections--; return MHD_YES; +#if HTTPS_SUPPORT || EPOLL_SUPPORT + cleanup: + SHUTDOWN (client_socket, SHUT_RDWR); + if (0 != CLOSE (client_socket)) + MHD_PANIC ("close failed\n"); + MHD_ip_limit_del (daemon, addr, addrlen); + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_lock (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + DLL_remove (daemon->connections_head, + daemon->connections_tail, + connection); + XDLL_remove (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_unlock (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); + MHD_pool_destroy (connection->pool); + free (connection->addr); + free (connection); + return MHD_NO; +#endif } @@ -1118,7 +1288,11 @@ MHD_add_connection (struct MHD_Daemon *daemon, * accept policy callback. * * @param daemon handle with the listen socket - * @return MHD_YES on success + * @return MHD_YES on success (connections denied by policy or due + * to 'out of memory' and similar errors) are still considered + * successful as far as MHD_accept_connection is concerned); + * a return code of MHD_NO only refers to the actual + * 'accept' system call. */ static int MHD_accept_connection (struct MHD_Daemon *daemon) @@ -1143,14 +1317,9 @@ MHD_accept_connection (struct MHD_Daemon *daemon) s = accept4 (fd, addr, &addrlen, SOCK_CLOEXEC); need_fcntl = MHD_NO; #else - s = -1; + s = ACCEPT (fd, addr, &addrlen); need_fcntl = MHD_YES; #endif - if (-1 == s) - { - s = ACCEPT (fd, addr, &addrlen); - need_fcntl = MHD_YES; - } if ((-1 == s) || (addrlen <= 0)) { #if HAVE_MESSAGES @@ -1163,11 +1332,13 @@ MHD_accept_connection (struct MHD_Daemon *daemon) if (-1 != s) { SHUTDOWN (s, SHUT_RDWR); - CLOSE (s); + if (0 != CLOSE (s)) + MHD_PANIC ("close failed\n"); /* just in case */ } return MHD_NO; } +#if !HAVE_ACCEPT4 if (MHD_YES == need_fcntl) { /* make socket non-inheritable */ @@ -1198,13 +1369,15 @@ MHD_accept_connection (struct MHD_Daemon *daemon) } #endif } +#endif #if HAVE_MESSAGES #if DEBUG_CONNECT MHD_DLOG (daemon, "Accepted connection on socket %d\n", s); #endif #endif - return MHD_add_connection (daemon, s, + (void) MHD_add_connection (daemon, s, addr, addrlen); + return MHD_YES; } @@ -1222,10 +1395,9 @@ MHD_cleanup_connections (struct MHD_Daemon *daemon) void *unused; int rc; - if (0 != pthread_mutex_lock(&daemon->cleanup_connection_mutex)) - { - MHD_PANIC ("Failed to acquire cleanup mutex\n"); - } + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_lock (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); while (NULL != (pos = daemon->cleanup_head)) { DLL_remove (daemon->cleanup_head, @@ -1245,22 +1417,39 @@ MHD_cleanup_connections (struct MHD_Daemon *daemon) gnutls_deinit (pos->tls_session); #endif MHD_ip_limit_del (daemon, (struct sockaddr*)pos->addr, pos->addr_len); +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (-1 != daemon->epoll_fd) ) + { + /* epoll documentation suggests that closing a FD + automatically removes it from the epoll set; however, + this is not true as if we fail to do manually remove it, + we are still seeing an event for this fd in epoll, + causing grief (use-after-free...) --- at least on my + system. */ + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_DEL, + pos->socket_fd, + NULL)) + MHD_PANIC ("Failed to remove FD from epoll set\n"); + } +#endif if (NULL != pos->response) { MHD_destroy_response (pos->response); pos->response = NULL; } - if (-1 != pos->socket_fd) - CLOSE (pos->socket_fd); + if ( (-1 != pos->socket_fd) && + (0 != CLOSE (pos->socket_fd)) ) + MHD_PANIC ("close failed\n"); if (NULL != pos->addr) free (pos->addr); free (pos); daemon->max_connections++; } - if (0 != pthread_mutex_unlock(&daemon->cleanup_connection_mutex)) - { - MHD_PANIC ("Failed to release cleanup mutex\n"); - } + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_unlock (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); } @@ -1292,17 +1481,20 @@ MHD_get_timeout (struct MHD_Daemon *daemon, #endif return MHD_NO; } - have_timeout = MHD_NO; - for (pos = daemon->connections_head; NULL != pos; pos = pos->next) - { + #if HTTPS_SUPPORT - if (MHD_YES == pos->tls_read_ready) - { - earliest_deadline = 0; - have_timeout = MHD_YES; - break; - } + if (0 != daemon->num_tls_read_ready) + { + /* if there is any TLS connection with data ready for + reading, we must not block in the event loop */ + *timeout = 0; + return MHD_YES; + } #endif + + have_timeout = MHD_NO; + for (pos = daemon->manual_timeout_head; NULL != pos; pos = pos->nextX) + { if (0 != pos->connection_timeout) { if ( (! have_timeout) || @@ -1316,6 +1508,22 @@ MHD_get_timeout (struct MHD_Daemon *daemon, have_timeout = MHD_YES; } } + /* normal timeouts are sorted, so we only need to look at the 'head' */ + pos = daemon->normal_timeout_head; + if ( (NULL != pos) && + (0 != pos->connection_timeout) ) + { + if ( (! have_timeout) || + (earliest_deadline > pos->last_activity + pos->connection_timeout) ) + earliest_deadline = pos->last_activity + pos->connection_timeout; +#if HTTPS_SUPPORT + if ( (0 != (daemon->options & MHD_USE_SSL)) && + (0 != gnutls_record_check_pending (pos->tls_session)) ) + earliest_deadline = 0; +#endif + have_timeout = MHD_YES; + } + if (MHD_NO == have_timeout) return MHD_NO; now = MHD_monotonic_time(); @@ -1356,10 +1564,23 @@ MHD_run_from_select (struct MHD_Daemon *daemon, struct MHD_Connection *pos; struct MHD_Connection *next; +#if EPOLL_SUPPORT + if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + { + /* we're in epoll mode, the epoll FD stands for + the entire event set! */ + if (daemon->epoll_fd >= FD_SETSIZE) + return MHD_NO; /* poll fd too big, fail hard */ + if (FD_ISSET (daemon->epoll_fd, read_fd_set)) + return MHD_run (daemon); + return MHD_YES; + } +#endif + /* select connection thread handling type */ if ( (-1 != (ds = daemon->socket_fd)) && (FD_ISSET (ds, read_fd_set)) ) - MHD_accept_connection (daemon); + (void) MHD_accept_connection (daemon); /* drain signaling pipe to avoid spinning select */ if ( (-1 != daemon->wpipe[0]) && (FD_ISSET (daemon->wpipe[0], read_fd_set)) ) @@ -1373,18 +1594,30 @@ MHD_run_from_select (struct MHD_Daemon *daemon, { next = pos->next; ds = pos->socket_fd; - if (ds != -1) - { - if ( (FD_ISSET (ds, read_fd_set)) + if (-1 == ds) + continue; + switch (pos->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + if ( (FD_ISSET (ds, read_fd_set)) #if HTTPS_SUPPORT || (MHD_YES == pos->tls_read_ready) #endif ) - pos->read_handler (pos); - if (FD_ISSET (ds, write_fd_set)) - pos->write_handler (pos); - pos->idle_handler (pos); - } + pos->read_handler (pos); + break; + case MHD_EVENT_LOOP_INFO_WRITE: + if (FD_ISSET (ds, write_fd_set)) + pos->write_handler (pos); + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + /* only idle handler */ + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* should never happen */ + break; + } + pos->idle_handler (pos); } } return MHD_YES; @@ -1502,11 +1735,11 @@ MHD_poll_all (struct MHD_Daemon *daemon, /* 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 ( (MHD_EVENT_LOOP_INFO_READ == pos->event_loop_info) || + (MHD_EVENT_LOOP_INFO_WRITE == pos->event_loop_info) ) + num_connections++; { struct pollfd p[2 + num_connections]; - struct MHD_Pollfd mp; MHD_UNSIGNED_LONG_LONG ltimeout; unsigned int i; int timeout; @@ -1544,13 +1777,22 @@ MHD_poll_all (struct MHD_Daemon *daemon, i = 0; for (pos = daemon->connections_head; NULL != pos; pos = pos->next) { - memset(&mp, 0, sizeof (struct MHD_Pollfd)); - MHD_connection_get_pollfd (pos, &mp); - p[poll_server+i].fd = mp.fd; - if (mp.events & MHD_POLL_ACTION_IN) - p[poll_server+i].events |= POLLIN; - if (mp.events & MHD_POLL_ACTION_OUT) - p[poll_server+i].events |= POLLOUT; + p[poll_server+i].fd = pos->socket_fd; + switch (pos->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + p[poll_server+i].events |= POLLIN; + break; + case MHD_EVENT_LOOP_INFO_WRITE: + p[poll_server+i].events |= POLLOUT; + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + /* not in poll */ + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* should never happen */ + break; + } i++; } if (0 == poll_server + num_connections) @@ -1574,24 +1816,44 @@ MHD_poll_all (struct MHD_Daemon *daemon, while (NULL != (pos = next)) { next = pos->next; - /* first, sanity checks */ - if (i >= num_connections) - break; /* connection list changed somehow, retry later ... */ - MHD_connection_get_pollfd (pos, &mp); - if (p[poll_server+i].fd != mp.fd) - break; /* fd mismatch, something else happened, retry later ... */ - - /* normal handling */ - if (0 != (p[poll_server+i].revents & POLLIN)) - pos->read_handler (pos); - if (0 != (p[poll_server+i].revents & POLLOUT)) - pos->write_handler (pos); - pos->idle_handler (pos); - i++; + switch (pos->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + /* first, sanity checks */ + if (i >= num_connections) + break; /* connection list changed somehow, retry later ... */ + if (p[poll_server+i].fd != pos->socket_fd) + break; /* fd mismatch, something else happened, retry later ... */ + /* normal handling */ + if (0 != (p[poll_server+i].revents & POLLIN)) + pos->read_handler (pos); + pos->idle_handler (pos); + i++; + break; + case MHD_EVENT_LOOP_INFO_WRITE: + /* first, sanity checks */ + if (i >= num_connections) + break; /* connection list changed somehow, retry later ... */ + if (p[poll_server+i].fd != pos->socket_fd) + break; /* fd mismatch, something else happened, retry later ... */ + /* normal handling */ + if (0 != (p[poll_server+i].revents & POLLOUT)) + pos->write_handler (pos); + pos->idle_handler (pos); + i++; + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + pos->idle_handler (pos); + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* should never happen */ + break; + } } + /* handle 'listen' FD */ if ( (-1 != poll_listen) && (0 != (p[poll_listen].revents & POLLIN)) ) - MHD_accept_connection (daemon); + (void) MHD_accept_connection (daemon); } return MHD_YES; } @@ -1651,7 +1913,7 @@ MHD_poll_listen_socket (struct MHD_Daemon *daemon, return MHD_NO; if ( (-1 != poll_listen) && (0 != (p[poll_listen].revents & POLLIN)) ) - MHD_accept_connection (daemon); + (void) MHD_accept_connection (daemon); return MHD_YES; } #endif @@ -1681,6 +1943,204 @@ MHD_poll (struct MHD_Daemon *daemon, } +#if 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 + + +/** + * Do 'epoll'-based processing (this function is allowed to + * block). + * + * @param daemon daemon to run poll loop for + * @param may_block YES if blocking, NO if non-blocking + * @return MHD_NO on serious errors, MHD_YES on success + */ +static int +MHD_epoll (struct MHD_Daemon *daemon, + int may_block) +{ + struct MHD_Connection *pos; + struct MHD_Connection *next; + 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; + + if (-1 == daemon->epoll_fd) + return MHD_NO; /* we're down! */ + if (MHD_YES == daemon->shutdown) + return MHD_NO; + if ( (-1 != daemon->socket_fd) && + (0 != daemon->max_connections) && + (MHD_NO == daemon->listen_socket_in_epoll) ) + { + event.events = EPOLLIN; + event.data.ptr = daemon; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + daemon->socket_fd, + &event)) + { +#if HAVE_MESSAGES + if (0 != (daemon->options & MHD_USE_DEBUG)) + MHD_DLOG (daemon, + "Call to epoll_ctl failed: %s\n", + STRERROR (errno)); +#endif + return MHD_NO; + } + daemon->listen_socket_in_epoll = MHD_YES; + } + if ( (MHD_YES == daemon->listen_socket_in_epoll) && + (0 == daemon->max_connections) ) + { + /* we're at the connection limit, disable listen socket + for event loop for now */ + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_DEL, + daemon->socket_fd, + NULL)) + MHD_PANIC ("Failed to remove listen FD from epoll set\n"); + daemon->listen_socket_in_epoll = MHD_NO; + } + if (MHD_YES == may_block) + { + if (MHD_YES == MHD_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; + + /* 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) + { + if (EINTR == errno) + return MHD_YES; +#if HAVE_MESSAGES + if (0 != (daemon->options & MHD_USE_DEBUG)) + MHD_DLOG (daemon, + "Call to epoll_wait failed: %s\n", + STRERROR (errno)); +#endif + return MHD_NO; + } + for (i=0;i<num_events;i++) + { + if (NULL == events[i].data.ptr) + continue; /* shutdown signal! */ + if (daemon != events[i].data.ptr) + { + /* 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; + if (0 != (events[i].events & EPOLLIN)) + { + pos->epoll_state |= MHD_EPOLL_STATE_READ_READY; + if ( (MHD_EVENT_LOOP_INFO_READ == pos->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 (0 != (events[i].events & EPOLLOUT)) + { + pos->epoll_state |= MHD_EPOLL_STATE_WRITE_READY; + if ( (MHD_EVENT_LOOP_INFO_WRITE == pos->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; + } + } + } + else /* must be listen socket */ + { + /* run 'accept' until it fails or we are not allowed to take + on more connections */ + while ( (MHD_YES == MHD_accept_connection (daemon)) && + (0 != daemon->max_connections) ) ; + } + } + } + + /* process events for connections */ + while (NULL != (pos = daemon->eready_tail)) + { + EDLL_remove (daemon->eready_head, + daemon->eready_tail, + pos); + pos->epoll_state -= MHD_EPOLL_STATE_IN_EREADY_EDLL; + if (MHD_EVENT_LOOP_INFO_READ == pos->event_loop_info) + pos->read_handler (pos); + if (MHD_EVENT_LOOP_INFO_WRITE == pos->event_loop_info) + pos->write_handler (pos); + pos->idle_handler (pos); + } + /* Finally, handle timed-out connections; we need to do this here + as the epoll mechanism won't call the 'idle_handler' 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. */ + next = daemon->manual_timeout_head; + while (NULL != (pos = next)) + { + next = pos->nextX; + pos->idle_handler (pos); + } + /* Connections with the default timeout are sorted by prepending + them to the head of the list whenever we touch the connection; + thus it sufficies to iterate from the tail until the first + connection is NOT timed out */ + next = daemon->normal_timeout_tail; + while (NULL != (pos = next)) + { + next = pos->prevX; + pos->idle_handler (pos); + if (MHD_CONNECTION_CLOSED != pos->state) + break; /* sorted by timeout, no need to visit the rest! */ + } + return MHD_YES; +} +#endif + + /** * Run webserver operations (without blocking unless * in client callbacks). This method should be called @@ -1702,10 +2162,14 @@ MHD_run (struct MHD_Daemon *daemon) (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) || (0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)) ) return MHD_NO; - if (0 == (daemon->options & MHD_USE_POLL)) - MHD_select (daemon, MHD_NO); - else + if (0 != (daemon->options & MHD_USE_POLL)) MHD_poll (daemon, MHD_NO); +#if EPOLL_SUPPORT + else if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + MHD_epoll (daemon, MHD_NO); +#endif + else + MHD_select (daemon, MHD_NO); MHD_cleanup_connections (daemon); return MHD_YES; } @@ -1725,10 +2189,14 @@ MHD_select_thread (void *cls) while (MHD_YES != daemon->shutdown) { - if (0 == (daemon->options & MHD_USE_POLL)) - MHD_select (daemon, MHD_YES); + if (0 != (daemon->options & MHD_USE_POLL)) + MHD_poll (daemon, MHD_YES); +#if EPOLL_SUPPORT + else if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + MHD_epoll (daemon, MHD_YES); +#endif else - MHD_poll (daemon, MHD_YES); + MHD_select (daemon, MHD_YES); MHD_cleanup_connections (daemon); } return NULL; @@ -1784,10 +2252,50 @@ MHD_quiesce_daemon (struct MHD_Daemon *daemon) int ret; ret = daemon->socket_fd; + if (-1 == ret) + return -1; + if ( (-1 == daemon->wpipe[1]) && + (0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Using MHD_quiesce_daemon in this mode requires MHD_USE_PIPE_FOR_SHUTDOWN\n"); +#endif + return -1; + } + if (NULL != daemon->worker_pool) - for (i = 0; i < daemon->worker_pool_size; i++) - daemon->worker_pool[i].socket_fd = -1; + for (i = 0; i < daemon->worker_pool_size; i++) + { + daemon->worker_pool[i].socket_fd = -1; +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (-1 != daemon->worker_pool[i].epoll_fd) && + (MHD_YES == daemon->worker_pool[i].listen_socket_in_epoll) ) + { + if (0 != epoll_ctl (daemon->worker_pool[i].epoll_fd, + EPOLL_CTL_DEL, + ret, + NULL)) + MHD_PANIC ("Failed to remove listen FD from epoll set\n"); + daemon->worker_pool[i].listen_socket_in_epoll = MHD_NO; + } +#endif + } daemon->socket_fd = -1; +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (-1 != daemon->epoll_fd) && + (MHD_YES == daemon->listen_socket_in_epoll) ) + { + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_DEL, + ret, + NULL)) + MHD_PANIC ("Failed to remove listen FD from epoll set\n"); + daemon->listen_socket_in_epoll = MHD_NO; + } +#endif return ret; } @@ -2157,6 +2665,54 @@ create_socket (int domain, int type, int protocol) } +#if EPOLL_SUPPORT +/** + * Setup epoll FD for the daemon and initialize it to listen + * on the listen FD. + * + * @param daemon daemon to initialize for epoll + * @return MHD_YES on success, MHD_NO on failure + */ +static int +setup_epoll_to_listen (struct MHD_Daemon *daemon) +{ + struct epoll_event event; + + daemon->epoll_fd = epoll_create1 (EPOLL_CLOEXEC); + if (-1 == daemon->epoll_fd) + { +#if HAVE_MESSAGES + if (0 != (daemon->options & MHD_USE_DEBUG)) + MHD_DLOG (daemon, + "Call to epoll_create1 failed: %s\n", + STRERROR (errno)); +#endif + return MHD_NO; + } + if (-1 == daemon->socket_fd) + return MHD_YES; /* non-listening daemon */ + event.events = EPOLLIN; + event.data.ptr = daemon; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + daemon->socket_fd, + &event)) + { +#if HAVE_MESSAGES + if (0 != (daemon->options & MHD_USE_DEBUG)) + MHD_DLOG (daemon, + "Call to epoll_ctl failed: %s\n", + STRERROR (errno)); +#endif + return MHD_NO; + } + daemon->listen_socket_in_epoll = MHD_YES; + return MHD_YES; +} +#endif + + + /** * Start a webserver on the given port. * @@ -2209,6 +2765,9 @@ MHD_start_daemon_va (unsigned int flags, if (NULL == (daemon = malloc (sizeof (struct MHD_Daemon)))) return NULL; memset (daemon, 0, sizeof (struct MHD_Daemon)); +#if EPOLL_SUPPORT + daemon->epoll_fd = -1; +#endif /* try to open listen socket */ #if HTTPS_SUPPORT if (0 != (flags & MHD_USE_SSL)) @@ -2236,10 +2795,12 @@ MHD_start_daemon_va (unsigned int flags, daemon->custom_error_log_cls = stderr; #endif #ifdef HAVE_LISTEN_SHUTDOWN - use_pipe = (0 != (daemon->options & MHD_USE_NO_LISTEN_SOCKET)); + use_pipe = (0 != (daemon->options & (MHD_USE_NO_LISTEN_SOCKET | MHD_USE_PIPE_FOR_SHUTDOWN))); #else use_pipe = 1; /* yes, must use pipe to signal shutdown */ #endif + if (0 == (flags & (MHD_USE_SELECT_INTERNALLY | MHD_USE_THREAD_PER_CONNECTION))) + use_pipe = 0; /* useless if we are using 'external' select */ if ( (use_pipe) && (0 != PIPE (daemon->wpipe)) ) { @@ -2259,8 +2820,10 @@ MHD_start_daemon_va (unsigned int flags, MHD_DLOG (daemon, "file descriptor for control pipe exceeds maximum value\n"); #endif - CLOSE (daemon->wpipe[0]); - CLOSE (daemon->wpipe[1]); + if (0 != CLOSE (daemon->wpipe[0])) + MHD_PANIC ("close failed\n"); + if (0 != CLOSE (daemon->wpipe[1])) + MHD_PANIC ("close failed\n"); free (daemon); return NULL; } @@ -2291,7 +2854,7 @@ MHD_start_daemon_va (unsigned int flags, #ifdef DAUTH_SUPPORT if (daemon->nonce_nc_size > 0) { - if ( ( (size_t) (daemon->nonce_nc_size * sizeof(struct MHD_NonceNc))) / + if ( ( (size_t) (daemon->nonce_nc_size * sizeof (struct MHD_NonceNc))) / sizeof(struct MHD_NonceNc) != daemon->nonce_nc_size) { #if HAVE_MESSAGES @@ -2305,7 +2868,7 @@ MHD_start_daemon_va (unsigned int flags, free (daemon); return NULL; } - daemon->nnc = malloc (daemon->nonce_nc_size * sizeof(struct MHD_NonceNc)); + daemon->nnc = malloc (daemon->nonce_nc_size * sizeof (struct MHD_NonceNc)); if (NULL == daemon->nnc) { #if HAVE_MESSAGES @@ -2359,6 +2922,32 @@ MHD_start_daemon_va (unsigned int flags, goto free_and_fail; } #endif +#if EPOLL_SUPPORT + if ( (0 != (flags & MHD_USE_EPOLL_LINUX_ONLY)) && + (0 == daemon->worker_pool_size) && + (0 == (daemon->options & MHD_USE_NO_LISTEN_SOCKET)) ) + { + if (0 != (flags & MHD_USE_THREAD_PER_CONNECTION)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Combining MHD_USE_THREAD_PER_CONNECTION and MHD_USE_EPOLL_LINUX_ONLY is not supported.\n"); +#endif + goto free_and_fail; + } + if (MHD_YES != setup_epoll_to_listen (daemon)) + goto free_and_fail; + } +#else + if (0 != (flags & MHD_USE_EPOLL_LINUX_ONLY)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "epoll is not supported on this platform by this build.\n"); +#endif + goto free_and_fail; + } +#endif if ( (-1 == daemon->socket_fd) && (0 == (daemon->options & MHD_USE_NO_LISTEN_SOCKET)) ) { @@ -2377,10 +2966,11 @@ MHD_start_daemon_va (unsigned int flags, #endif goto free_and_fail; } - if ((SETSOCKOPT (socket_fd, - SOL_SOCKET, - SO_REUSEADDR, - &on, sizeof (on)) < 0) && ((flags & MHD_USE_DEBUG) != 0)) + if ( (0 > SETSOCKOPT (socket_fd, + SOL_SOCKET, + SO_REUSEADDR, + &on, sizeof (on))) && + (0 != (flags & MHD_USE_DEBUG)) ) { #if HAVE_MESSAGES MHD_DLOG (daemon, @@ -2423,25 +3013,31 @@ MHD_start_daemon_va (unsigned int flags, } daemon->socket_fd = socket_fd; - if (0 != (flags & MHD_USE_IPv6)) + if ( (0 != (flags & MHD_USE_IPv6)) && + (0 == (flags & MHD_USE_DUAL_STACK)) ) { #ifdef IPPROTO_IPV6 #ifdef IPV6_V6ONLY /* Note: "IPV6_V6ONLY" is declared by Windows Vista ff., see "IPPROTO_IPV6 Socket Options" (http://msdn.microsoft.com/en-us/library/ms738574%28v=VS.85%29.aspx); and may also be missing on older POSIX systems; good luck if you have any of those, - your IPv6 socket may then also bind against IPv4... */ + your IPv6 socket may then also bind against IPv4 anyway... */ #ifndef WINDOWS const int on = 1; - setsockopt (socket_fd, - IPPROTO_IPV6, IPV6_V6ONLY, - &on, sizeof (on)); #else const char on = 1; - setsockopt (socket_fd, - IPPROTO_IPV6, IPV6_V6ONLY, - &on, sizeof (on)); #endif + if ( (0 > SETSOCKOPT (socket_fd, + IPPROTO_IPV6, IPV6_V6ONLY, + &on, sizeof (on))) && + (0 != (flags & MHD_USE_DEBUG)) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + STRERROR (errno)); +#endif + } #endif #endif } @@ -2454,11 +3050,26 @@ MHD_start_daemon_va (unsigned int flags, (unsigned int) port, STRERROR (errno)); #endif - CLOSE (socket_fd); + if (0 != CLOSE (socket_fd)) + MHD_PANIC ("close failed\n"); goto free_and_fail; } - - if (LISTEN (socket_fd, 20) < 0) +#if EPOLL_SUPPORT + if (0 != (flags & MHD_USE_EPOLL_LINUX_ONLY)) + { + int sk_flags = fcntl (socket_fd, F_GETFL); + if (0 != fcntl (socket_fd, F_SETFL, sk_flags | O_NONBLOCK)) + { + MHD_DLOG (daemon, + "Failed to make listen socket non-blocking: %s\n", + STRERROR (errno)); + if (0 != CLOSE (socket_fd)) + MHD_PANIC ("close failed\n"); + goto free_and_fail; + } + } +#endif + if (LISTEN (socket_fd, 32) < 0) { #if HAVE_MESSAGES if (0 != (flags & MHD_USE_DEBUG)) @@ -2466,7 +3077,8 @@ MHD_start_daemon_va (unsigned int flags, "Failed to listen for connections: %s\n", STRERROR (errno)); #endif - CLOSE (socket_fd); + if (0 != CLOSE (socket_fd)) + MHD_PANIC ("close failed\n"); goto free_and_fail; } } @@ -2476,7 +3088,7 @@ MHD_start_daemon_va (unsigned int flags, } #ifndef WINDOWS if ( (socket_fd >= FD_SETSIZE) && - (0 == (flags & MHD_USE_POLL)) ) + (0 == (flags & (MHD_USE_POLL | MHD_USE_EPOLL_LINUX_ONLY)) ) ) { #if HAVE_MESSAGES if ((flags & MHD_USE_DEBUG) != 0) @@ -2485,7 +3097,8 @@ MHD_start_daemon_va (unsigned int flags, socket_fd, FD_SETSIZE); #endif - CLOSE (socket_fd); + if (0 != CLOSE (socket_fd)) + MHD_PANIC ("close failed\n"); goto free_and_fail; } #endif @@ -2496,8 +3109,9 @@ MHD_start_daemon_va (unsigned int flags, MHD_DLOG (daemon, "MHD failed to initialize IP connection limit mutex\n"); #endif - if (-1 != socket_fd) - CLOSE (socket_fd); + if ( (-1 != socket_fd) && + (0 != CLOSE (socket_fd)) ) + MHD_PANIC ("close failed\n"); goto free_and_fail; } if (0 != pthread_mutex_init (&daemon->cleanup_connection_mutex, NULL)) @@ -2507,8 +3121,9 @@ MHD_start_daemon_va (unsigned int flags, "MHD failed to initialize IP connection limit mutex\n"); #endif pthread_mutex_destroy (&daemon->cleanup_connection_mutex); - if (-1 != socket_fd) - CLOSE (socket_fd); + if ( (-1 != socket_fd) && + (0 != CLOSE (socket_fd)) ) + MHD_PANIC ("close failed\n"); goto free_and_fail; } @@ -2520,8 +3135,9 @@ MHD_start_daemon_va (unsigned int flags, MHD_DLOG (daemon, "Failed to initialize TLS support\n"); #endif - if (-1 != socket_fd) - CLOSE (socket_fd); + if ( (-1 != socket_fd) && + (0 != CLOSE (socket_fd)) ) + MHD_PANIC ("close failed\n"); pthread_mutex_destroy (&daemon->cleanup_connection_mutex); pthread_mutex_destroy (&daemon->per_ip_connection_mutex); goto free_and_fail; @@ -2541,8 +3157,9 @@ MHD_start_daemon_va (unsigned int flags, #endif pthread_mutex_destroy (&daemon->cleanup_connection_mutex); pthread_mutex_destroy (&daemon->per_ip_connection_mutex); - if (-1 != socket_fd) - CLOSE (socket_fd); + if ( (-1 != socket_fd) && + (0 != CLOSE (socket_fd)) ) + MHD_PANIC ("close failed\n"); goto free_and_fail; } if ( (daemon->worker_pool_size > 0) && @@ -2596,8 +3213,8 @@ MHD_start_daemon_va (unsigned int flags, { /* Create copy of the Daemon object for each worker */ struct MHD_Daemon *d = &daemon->worker_pool[i]; - memcpy (d, daemon, sizeof (struct MHD_Daemon)); + memcpy (d, daemon, sizeof (struct MHD_Daemon)); /* Adjust pooling params for worker daemons; note that memcpy() has already copied MHD_USE_SELECT_INTERNALLY thread model into the worker threads. */ @@ -2611,7 +3228,11 @@ MHD_start_daemon_va (unsigned int flags, d->max_connections = conns_per_thread; if (i < leftover_conns) ++d->max_connections; - +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (MHD_YES != setup_epoll_to_listen (d)) ) + goto thread_failed; +#endif /* Must init cleanup connection mutex for each worker */ if (0 != pthread_mutex_init (&d->cleanup_connection_mutex, NULL)) { @@ -2623,7 +3244,8 @@ MHD_start_daemon_va (unsigned int flags, } /* Spawn the worker thread */ - if (0 != (res_thread_create = create_thread (&d->pid, daemon, &MHD_select_thread, d))) + if (0 != (res_thread_create = + create_thread (&d->pid, daemon, &MHD_select_thread, d))) { #if HAVE_MESSAGES MHD_DLOG (daemon, @@ -2646,8 +3268,9 @@ thread_failed: MHD_USE_SELECT_INTERNALLY mode. */ if (0 == i) { - if (-1 != socket_fd) - CLOSE (socket_fd); + if ( (-1 != socket_fd) && + (0 != CLOSE (socket_fd)) ) + MHD_PANIC ("close failed\n"); pthread_mutex_destroy (&daemon->cleanup_connection_mutex); pthread_mutex_destroy (&daemon->per_ip_connection_mutex); if (NULL != daemon->worker_pool) @@ -2666,6 +3289,8 @@ thread_failed: free_and_fail: /* clean up basic memory state in 'daemon' and return NULL to indicate failure */ + if (-1 != daemon->epoll_fd) + close (daemon->epoll_fd); #ifdef DAUTH_SUPPORT free (daemon->nnc); pthread_mutex_destroy (&daemon->nnc_lock); @@ -2680,6 +3305,37 @@ thread_failed: /** + * Close the given connection, remove it from all of its + * DLLs and move it into the cleanup queue. + * + * @param pos connection to move to cleanup + */ +static void +close_connection (struct MHD_Connection *pos) +{ + struct MHD_Daemon *daemon = pos->daemon; + + MHD_connection_close (pos, + MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN); + if (pos->connection_timeout == pos->daemon->connection_timeout) + XDLL_remove (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + pos); + else + XDLL_remove (daemon->manual_timeout_head, + daemon->manual_timeout_tail, + pos); + DLL_remove (daemon->connections_head, + daemon->connections_tail, + pos); + pos->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; + DLL_insert (daemon->cleanup_head, + daemon->cleanup_tail, + pos); +} + + +/** * Close all connections for the daemon; must only be called after * all of the threads have been joined and there is no more concurrent * activity on the connection lists. @@ -2695,47 +3351,61 @@ close_all_connections (struct MHD_Daemon *daemon) /* first, make sure all threads are aware of shutdown; need to traverse DLLs in peace... */ - if (0 != pthread_mutex_lock(&daemon->cleanup_connection_mutex)) - { - MHD_PANIC ("Failed to acquire cleanup mutex\n"); - } - for (pos = daemon->connections_head; NULL != pos; pos = pos->next) + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_lock (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + for (pos = daemon->connections_head; NULL != pos; pos = pos->nextX) SHUTDOWN (pos->socket_fd, (pos->read_closed == MHD_YES) ? SHUT_WR : SHUT_RDWR); - if (0 != pthread_mutex_unlock (&daemon->cleanup_connection_mutex)) - { - MHD_PANIC ("Failed to release cleanup mutex\n"); - } + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (0 != pthread_mutex_unlock (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); - /* now, collect threads */ + /* now, collect threads from thread pool */ if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) { while (NULL != (pos = daemon->connections_head)) { - if (0 != (rc = pthread_join (pos->pid, &unused))) - { - MHD_PANIC ("Failed to join a thread\n"); - } + if (0 != (rc = pthread_join (pos->pid, &unused))) + MHD_PANIC ("Failed to join a thread\n"); pos->thread_joined = MHD_YES; } } /* now that we're alone, move everyone to cleanup */ while (NULL != (pos = daemon->connections_head)) - { - MHD_connection_close (pos, - MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN); - DLL_remove (daemon->connections_head, - daemon->connections_tail, - pos); - DLL_insert (daemon->cleanup_head, - daemon->cleanup_tail, - pos); - } + close_connection (pos); MHD_cleanup_connections (daemon); } +#if EPOLL_SUPPORT +/** + * Shutdown 'epoll' event loop by adding 'wpipe' to its event set. + * + * @param daemon daemon of which the epoll instance must be signalled + */ +static void +epoll_shutdown (struct MHD_Daemon *daemon) +{ + struct epoll_event event; + + if (-1 == daemon->wpipe[1]) + { + /* wpipe was required in this mode, how could this happen? */ + MHD_PANIC ("Internal error\n"); + } + event.events = EPOLLOUT; + event.data.ptr = NULL; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + daemon->wpipe[1], + &event)) + MHD_PANIC ("Failed to add wpipe to epoll set to signal termination\n"); +} +#endif + + /** * Shutdown an http daemon * @@ -2762,6 +3432,12 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) { daemon->worker_pool[i].shutdown = MHD_YES; daemon->worker_pool[i].socket_fd = -1; +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (-1 != daemon->worker_pool[i].epoll_fd) && + (-1 == fd) ) + epoll_shutdown (&daemon->worker_pool[i]); +#endif } } if (-1 != daemon->wpipe[1]) @@ -2777,6 +3453,13 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) (void) SHUTDOWN (fd, SHUT_RDWR); } #endif +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (-1 != daemon->epoll_fd) && + (-1 == fd) ) + epoll_shutdown (daemon); +#endif + #if DEBUG_CLOSE #if HAVE_MESSAGES MHD_DLOG (daemon, "MHD listen socket shutdown\n"); @@ -2790,12 +3473,13 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) /* MHD_USE_NO_LISTEN_SOCKET disables thread pools, hence we need to check */ for (i = 0; i < daemon->worker_pool_size; ++i) { - if (0 != (rc = pthread_join (daemon->worker_pool[i].pid, &unused))) - { - MHD_PANIC ("Failed to join a thread\n"); - } - close_all_connections (&daemon->worker_pool[i]); + if (0 != (rc = pthread_join (daemon->worker_pool[i].pid, &unused))) + MHD_PANIC ("Failed to join a thread\n"); + close_all_connections (&daemon->worker_pool[i]); pthread_mutex_destroy (&daemon->worker_pool[i].cleanup_connection_mutex); + if ( (-1 != daemon->worker_pool[i].epoll_fd) && + (0 != CLOSE (daemon->worker_pool[i].epoll_fd)) ) + MHD_PANIC ("close failed\n"); } free (daemon->worker_pool); } @@ -2813,8 +3497,9 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) } } close_all_connections (daemon); - if (-1 != fd) - (void) CLOSE (fd); + if ( (-1 != fd) && + (0 != CLOSE (fd)) ) + MHD_PANIC ("close failed\n"); /* TLS clean up */ #if HTTPS_SUPPORT @@ -2825,6 +3510,12 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) gnutls_certificate_free_credentials (daemon->x509_cred); } #endif +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (-1 != daemon->epoll_fd) && + (0 != CLOSE (daemon->epoll_fd)) ) + MHD_PANIC ("close failed\n"); +#endif #ifdef DAUTH_SUPPORT free (daemon->nnc); @@ -2835,8 +3526,10 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) if (-1 != daemon->wpipe[1]) { - (void) CLOSE (daemon->wpipe[0]); - (void) CLOSE (daemon->wpipe[1]); + if (0 != CLOSE (daemon->wpipe[0])) + MHD_PANIC ("close failed\n"); + if (0 != CLOSE (daemon->wpipe[1])) + MHD_PANIC ("close failed\n"); } free (daemon); } @@ -2858,8 +3551,16 @@ MHD_get_daemon_info (struct MHD_Daemon *daemon, { switch (infoType) { + case MHD_DAEMON_INFO_KEY_SIZE: + return NULL; /* no longer supported */ + case MHD_DAEMON_INFO_MAC_KEY_SIZE: + return NULL; /* no longer supported */ case MHD_DAEMON_INFO_LISTEN_FD: return (const union MHD_DaemonInfo *) &daemon->socket_fd; +#if EPOLL_SUPPORT + case MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY: + return (const union MHD_DaemonInfo *) &daemon->epoll_fd; +#endif default: return NULL; }; diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h @@ -1,6 +1,6 @@ /* This file is part of libmicrohttpd - (C) 2007, 2008, 2009, 2010, 2011, 2012 Daniel Pittman and Christian Grothoff + (C) 2007-2013 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 @@ -32,6 +32,10 @@ #if HTTPS_SUPPORT #include <gnutls/gnutls.h> #endif +#if EPOLL_SUPPORT +#include <sys/epoll.h> +#endif + /** * Should we perform additional sanity checks at runtime (on our internal @@ -69,63 +73,67 @@ extern void *mhd_panic_cls; #define MHD_PANIC(msg) mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL) #endif + /** - * Events we care about with respect to poll/select - * for file descriptors. + * State of the socket with respect to epoll (bitmask). */ -enum MHD_PollActions +enum MHD_EpollState { + /** - * No event interests us. + * The socket is not involved with a defined state in epoll right + * now. */ - MHD_POLL_ACTION_NOTHING = 0, + MHD_EPOLL_STATE_UNREADY = 0, /** - * We would like to read. + * epoll told us that data was ready for reading, and we did + * not consume all of it yet. */ - MHD_POLL_ACTION_IN = 1, + MHD_EPOLL_STATE_READ_READY = 1, /** - * We would like to write. - */ - MHD_POLL_ACTION_OUT = 2 + * epoll told us that space was available for writing, and we did + * not consume all of it yet. + */ + MHD_EPOLL_STATE_WRITE_READY = 2, + + /** + * Is this connection currently in the 'eready' EDLL? + */ + MHD_EPOLL_STATE_IN_EREADY_EDLL = 4 + }; /** - * State of the socket with respect to epoll. + * What is this connection waiting for? */ -enum MHD_EpollState +enum MHD_ConnectionEventLoopInfo { - /** - * + * We are waiting to be able to read. */ - MHD_EPOLL_STATE_NOTHING = 0 + MHD_EVENT_LOOP_INFO_READ = 0, + /** + * We are waiting to be able to write. + */ + MHD_EVENT_LOOP_INFO_WRITE = 1, + /** + * We are waiting for the application to provide data. + */ + MHD_EVENT_LOOP_INFO_BLOCK = 2, + /** + * We are finished and are awaiting cleanup. + */ + MHD_EVENT_LOOP_INFO_CLEANUP = 3 }; /** - * Socket descriptor and events we care about. - */ -struct MHD_Pollfd -{ - /** - * Socket descriptor. - */ - int fd; - - /** - * Which events do we care about for this socket? - */ - enum MHD_PollActions events; -}; - - -/** * Maximum length of a nonce in digest authentication. 32(MD5 Hex) + * 8(Timestamp Hex) + 1(NULL); hence 41 should suffice, but Opera * (already) takes more (see Mantis #1633), so we've increased the @@ -477,17 +485,39 @@ typedef ssize_t (*TransmitCallback) (struct MHD_Connection * conn, struct MHD_Connection { +#if EPOLL_SUPPORT /** - * This is a doubly-linked list. + * Next pointer for the EDLL listing connections that are epoll-ready. + */ + struct MHD_Connection *nextE; + + /** + * Previous pointer for the EDLL listing connections that are epoll-ready. + */ + struct MHD_Connection *prevE; +#endif + + /** + * Next pointer for the DLL describing our IO state. */ struct MHD_Connection *next; /** - * This is a doubly-linked list. + * Previous pointer for the DLL describing our IO state. */ struct MHD_Connection *prev; /** + * Next pointer for the XDLL organizing connections by timeout. + */ + struct MHD_Connection *nextX; + + /** + * Previous pointer for the XDLL organizing connections by timeout. + */ + struct MHD_Connection *prevX; + + /** * Reference to the MHD_Daemon struct. */ struct MHD_Daemon *daemon; @@ -667,11 +697,10 @@ struct MHD_Connection int socket_fd; /** - * Has this socket been closed for reading (i.e. - * other side closed the connection)? If so, - * we must completely close the connection once - * we are done sending our response (and stop - * trying to read from this socket). + * Has this socket been closed for reading (i.e. other side closed + * the connection)? If so, we must completely close the connection + * once we are done sending our response (and stop trying to read + * from this socket). */ int read_closed; @@ -680,12 +709,24 @@ struct MHD_Connection */ int thread_joined; +#if EPOLL_SUPPORT + /** + * What is the state of this socket in relation to epoll? + */ + enum MHD_EpollState epoll_state; +#endif + /** * State in the FSM for this connection. */ enum MHD_CONNECTION_STATE state; /** + * What is this connection waiting for? + */ + enum MHD_ConnectionEventLoopInfo event_loop_info; + + /** * HTTP response code. Only valid if response object * is already set. */ @@ -797,7 +838,11 @@ typedef size_t (*UnescapeCallback)(void *cls, /** - * State kept for each MHD daemon. + * State kept for each MHD daemon. All connections are kept in two + * doubly-linked lists. The first one reflects the state of the + * connection in terms of what operations we are waiting for (read, + * write, locally blocked, cleanup) whereas the second is about its + * timeout state (default or custom). */ struct MHD_Daemon { @@ -813,7 +858,7 @@ struct MHD_Daemon void *default_handler_cls; /** - * Tail of doubly-linked list of our current, active connections. + * Head of doubly-linked list of our current, active connections. */ struct MHD_Connection *connections_head; @@ -823,7 +868,7 @@ struct MHD_Daemon struct MHD_Connection *connections_tail; /** - * Tail of doubly-linked list of connections to clean up. + * Head of doubly-linked list of connections to clean up. */ struct MHD_Connection *cleanup_head; @@ -832,10 +877,54 @@ struct MHD_Daemon */ struct MHD_Connection *cleanup_tail; +#if EPOLL_SUPPORT + /** + * Head of EDLL of connections ready for processing (in epoll mode). + */ + struct MHD_Connection *eready_head; + + /** + * Tail of EDLL of connections ready for processing (in epoll mode) + */ + struct MHD_Connection *eready_tail; +#endif + + /** + * Head of the XDLL of ALL connections with a default ('normal') + * timeout, sorted by timeout (earliest at the tail, most recently + * used connection at the head). MHD can just look at the tail of + * this list to determine the timeout for all of its elements; + * whenever there is an event of a connection, the connection is + * moved back to the tail of the list. + * + * All connections by default start in this list; if a custom + * timeout that does not match 'connection_timeout' is set, they + * are moved to the 'manual_timeout_head'-XDLL. + */ + struct MHD_Connection *normal_timeout_head; + + /** + * Tail of the XDLL of ALL connections with a default timeout, + * sorted by timeout (earliest timeout at the tail). + */ + struct MHD_Connection *normal_timeout_tail; + + /** + * Head of the XDLL of ALL connections with a non-default/custom + * timeout, unsorted. MHD will do a O(n) scan over this list to + * determine the current timeout. + */ + struct MHD_Connection *manual_timeout_head; + /** - * Function to call to check if we should - * accept or reject an incoming request. - * May be NULL. + * Tail of the XDLL of ALL connections with a non-default/custom + * timeout, unsorted. + */ + struct MHD_Connection *manual_timeout_tail; + + /** + * Function to call to check if we should accept or reject an + * incoming request. May be NULL. */ MHD_AcceptPolicyCallback apc; @@ -942,6 +1031,19 @@ struct MHD_Daemon */ int socket_fd; +#if EPOLL_SUPPORT + /** + * File descriptor associated with our epoll loop. + */ + int epoll_fd; + + /** + * MHD_YES if the listen socket is in the 'epoll' set, + * MHD_NO if not. + */ + int listen_socket_in_epoll; +#endif + /** * Pipe we use to signal shutdown, unless * 'HAVE_LISTEN_SHUTDOWN' is defined AND we have a listen @@ -1018,6 +1120,14 @@ struct MHD_Daemon */ const char *https_mem_trust; + /** + * For how many connections do we have 'tls_read_ready' set to MHD_YES? + * Used to avoid O(n) traversal over all connections when determining + * event-loop timeout (as it needs to be zero if there is any connection + * which might have ready data within TLS). + */ + unsigned int num_tls_read_ready; + #endif #ifdef DAUTH_SUPPORT @@ -1099,10 +1209,93 @@ struct MHD_Daemon (element)->prev = NULL; } while (0) + +/** + * Insert an element at the head of a XDLL. Assumes that head, tail and + * element are structs with prevX and nextX fields. + * + * @param head pointer to the head of the XDLL + * @param tail pointer to the tail of the XDLL + * @param element element to insert + */ +#define XDLL_insert(head,tail,element) do { \ + (element)->nextX = (head); \ + (element)->prevX = NULL; \ + if ((tail) == NULL) \ + (tail) = element; \ + else \ + (head)->prevX = element; \ + (head) = (element); } while (0) + + +/** + * Remove an element from a XDLL. Assumes + * that head, tail and element are structs + * with prevX and nextX fields. + * + * @param head pointer to the head of the XDLL + * @param tail pointer to the tail of the XDLL + * @param element element to remove + */ +#define XDLL_remove(head,tail,element) do { \ + if ((element)->prevX == NULL) \ + (head) = (element)->nextX; \ + else \ + (element)->prevX->nextX = (element)->nextX; \ + if ((element)->nextX == NULL) \ + (tail) = (element)->prevX; \ + else \ + (element)->nextX->prevX = (element)->prevX; \ + (element)->nextX = NULL; \ + (element)->prevX = NULL; } while (0) + + +/** + * Insert an element at the head of a EDLL. Assumes that head, tail and + * element are structs with prevE and nextE fields. + * + * @param head pointer to the head of the EDLL + * @param tail pointer to the tail of the EDLL + * @param element element to insert + */ +#define EDLL_insert(head,tail,element) do { \ + (element)->nextE = (head); \ + (element)->prevE = NULL; \ + if ((tail) == NULL) \ + (tail) = element; \ + else \ + (head)->prevE = element; \ + (head) = (element); } while (0) + + +/** + * Remove an element from a EDLL. Assumes + * that head, tail and element are structs + * with prevE and nextE fields. + * + * @param head pointer to the head of the EDLL + * @param tail pointer to the tail of the EDLL + * @param element element to remove + */ +#define EDLL_remove(head,tail,element) do { \ + if ((element)->prevE == NULL) \ + (head) = (element)->nextE; \ + else \ + (element)->prevE->nextE = (element)->nextE; \ + if ((element)->nextE == NULL) \ + (tail) = (element)->prevE; \ + else \ + (element)->nextE->prevE = (element)->prevE; \ + (element)->nextE = NULL; \ + (element)->prevE = NULL; } while (0) + + /** * Equivalent to time(NULL) but tries to use some sort of monotonic * clock that isn't affected by someone setting the system real time * clock. + * + * @return 'current' time */ time_t MHD_monotonic_time(void); diff --git a/src/microhttpd/memorypool.c b/src/microhttpd/memorypool.c @@ -90,10 +90,13 @@ MHD_pool_create (size_t max) pool = malloc (sizeof (struct MemoryPool)); if (pool == NULL) - return NULL; + return NULL; #ifdef MAP_ANONYMOUS - pool->memory = MMAP (NULL, max, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (max <= 32 * 1024) + pool->memory = MAP_FAILED; + else + pool->memory = MMAP (NULL, max, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); #else pool->memory = MAP_FAILED; #endif diff --git a/src/testcurl/Makefile.am b/src/testcurl/Makefile.am @@ -28,8 +28,8 @@ endif check_PROGRAMS = \ test_start_stop \ - test_get \ test_quiesce \ + test_get \ test_get_sendfile \ test_urlparse \ test_put \ @@ -52,6 +52,8 @@ check_PROGRAMS = \ $(CURL_FORK_TEST) \ perf_get $(PERF_GET_CONCURRENT) + + if HAVE_POSTPROCESSOR check_PROGRAMS += \ test_post \ diff --git a/src/testcurl/perf_get.c b/src/testcurl/perf_get.c @@ -219,7 +219,8 @@ testInternalGet (int port, int poll_flag) } curl_easy_cleanup (c); } - stop (poll_flag ? "internal poll" : "internal select"); + stop (poll_flag == MHD_USE_POLL ? "internal poll" : + poll_flag == MHD_USE_EPOLL_LINUX_ONLY ? "internal epoll" : "internal select"); MHD_stop_daemon (d); if (cbc.pos != strlen ("/hello_world")) return 4; @@ -278,7 +279,8 @@ testMultithreadedGet (int port, int poll_flag) } curl_easy_cleanup (c); } - stop (poll_flag ? "thread with poll" : "thread with select"); + stop ((poll_flag & MHD_USE_POLL) ? "thread with poll" : + (poll_flag & MHD_USE_EPOLL_LINUX_ONLY) ? "thread with epoll" : "thread with select"); MHD_stop_daemon (d); if (cbc.pos != strlen ("/hello_world")) return 64; @@ -337,7 +339,8 @@ testMultithreadedPoolGet (int port, int poll_flag) } curl_easy_cleanup (c); } - stop (poll_flag ? "thread pool with poll" : "thread pool with select"); + stop (0 != (poll_flag & MHD_USE_POLL) ? "thread pool with poll" : + 0 != (poll_flag & MHD_USE_EPOLL_LINUX_ONLY) ? "thread pool with epoll" : "thread pool with select"); MHD_stop_daemon (d); if (cbc.pos != strlen ("/hello_world")) return 64; @@ -507,6 +510,10 @@ main (int argc, char *const *argv) errorCount += testMultithreadedGet (port++, MHD_USE_POLL); errorCount += testMultithreadedPoolGet (port++, MHD_USE_POLL); #endif +#if EPOLL_SUPPORT + errorCount += testInternalGet (port++, MHD_USE_EPOLL_LINUX_ONLY); + errorCount += testMultithreadedPoolGet (port++, MHD_USE_EPOLL_LINUX_ONLY); +#endif MHD_destroy_response (response); if (errorCount != 0) fprintf (stderr, "Error (code: %u)\n", errorCount); diff --git a/src/testcurl/perf_get_concurrent.c b/src/testcurl/perf_get_concurrent.c @@ -336,6 +336,10 @@ main (int argc, char *const *argv) errorCount += testMultithreadedGet (port++, MHD_USE_POLL); errorCount += testMultithreadedPoolGet (port++, MHD_USE_POLL); #endif +#if EPOLL_SUPPORT + errorCount += testInternalGet (port++, MHD_USE_EPOLL_LINUX_ONLY); + errorCount += testMultithreadedPoolGet (port++, MHD_USE_EPOLL_LINUX_ONLY); +#endif MHD_destroy_response (response); if (errorCount != 0) fprintf (stderr, "Error (code: %u)\n", errorCount); diff --git a/src/testcurl/test_get.c b/src/testcurl/test_get.c @@ -145,6 +145,7 @@ testInternalGet (int poll_flag) return 0; } + static int testMultithreadedGet (int poll_flag) { @@ -194,6 +195,7 @@ testMultithreadedGet (int poll_flag) return 0; } + static int testMultithreadedPoolGet (int poll_flag) { @@ -244,6 +246,7 @@ testMultithreadedPoolGet (int poll_flag) return 0; } + static int testExternalGet () { @@ -366,6 +369,7 @@ testExternalGet () return 0; } + static int testUnknownPortGet (int poll_flag) { @@ -506,6 +510,11 @@ main (int argc, char *const *argv) errorCount += testUnknownPortGet (MHD_USE_POLL); errorCount += testStopRace (MHD_USE_POLL); #endif +#if EPOLL_SUPPORT + errorCount += testInternalGet (MHD_USE_EPOLL_LINUX_ONLY); + errorCount += testMultithreadedPoolGet (MHD_USE_EPOLL_LINUX_ONLY); + errorCount += testUnknownPortGet (MHD_USE_EPOLL_LINUX_ONLY); +#endif if (errorCount != 0) fprintf (stderr, "Error (code: %u)\n", errorCount); curl_global_cleanup (); diff --git a/src/testcurl/test_quiesce.c b/src/testcurl/test_quiesce.c @@ -31,6 +31,8 @@ #include <stdlib.h> #include <string.h> #include <time.h> +#include <sys/types.h> +#include <sys/wait.h> #ifndef WINDOWS #include <unistd.h> @@ -38,7 +40,7 @@ #endif static int oneone; -static int done; + struct CBC { @@ -47,6 +49,7 @@ struct CBC size_t size; }; + static size_t copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) { @@ -59,6 +62,7 @@ copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) return size * nmemb; } + static int ahc_echo (void *cls, struct MHD_Connection *connection, @@ -91,24 +95,28 @@ ahc_echo (void *cls, return ret; } -void request_completed (void *cls, struct MHD_Connection *connection, - void **con_cls, enum MHD_RequestTerminationCode code) + +static void +request_completed (void *cls, struct MHD_Connection *connection, + void **con_cls, enum MHD_RequestTerminationCode code) { int *done = (int *)cls; *done = 1; } -void ServeOneRequest(int fd) + +static void +ServeOneRequest(int fd) { struct MHD_Daemon *d; fd_set rs; fd_set ws; fd_set es; int max; - struct CURLMsg *msg; time_t start; struct timeval tv; int done = 0; + d = MHD_start_daemon (MHD_USE_DEBUG, 1082, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_LISTEN_SOCKET, fd, @@ -140,9 +148,12 @@ void ServeOneRequest(int fd) _exit(0); } -CURL *setupCURL(void *cbc) + +static CURL * +setupCURL (void *cbc) { CURL *c; + c = curl_easy_init (); curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11080/hello_world"); curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); @@ -162,6 +173,7 @@ CURL *setupCURL(void *cbc) return c; } + static int testGet (int type, int pool_count, int poll_flag) { @@ -171,18 +183,17 @@ testGet (int type, int pool_count, int poll_flag) struct CBC cbc; CURLcode errornum; int fd; - pid_t pid; cbc.buf = buf; cbc.size = 2048; cbc.pos = 0; if (pool_count > 0) { - d = MHD_start_daemon (type | MHD_USE_DEBUG | poll_flag, + d = MHD_start_daemon (type | MHD_USE_DEBUG | MHD_USE_PIPE_FOR_SHUTDOWN | poll_flag, 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_THREAD_POOL_SIZE, pool_count, MHD_OPTION_END); } else { - d = MHD_start_daemon (type | MHD_USE_DEBUG | poll_flag, + d = MHD_start_daemon (type | MHD_USE_DEBUG | MHD_USE_PIPE_FOR_SHUTDOWN | poll_flag, 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); } if (d == NULL) @@ -211,12 +222,12 @@ testGet (int type, int pool_count, int poll_flag) return 8; } - fd = MHD_quiesce_daemon(d); - - if (fork() == 0) { - ServeOneRequest(fd); - _exit(1); - } + fd = MHD_quiesce_daemon (d); + if (fork() == 0) + { + ServeOneRequest (fd); + _exit(1); + } cbc.pos = 0; if (CURLE_OK != (errornum = curl_easy_perform (c))) @@ -225,25 +236,28 @@ testGet (int type, int pool_count, int poll_flag) "curl_easy_perform failed: `%s'\n", curl_easy_strerror (errornum)); curl_easy_cleanup (c); + MHD_stop_daemon (d); return 2; } waitpid(-1, NULL, 0); - if (cbc.pos != strlen ("/hello_world")) { - fprintf(stderr, "%s\n", cbc.buf); - curl_easy_cleanup (c); - MHD_stop_daemon (d); - close(fd); - return 4; - } - if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) { - fprintf(stderr, "%s\n", cbc.buf); - curl_easy_cleanup (c); - MHD_stop_daemon (d); - close(fd); - return 8; - } + if (cbc.pos != strlen ("/hello_world")) + { + fprintf(stderr, "%s\n", cbc.buf); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + close(fd); + return 4; + } + if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) + { + fprintf(stderr, "%s\n", cbc.buf); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + close(fd); + return 8; + } /* at this point, the forked server quit, and the new * server has quiesced, so new requests should fail @@ -408,6 +422,10 @@ main (int argc, char *const *argv) errorCount += testGet (MHD_USE_THREAD_PER_CONNECTION, 0, MHD_USE_POLL); errorCount += testGet (MHD_USE_SELECT_INTERNALLY, 4, MHD_USE_POLL); #endif +#if EPOLL_SUPPORT + errorCount += testGet (MHD_USE_SELECT_INTERNALLY, 0, MHD_USE_EPOLL_LINUX_ONLY); + errorCount += testGet (MHD_USE_SELECT_INTERNALLY, 4, MHD_USE_EPOLL_LINUX_ONLY); +#endif if (errorCount != 0) fprintf (stderr, "Error (code: %u)\n", errorCount); curl_global_cleanup (); diff --git a/src/testcurl/test_start_stop.c b/src/testcurl/test_start_stop.c @@ -110,6 +110,10 @@ main (int argc, char *const *argv) errorCount += testMultithreadedGet (MHD_USE_POLL); errorCount += testMultithreadedPoolGet (MHD_USE_POLL); #endif +#if EPOLL_SUPPORT + errorCount += testInternalGet (MHD_USE_EPOLL_LINUX_ONLY); + errorCount += testMultithreadedPoolGet (MHD_USE_EPOLL_LINUX_ONLY); +#endif if (errorCount != 0) fprintf (stderr, "Error (code: %u)\n", errorCount); return errorCount != 0; /* 0 == pass */