libmicrohttpd

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

commit e42ec8f54d28c982307367c483cee34ade5c54f8
parent 510881918a1bb72b2f9c007ae0ba14685429068c
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
Date:   Mon, 30 Aug 2021 21:13:54 +0300

Switched internal timers to milliseconds resolutions.

This is needed to handle connection timeouts precisely.
* Fixed connections expired "just before or after" required timeout
* Fixed busy-waiting with low-resolution system timers when connection is
about to timeout. Now low-resolution timers are handled automatically.
* Added log message if application tries to set ridiculously large timeout

Diffstat:
Msrc/include/microhttpd.h | 6+++++-
Msrc/microhttpd/connection.c | 50++++++++++++++++++++++++++++++++++----------------
Msrc/microhttpd/daemon.c | 152++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/microhttpd/internal.h | 25+++++++++++++++++--------
4 files changed, 148 insertions(+), 85 deletions(-)

diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -96,7 +96,7 @@ extern "C" * they are parsed as decimal numbers. * Example: 0x01093001 = 1.9.30-1. */ -#define MHD_VERSION 0x00097310 +#define MHD_VERSION 0x00097311 /* If generic headers don't work on your platform, include headers which define 'va_list', 'size_t', 'ssize_t', 'intptr_t', @@ -1432,6 +1432,8 @@ enum MHD_OPTION * After how many seconds of inactivity should a * connection automatically be timed out? (followed * by an `unsigned int`; use zero for no timeout). + * Values larger than (UINT64_MAX / 2000 - 1) will + * be clipped to this number. */ MHD_OPTION_CONNECTION_TIMEOUT = 3, @@ -4133,6 +4135,8 @@ enum MHD_CONNECTION_OPTION * zero for no timeout. * If timeout was set to zero (or unset) before, setup of new value by * MHD_set_connection_option() will reset timeout timer. + * Values larger than (UINT64_MAX / 2000 - 1) will + * be clipped to this number. */ MHD_CONNECTION_OPTION_TIMEOUT diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c @@ -3487,17 +3487,17 @@ MHD_update_last_activity_ (struct MHD_Connection *connection) { struct MHD_Daemon *daemon = connection->daemon; - if (0 == connection->connection_timeout) + if (0 == connection->connection_timeout_ms) return; /* Skip update of activity for connections without timeout timer. */ if (connection->suspended) return; /* no activity on suspended connections */ - connection->last_activity = MHD_monotonic_sec_counter (); + connection->last_activity = MHD_monotonic_msec_counter (); if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) return; /* each connection has personal timeout */ - if (connection->connection_timeout != daemon->connection_timeout) + if (connection->connection_timeout_ms != daemon->connection_timeout_ms) return; /* custom timeout, no need to move it in "normal" DLL */ #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex); @@ -4011,7 +4011,7 @@ cleanup_connection (struct MHD_Connection *connection) { if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) { - if (connection->connection_timeout == daemon->connection_timeout) + if (connection->connection_timeout_ms == daemon->connection_timeout_ms) XDLL_remove (daemon->normal_timeout_head, daemon->normal_timeout_tail, connection); @@ -4584,11 +4584,13 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) } if (! connection->suspended) { - time_t timeout; - timeout = connection->connection_timeout; + uint64_t timeout; + timeout = connection->connection_timeout_ms; + /* Keep the next lines in sync with #MHD_get_timeout() to avoid + * undesired side-effects like busy-waiting. */ if ( (0 != timeout) && - (timeout <= (MHD_monotonic_sec_counter () - - connection->last_activity)) ) + (timeout < (MHD_monotonic_msec_counter () + - connection->last_activity)) ) { MHD_connection_close_ (connection, MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); @@ -4719,8 +4721,8 @@ MHD_get_connection_info (struct MHD_Connection *connection, connection->suspended_dummy = connection->suspended ? MHD_YES : MHD_NO; return (const union MHD_ConnectionInfo *) &connection->suspended_dummy; case MHD_CONNECTION_INFO_CONNECTION_TIMEOUT: - connection->connection_timeout_dummy = (unsigned - int) connection->connection_timeout; + connection->connection_timeout_dummy = + (unsigned int) connection->connection_timeout_ms * 1000; return (const union MHD_ConnectionInfo *) &connection-> connection_timeout_dummy; case MHD_CONNECTION_INFO_REQUEST_HEADER_SIZE: @@ -4759,15 +4761,15 @@ MHD_set_connection_option (struct MHD_Connection *connection, switch (option) { case MHD_CONNECTION_OPTION_TIMEOUT: - if (0 == connection->connection_timeout) - connection->last_activity = MHD_monotonic_sec_counter (); + if (0 == connection->connection_timeout_ms) + connection->last_activity = MHD_monotonic_msec_counter (); #if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex); #endif if ( (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && (! connection->suspended) ) { - if (connection->connection_timeout == daemon->connection_timeout) + if (connection->connection_timeout_ms == daemon->connection_timeout_ms) XDLL_remove (daemon->normal_timeout_head, daemon->normal_timeout_tail, connection); @@ -4777,13 +4779,29 @@ MHD_set_connection_option (struct MHD_Connection *connection, connection); } va_start (ap, option); - connection->connection_timeout = va_arg (ap, - unsigned int); + connection->connection_timeout_ms = va_arg (ap, + unsigned int); va_end (ap); +#if (0 == (UINT64_MAX + 0)) || ((UINT_MAX + 0) >= (UINT64_MAX + 0)) + if ((UINT64_MAX / 2000 - 1) < connection->connection_timeout_ms) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (connection->daemon, + _ ("The specified connection timeout (" PRIu64 ") is too " \ + "large. Maximum allowed value (" PRIu64 ") will be used " \ + "instead.\n"), + connection->connection_timeout_ms, + (UINT64_MAX / 2000 - 1)); +#endif + connection->connection_timeout_ms = UINT64_MAX / 2000 - 1; + } + else +#endif /* UINTMAX_MAX >= UINT64_MAX */ + connection->connection_timeout_ms *= 1000; if ( (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && (! connection->suspended) ) { - if (connection->connection_timeout == daemon->connection_timeout) + if (connection->connection_timeout_ms == daemon->connection_timeout_ms) XDLL_insert (daemon->normal_timeout_head, daemon->normal_timeout_tail, connection); diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c @@ -1869,7 +1869,6 @@ thread_main_handle_connection (void *data) MHD_socket maxsock; struct timeval tv; struct timeval *tvp; - time_t now; #if WINDOWS #ifdef HAVE_POLL int extra_slot; @@ -1893,7 +1892,7 @@ thread_main_handle_connection (void *data) while ( (! daemon->shutdown) && (MHD_CONNECTION_CLOSED != con->state) ) { - const time_t timeout = con->connection_timeout; + uint64_t timeout = con->connection_timeout_ms; #ifdef UPGRADE_SUPPORT struct MHD_UpgradeResponseHandle *const urh = con->urh; #else /* ! UPGRADE_SUPPORT */ @@ -1989,22 +1988,34 @@ thread_main_handle_connection (void *data) if ( (NULL == tvp) && (timeout > 0) ) { - now = MHD_monotonic_sec_counter (); - if (now - con->last_activity > timeout) + const uint64_t since_actv = MHD_monotonic_msec_counter () + - con->last_activity; + if (since_actv > timeout) + { tv.tv_sec = 0; + tv.tv_usec = 0; + } + else if (since_actv == timeout) + { + /* Exact match for timeout and time from last activity. + * Maybe this is just a precise match or this happens because the timer + * resolution is too low. + * Set wait time to 0.1 seconds to avoid busy-waiting with low + * timer resolution as connection is not yet timed-out */ + tv.tv_sec = 0; + tv.tv_usec = 100 * 1000; + } else { - const time_t seconds_left = timeout - (now - con->last_activity); -#if ! defined(_WIN32) || defined(__CYGWIN__) - tv.tv_sec = seconds_left; -#else /* _WIN32 && !__CYGWIN__ */ - if (seconds_left > TIMEVAL_TV_SEC_MAX) + const uint64_t mseconds_left = timeout - since_actv; +#if UINT64_MAX != TIMEVAL_TV_SEC_MAX + if (mseconds_left / 1000 > TIMEVAL_TV_SEC_MAX) tv.tv_sec = TIMEVAL_TV_SEC_MAX; else - tv.tv_sec = (_MHD_TIMEVAL_TV_SEC_TYPE) seconds_left; -#endif /* _WIN32 && ! __CYGWIN__ */ + tv.tv_sec = (_MHD_TIMEVAL_TV_SEC_TYPE) mseconds_left / 1000; +#endif /* UINT64_MAX != TIMEVAL_TV_SEC_MAX */ + tv.tv_usec = (mseconds_left % 1000) * 1000; } - tv.tv_usec = 0; tvp = &tv; } if (! use_poll) @@ -2474,7 +2485,6 @@ new_connection_prepare_ (struct MHD_Daemon *daemon, connection->sk_nodelay = _MHD_UNKNOWN; } - connection->connection_timeout = daemon->connection_timeout; if (NULL == (connection->addr = malloc (addrlen))) { eno = errno; @@ -2500,7 +2510,9 @@ new_connection_prepare_ (struct MHD_Daemon *daemon, connection->is_nonip = sk_is_nonip; connection->sk_spipe_suppress = sk_spipe_supprs; connection->daemon = daemon; - connection->last_activity = MHD_monotonic_sec_counter (); + connection->connection_timeout_ms = daemon->connection_timeout_ms; + if (0 != connection->connection_timeout_ms) + connection->last_activity = MHD_monotonic_msec_counter (); if (0 == (daemon->options & MHD_USE_TLS)) { @@ -3061,7 +3073,7 @@ internal_suspend_connection_ (struct MHD_Connection *connection) } if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) { - if (connection->connection_timeout == daemon->connection_timeout) + if (connection->connection_timeout_ms == daemon->connection_timeout_ms) XDLL_remove (daemon->normal_timeout_head, daemon->normal_timeout_tail, connection); @@ -3270,10 +3282,10 @@ resume_suspended_connections (struct MHD_Daemon *daemon) if (! used_thr_p_c) { /* Reset timeout timer on resume. */ - if (0 != pos->connection_timeout) - pos->last_activity = MHD_monotonic_sec_counter (); + if (0 != pos->connection_timeout_ms) + pos->last_activity = MHD_monotonic_msec_counter (); - if (pos->connection_timeout == daemon->connection_timeout) + if (pos->connection_timeout_ms == daemon->connection_timeout_ms) XDLL_insert (daemon->normal_timeout_head, daemon->normal_timeout_tail, pos); @@ -3824,10 +3836,9 @@ enum MHD_Result MHD_get_timeout (struct MHD_Daemon *daemon, MHD_UNSIGNED_LONG_LONG *timeout) { - time_t earliest_deadline; - time_t now; + uint64_t earliest_deadline; struct MHD_Connection *pos; - bool have_timeout; + struct MHD_Connection *earliest_tmot_conn; /**< the connection with earliest timeout */ #ifdef MHD_USE_THREADS mhd_assert ( (0 == (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) || \ @@ -3862,44 +3873,63 @@ MHD_get_timeout (struct MHD_Daemon *daemon, } #endif /* EPOLL_SUPPORT */ - have_timeout = false; - earliest_deadline = 0; /* avoid compiler warnings */ - for (pos = daemon->manual_timeout_tail; NULL != pos; pos = pos->prevX) - { - if (0 != pos->connection_timeout) - { - if ( (! have_timeout) || - (earliest_deadline - pos->last_activity > pos->connection_timeout) ) - earliest_deadline = pos->last_activity + pos->connection_timeout; - have_timeout = true; - } - } + earliest_tmot_conn = NULL; + earliest_deadline = 0; /* mute compiler warning */ /* normal timeouts are sorted, so we only need to look at the 'tail' (oldest) */ pos = daemon->normal_timeout_tail; if ( (NULL != pos) && - (0 != pos->connection_timeout) ) + (0 != pos->connection_timeout_ms) ) { - if ( (! have_timeout) || - (earliest_deadline - pos->connection_timeout > pos->last_activity) ) - earliest_deadline = pos->last_activity + pos->connection_timeout; - have_timeout = true; + earliest_tmot_conn = pos; + earliest_deadline = pos->last_activity + pos->connection_timeout_ms; } - if (! have_timeout) - return MHD_NO; - now = MHD_monotonic_sec_counter (); - if (earliest_deadline < now) - *timeout = 0; - else + for (pos = daemon->manual_timeout_tail; NULL != pos; pos = pos->prevX) { - const time_t second_left = earliest_deadline - now; + if (0 != pos->connection_timeout_ms) + { + if ( (NULL == earliest_tmot_conn) || + (earliest_deadline - pos->last_activity > + pos->connection_timeout_ms) ) + { + earliest_tmot_conn = pos; + earliest_deadline = pos->last_activity + pos->connection_timeout_ms; + } + } + } - if (((unsigned long long) second_left) > ULLONG_MAX / 1000) - *timeout = ULLONG_MAX; + if (NULL != earliest_tmot_conn) + { + const uint64_t since_actv = MHD_monotonic_msec_counter () + - earliest_tmot_conn->last_activity; + /* Keep the next lines in sync with #MHD_connection_handle_idle() and + * with #thread_main_handle_connection(). */ + if (since_actv > earliest_tmot_conn->connection_timeout_ms) + *timeout = 0; + else if (since_actv == earliest_tmot_conn->connection_timeout_ms) + { + /* Exact match for timeout and time from last activity. + * Maybe this is just a precise match or this happens because the timer + * resolution is too low. + * Set wait time to 0.1 seconds to avoid busy-waiting with low + * timer resolution as connection is not yet timed-out */ + *timeout = 100; + } else - *timeout = 1000LLU * (unsigned long long) second_left; + { + const uint64_t mssecond_left = earliest_tmot_conn->connection_timeout_ms + - since_actv; + +#if UINT64_MAX != ULLONG_MAX + if (mssecond_left > ULLONG_MAX) + *timeout = ULLONG_MAX; + else +#endif /* UINT64 != ULLONG_MAX */ + *timeout = (unsigned long long) mssecond_left; + } + return MHD_YES; } - return MHD_YES; + return MHD_NO; } @@ -5247,7 +5277,7 @@ close_connection (struct MHD_Connection *pos) #endif mhd_assert (! pos->suspended); mhd_assert (! pos->resuming); - if (pos->connection_timeout == daemon->connection_timeout) + if (pos->connection_timeout_ms == daemon->connection_timeout_ms) XDLL_remove (daemon->normal_timeout_head, daemon->normal_timeout_tail, pos); @@ -5593,20 +5623,22 @@ parse_options_va (struct MHD_Daemon *daemon, case MHD_OPTION_CONNECTION_TIMEOUT: uv = va_arg (ap, unsigned int); - daemon->connection_timeout = (time_t) uv; - /* Next comparison could be always false on some platforms and whole branch will - * be optimized out on those platforms. On others it will be compiled into real - * check. */ - if ( ( (MHD_TYPE_IS_SIGNED_ (time_t)) && - (daemon->connection_timeout < 0) ) || /* Compiler may warn on some platforms, ignore warning. */ - (uv != (unsigned int) daemon->connection_timeout) ) +#if (0 == (UINT64_MAX + 0)) || ((UINT_MAX + 0) >= (UINT64_MAX + 0)) + if ((UINT64_MAX / 2000 - 1) < uv) { #ifdef HAVE_MESSAGES MHD_DLOG (daemon, - _ ("Warning: Too large timeout value, ignored.\n")); + _ ("The specified connection timeout (%u) is too large. " \ + "Maximum allowed value (" PRIu64 ") will be used " \ + "instead.\n"), + uv, + (UINT64_MAX / 2000 - 1)); #endif - daemon->connection_timeout = 0; + daemon->connection_timeout_ms = UINT64_MAX / 2000 - 1; } + else +#endif /* UINTMAX_MAX >= UINT64_MAX */ + daemon->connection_timeout_ms = uv * 1000; break; case MHD_OPTION_NOTIFY_COMPLETED: daemon->notify_completed = va_arg (ap, @@ -6444,7 +6476,7 @@ MHD_start_daemon_va (unsigned int flags, daemon->pool_size = MHD_POOL_SIZE_DEFAULT; daemon->pool_increment = MHD_BUF_INC_SIZE; daemon->unescape_callback = &unescape_wrapper; - daemon->connection_timeout = 0; /* no timeout */ + daemon->connection_timeout_ms = 0; /* no timeout */ MHD_itc_set_invalid_ (daemon->itc); #ifdef SOMAXCONN daemon->listen_backlog_size = SOMAXCONN; diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h @@ -43,6 +43,13 @@ #include <stdbool.h> #endif +#ifdef HAVE_INTTYPES_H +#include <inttypes.h> +#endif /* HAVE_INTTYPES_H */ + +#ifndef PRIu64 +#define PRIu64 "llu" +#endif /* ! PRIu64 */ #ifdef MHD_PANIC /* Override any defined MHD_PANIC macro with proper one */ @@ -1143,13 +1150,14 @@ struct MHD_Connection * Last time this connection had any activity * (reading or writing). */ - time_t last_activity; + uint64_t last_activity; /** - * After how many seconds of inactivity should - * this connection time out? Zero for no timeout. + * After how many milliseconds of inactivity should + * this connection time out? + * Zero for no timeout. */ - time_t connection_timeout; + uint64_t connection_timeout_ms; /** * Special member to be returned by #MHD_get_connection_info() @@ -1707,7 +1715,7 @@ struct MHD_Daemon * moved back to the tail of the list. * * All connections by default start in this list; if a custom - * timeout that does not match @e connection_timeout is set, they + * timeout that does not match @e connection_timeout_ms is set, they * are moved to the @e manual_timeout_head-XDLL. * Not used in MHD_USE_THREAD_PER_CONNECTION mode as each thread * needs only one connection-specific timeout. @@ -1968,10 +1976,11 @@ struct MHD_Daemon unsigned int connection_limit; /** - * After how many seconds of inactivity should - * connections time out? Zero for no timeout. + * After how many milliseconds of inactivity should + * this connection time out? + * Zero for no timeout. */ - time_t connection_timeout; + uint64_t connection_timeout_ms; /** * Maximum number of connections per IP, or 0 for