diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c index 02fc0e71..84bfc662 100644 --- a/src/microhttpd/connection.c +++ b/src/microhttpd/connection.c @@ -4180,6 +4180,56 @@ MHD_connection_handle_write (struct MHD_Connection *connection) } +/** + * Check whether connection has timed out. + * @param c the connection to check + * @return true if connection has timeout and needs to be closed, + * false otherwise. + */ +static bool +connection_check_timedout (struct MHD_Connection *c) +{ + const uint64_t timeout = c->connection_timeout_ms; + uint64_t now; + uint64_t since_actv; + + if (c->suspended) + return false; + if (0 == timeout) + return false; + now = MHD_monotonic_msec_counter (); + since_actv = now - c->last_activity; + /* Keep the next lines in sync with #connection_get_wait() to avoid + * undesired side-effects like busy-waiting. */ + if (timeout < since_actv) + { + if (UINT64_MAX / 2 < since_actv) + { + const uint64_t jump_back = c->last_activity - now; + /* Very unlikely that it is more than quarter-million years pause. + * More likely that system clock jumps back. */ + if (5000 >= jump_back) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (c->daemon, + _ ("Detected system clock %u milliseconds jump back.\n"), + (unsigned int) jump_back); +#endif + return false; + } +#ifdef HAVE_MESSAGES + MHD_DLOG (c->daemon, + _ ("Detected too large system clock %" PRIu64 " milliseconds " + "jump back.\n"), + jump_back); +#endif + } + return true; + } + return false; +} + + /** * Clean up the state of the given connection and move it into the * clean up queue for final disposal. @@ -4799,21 +4849,12 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) } break; } - if (! connection->suspended) + if (connection_check_timedout (connection)) { - 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_msec_counter () - - connection->last_activity)) ) - { - MHD_connection_close_ (connection, - MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); - connection->in_idle = false; - return MHD_YES; - } + MHD_connection_close_ (connection, + MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); + connection->in_idle = false; + return MHD_YES; } MHD_connection_update_event_loop_info (connection); ret = MHD_YES; diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c index d4064dd7..5ca612ea 100644 --- a/src/microhttpd/daemon.c +++ b/src/microhttpd/daemon.c @@ -1857,6 +1857,54 @@ thread_main_connection_upgrade (struct MHD_Connection *con) #endif /* UPGRADE_SUPPORT */ +/** + * Get maximum wait period for the connection (the amount of time left before + * connection time out) + * @param c the connection to check + * @return the maximum wait period before the connection must be processed + * again. + */ +static uint64_t +connection_get_wait (struct MHD_Connection *c) +{ + const uint64_t now = MHD_monotonic_msec_counter (); + const uint64_t since_actv = now - c->last_activity; + const uint64_t timeout = c->connection_timeout_ms; + uint64_t mseconds_left; + + mhd_assert (0 != timeout); + /* Keep the next lines in sync with #connection_check_timedout() to avoid + * undesired side-effects like busy-waiting. */ + if (timeout < since_actv) + { + if (UINT64_MAX / 2 < since_actv) + { + const uint64_t jump_back = c->last_activity - now; + /* Very unlikely that it is more than quarter-million years pause. + * More likely that system clock jumps back. */ + if (5000 >= jump_back) + { /* Jump back is less than 5 seconds, try to recover. */ + return 100; /* Set wait time to 0.1 seconds */ + } + /* Too large jump back */ + } + return 0; /* Connection has timed out */ + } + 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 timed-out yet. */ + return 100; + } + mseconds_left = timeout - since_actv; + + return mseconds_left; +} + + /** * Main function of the thread that handles an individual * connection when #MHD_USE_THREAD_PER_CONNECTION is set. @@ -1995,35 +2043,16 @@ thread_main_handle_connection (void *data) if ( (NULL == tvp) && (timeout > 0) ) { - 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; - } + const uint64_t mseconds_left = connection_get_wait (con); +#if (SIZEOF_UINT64_T - 2) >= SIZEOF_STRUCT_TIMEVAL_TV_SEC + if (mseconds_left / 1000 > TIMEVAL_TV_SEC_MAX) + tv.tv_sec = TIMEVAL_TV_SEC_MAX; else - { - const uint64_t mseconds_left = timeout - since_actv; -#if (SIZEOF_UINT64_T - 1) >= SIZEOF_STRUCT_TIMEVAL_TV_SEC - if (mseconds_left / 1000 > TIMEVAL_TV_SEC_MAX) - tv.tv_sec = TIMEVAL_TV_SEC_MAX; - else -#endif /* (SIZEOF_UINT64_T - 1) >= SIZEOF_STRUCT_TIMEVAL_TV_SEC */ - tv.tv_sec = (_MHD_TIMEVAL_TV_SEC_TYPE) mseconds_left / 1000; +#endif /* (SIZEOF_UINT64_T - 2) >= SIZEOF_STRUCT_TIMEVAL_TV_SEC */ + tv.tv_sec = (_MHD_TIMEVAL_TV_SEC_TYPE) mseconds_left / 1000; + + tv.tv_usec = (mseconds_left % 1000) * 1000; - tv.tv_usec = (mseconds_left % 1000) * 1000; - } tvp = &tv; } if (! use_poll) @@ -3930,33 +3959,13 @@ MHD_get_timeout (struct MHD_Daemon *daemon, 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 - { - const uint64_t mssecond_left = earliest_tmot_conn->connection_timeout_ms - - since_actv; - + const uint64_t mssecond_left = connection_get_wait (earliest_tmot_conn); #if SIZEOF_UINT64_T > SIZEOF_UNSIGNED_LONG_LONG - if (mssecond_left > ULLONG_MAX) - *timeout = ULLONG_MAX; - else + if (mssecond_left > ULLONG_MAX) + *timeout = ULLONG_MAX; + else #endif /* UINT64 != ULLONG_MAX */ - *timeout = (unsigned long long) mssecond_left; - } + *timeout = (unsigned long long) mssecond_left; return MHD_YES; } return MHD_NO;