libmicrohttpd

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

commit 2a6eada873b10f20c9a7366d19bad98b9bd945df
parent 7131394fa881b732cd141fda9875953f5b5c1831
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 25 Oct 2015 14:33:28 +0000

-fix assertion failure from race on shutdown and buffer shrinkage with pipelining

Diffstat:
MChangeLog | 9+++++++++
Msrc/include/microhttpd.h | 2+-
Msrc/microhttpd/connection.c | 30+++++++++++++++---------------
Msrc/microhttpd/connection.h | 4++--
Msrc/microhttpd/connection_https.c | 8++++----
Msrc/microhttpd/daemon.c | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/testcurl/test_concurrent_stop.c | 10+++++++---
7 files changed, 108 insertions(+), 57 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,12 @@ +Sun Oct 25 15:29:23 CET 2015 + Fixing transient resource leak affecting long-lived + connections with many keep-alives and HTTP request + pipelining under certain circumstances (which reduced + the receive window). + Fixed assertion failure triggered by a race in + thread-per-connection mode on shutdown in rare + circumstances. -CG + Mon Oct 5 11:53:52 CEST 2015 Deduplicate code between digestauth and connection parsing logic for URI arguments, shared code moved diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -130,7 +130,7 @@ typedef intptr_t ssize_t; * Current version of the library. * 0x01093001 = 1.9.30-1. */ -#define MHD_VERSION 0x00094401 +#define MHD_VERSION 0x00094402 /** * MHD-internal return code for "YES". diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c @@ -264,8 +264,8 @@ need_100_continue (struct MHD_Connection *connection) * @param termination_code termination reason to give */ void -MHD_connection_close (struct MHD_Connection *connection, - enum MHD_RequestTerminationCode termination_code) +MHD_connection_close_ (struct MHD_Connection *connection, + enum MHD_RequestTerminationCode termination_code) { struct MHD_Daemon *daemon; @@ -300,8 +300,8 @@ connection_close_error (struct MHD_Connection *connection, if (NULL != emsg) MHD_DLOG (connection->daemon, emsg); #endif - MHD_connection_close (connection, - MHD_REQUEST_TERMINATED_WITH_ERROR); + MHD_connection_close_ (connection, + MHD_REQUEST_TERMINATED_WITH_ERROR); } @@ -368,8 +368,8 @@ try_ready_normal_body (struct MHD_Connection *connection) if (NULL != response->crc) (void) MHD_mutex_unlock_ (&response->mutex); if ( ((ssize_t)MHD_CONTENT_READER_END_OF_STREAM) == ret) - MHD_connection_close (connection, - MHD_REQUEST_TERMINATED_COMPLETED_OK); + MHD_connection_close_ (connection, + MHD_REQUEST_TERMINATED_COMPLETED_OK); else CONNECTION_CLOSE_ERROR (connection, "Closing connection (stream error)\n"); @@ -1611,8 +1611,8 @@ do_read (struct MHD_Connection *connection) /* other side closed connection; RFC 2616, section 8.1.4 suggests we should then shutdown ourselves as well. */ connection->read_closed = MHD_YES; - MHD_connection_close (connection, - MHD_REQUEST_TERMINATED_CLIENT_ABORT); + MHD_connection_close_ (connection, + MHD_REQUEST_TERMINATED_CLIENT_ABORT); return MHD_YES; } connection->read_buffer_offset += bytes_read; @@ -1959,8 +1959,8 @@ MHD_connection_handle_read (struct MHD_Connection *connection) /* nothing to do but default action */ if (MHD_YES == connection->read_closed) { - MHD_connection_close (connection, - MHD_REQUEST_TERMINATED_READ_ERROR); + MHD_connection_close_ (connection, + MHD_REQUEST_TERMINATED_READ_ERROR); continue; } break; @@ -2539,8 +2539,8 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) (MHD_NO == keepalive_possible (connection))) { /* have to close for some reason */ - MHD_connection_close (connection, - MHD_REQUEST_TERMINATED_COMPLETED_OK); + MHD_connection_close_ (connection, + MHD_REQUEST_TERMINATED_COMPLETED_OK); MHD_pool_destroy (connection->pool); connection->pool = NULL; connection->read_buffer = NULL; @@ -2555,7 +2555,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) connection->read_buffer = MHD_pool_reset (connection->pool, connection->read_buffer, - connection->read_buffer_size); + connection->read_buffer_offset); } connection->client_aware = MHD_NO; connection->client_context = NULL; @@ -2585,8 +2585,8 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) if ( (0 != timeout) && (timeout <= (MHD_monotonic_sec_counter() - connection->last_activity)) ) { - MHD_connection_close (connection, - MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); + MHD_connection_close_ (connection, + MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); connection->in_idle = MHD_NO; return MHD_YES; } diff --git a/src/microhttpd/connection.h b/src/microhttpd/connection.h @@ -89,8 +89,8 @@ MHD_connection_handle_idle (struct MHD_Connection *connection); * @param termination_code termination reason to give */ void -MHD_connection_close (struct MHD_Connection *connection, - enum MHD_RequestTerminationCode termination_code); +MHD_connection_close_ (struct MHD_Connection *connection, + enum MHD_RequestTerminationCode termination_code); #if EPOLL_SUPPORT diff --git a/src/microhttpd/connection_https.c b/src/microhttpd/connection_https.c @@ -69,8 +69,8 @@ run_tls_handshake (struct MHD_Connection *connection) MHD_DLOG (connection->daemon, "Error: received handshake message out of context\n"); #endif - MHD_connection_close (connection, - MHD_REQUEST_TERMINATED_WITH_ERROR); + MHD_connection_close_ (connection, + MHD_REQUEST_TERMINATED_WITH_ERROR); return MHD_YES; } return MHD_NO; @@ -142,8 +142,8 @@ MHD_tls_connection_handle_idle (struct MHD_Connection *connection) #endif timeout = connection->connection_timeout; if ( (timeout != 0) && (timeout <= (MHD_monotonic_sec_counter() - connection->last_activity))) - MHD_connection_close (connection, - MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); + MHD_connection_close_ (connection, + MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); switch (connection->state) { /* on newly created connections we might reach here before any reply has been received */ diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c @@ -135,7 +135,8 @@ mhd_panic_std (void *cls, const char *reason) { #if HAVE_MESSAGES - fprintf (stderr, "Fatal error in GNU libmicrohttpd %s:%u: %s\n", + fprintf (stderr, + "Fatal error in GNU libmicrohttpd %s:%u: %s\n", file, line, reason); #endif abort (); @@ -994,7 +995,8 @@ MHD_handle_connection (void *data) if (0 != (p[0].revents & POLLOUT)) con->write_handler (con); if (0 != (p[0].revents & (POLLERR | POLLHUP))) - MHD_connection_close (con, MHD_REQUEST_TERMINATED_WITH_ERROR); + MHD_connection_close_ (con, + MHD_REQUEST_TERMINATED_WITH_ERROR); if (MHD_NO == con->idle_handler (con)) goto exit; } @@ -1009,8 +1011,8 @@ MHD_handle_connection (void *data) #endif #endif if (MHD_CONNECTION_CLOSED != con->state) - MHD_connection_close (con, - MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN); + MHD_connection_close_ (con, + MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN); con->idle_handler (con); } exit: @@ -4359,8 +4361,8 @@ close_connection (struct MHD_Connection *pos) { struct MHD_Daemon *daemon = pos->daemon; - MHD_connection_close (pos, - MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN); + MHD_connection_close_ (pos, + MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN); if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) return; /* must let thread to the rest */ if (pos->connection_timeout == pos->daemon->connection_timeout) @@ -4398,35 +4400,50 @@ close_all_connections (struct MHD_Daemon *daemon) if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) MHD_PANIC ("Failed to acquire cleanup mutex\n"); + if (NULL != daemon->suspended_connections_head) + MHD_PANIC ("MHD_stop_daemon() called while we have suspended connections.\n"); for (pos = daemon->connections_head; NULL != pos; pos = pos->next) { shutdown (pos->socket_fd, - (pos->read_closed == MHD_YES) ? SHUT_WR : SHUT_RDWR); + (MHD_YES == pos->read_closed) ? SHUT_WR : SHUT_RDWR); #if MHD_WINSOCK_SOCKETS if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && (MHD_INVALID_PIPE_ != daemon->wpipe[1]) && (1 != MHD_pipe_write_ (daemon->wpipe[1], "e", 1)) ) - MHD_PANIC ("failed to signal shutdown via pipe"); + MHD_PANIC ("Failed to signal shutdown via pipe"); #endif } if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) MHD_PANIC ("Failed to release cleanup mutex\n"); - /* now, collect threads from thread pool */ + /* now, collect per-connection threads */ if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) { - for (pos = daemon->connections_head; NULL != pos; pos = pos->next) - { - if (0 != MHD_join_thread_ (pos->pid)) - MHD_PANIC ("Failed to join a thread\n"); - pos->thread_joined = MHD_YES; - } + pos = daemon->connections_head; + while (NULL != pos) + { + if (MHD_YES != pos->thread_joined) + { + if (0 != MHD_join_thread_ (pos->pid)) + MHD_PANIC ("Failed to join a thread\n"); + pos->thread_joined = MHD_YES; + /* The thread may have concurrently modified the DLL, + need to restart from the beginning */ + pos = daemon->connections_head; + continue; + } + pos = pos->next; + } } - /* now that we're alone, move everyone to cleanup */ while (NULL != (pos = daemon->connections_head)) + { + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != pos->thread_joined) ) + MHD_PANIC ("Failed to join a thread\n"); close_connection (pos); + } MHD_cleanup_connections (daemon); } @@ -4478,7 +4495,7 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) /* Prepare workers for shutdown */ if (NULL != daemon->worker_pool) { - /* MHD_USE_NO_LISTEN_SOCKET disables thread pools, hence we need to check */ + /* #MHD_USE_NO_LISTEN_SOCKET disables thread pools, hence we need to check */ for (i = 0; i < daemon->worker_pool_size; ++i) { daemon->worker_pool[i].shutdown = MHD_YES; @@ -4556,9 +4573,9 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) else { /* clean up master threads */ - if ((0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) || - ((0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)) - && (0 == daemon->worker_pool_size))) + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) || + ( (0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)) && + (0 == daemon->worker_pool_size) ) ) { if (0 != MHD_join_thread_ (daemon->pid)) { @@ -4676,7 +4693,8 @@ MHD_get_daemon_info (struct MHD_Daemon *daemon, * @ingroup logging */ void -MHD_set_panic_func (MHD_PanicCallback cb, void *cls) +MHD_set_panic_func (MHD_PanicCallback cb, + void *cls) { mhd_panic = cb; mhd_panic_cls = cls; @@ -4825,13 +4843,14 @@ MHD_is_feature_supported(enum MHD_FEATURE feature) #if defined(MHD_USE_POSIX_THREADS) GCRY_THREAD_OPTION_PTHREAD_IMPL; #elif defined(MHD_W32_MUTEX_) -static int gcry_w32_mutex_init (void **ppmtx) + +static int +gcry_w32_mutex_init (void **ppmtx) { *ppmtx = malloc (sizeof (MHD_mutex_)); if (NULL == *ppmtx) return ENOMEM; - if (MHD_YES != MHD_mutex_create_ ((MHD_mutex_*)*ppmtx)) { free (*ppmtx); @@ -4841,13 +4860,30 @@ static int gcry_w32_mutex_init (void **ppmtx) return 0; } -static int gcry_w32_mutex_destroy (void **ppmtx) - { int res = (MHD_YES == MHD_mutex_destroy_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1; - free (*ppmtx); return res; } -static int gcry_w32_mutex_lock (void **ppmtx) - { return (MHD_YES == MHD_mutex_lock_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1; } -static int gcry_w32_mutex_unlock (void **ppmtx) - { return (MHD_YES == MHD_mutex_unlock_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1; } + + +static int +gcry_w32_mutex_destroy (void **ppmtx) +{ + int res = (MHD_YES == MHD_mutex_destroy_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1; + free (*ppmtx); + return res; +} + + +static int +gcry_w32_mutex_lock (void **ppmtx) +{ + return (MHD_YES == MHD_mutex_lock_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1; +} + + +static int +gcry_w32_mutex_unlock (void **ppmtx) +{ + return (MHD_YES == MHD_mutex_unlock_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1; +} + static struct gcry_thread_cbs gcry_threads_w32 = { (GCRY_THREAD_OPTION_USER | (GCRY_THREAD_OPTION_VERSION << 8)), @@ -4862,7 +4898,8 @@ static struct gcry_thread_cbs gcry_threads_w32 = { /** * Initialize do setup work. */ -void MHD_init(void) +void +MHD_init(void) { mhd_panic = &mhd_panic_std; mhd_panic_cls = NULL; @@ -4895,7 +4932,8 @@ void MHD_init(void) } -void MHD_fini(void) +void +MHD_fini(void) { #if HTTPS_SUPPORT gnutls_global_deinit (); diff --git a/src/testcurl/test_concurrent_stop.c b/src/testcurl/test_concurrent_stop.c @@ -68,13 +68,15 @@ copyBuffer (void *ptr, return size * nmemb; } + static int ahc_echo (void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, - const char *upload_data, size_t *upload_data_size, + const char *upload_data, + size_t *upload_data_size, void **unused) { static int ptr; @@ -166,7 +168,8 @@ join_gets (pid_t pid) static int -testMultithreadedGet (int port, int poll_flag) +testMultithreadedGet (int port, + int poll_flag) { struct MHD_Daemon *d; pid_t p; @@ -184,7 +187,8 @@ testMultithreadedGet (int port, int poll_flag) static int -testMultithreadedPoolGet (int port, int poll_flag) +testMultithreadedPoolGet (int port, + int poll_flag) { struct MHD_Daemon *d; pid_t p;