commit 73c43a7ef7bdccda1b6b6714d2d64b1714e9cdad
parent 0dd7f1732a7623929691d12e937211303d6dbd71
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
Date: Sun, 26 Dec 2021 12:02:04 +0300
Added workaround for system clock jumps back
Same OSes (namely OpenBSD) has system clocks that must be monotonic,
but in practice clocks may jump forward and back up to 500 milliseconds.
Diffstat:
2 files changed, 116 insertions(+), 66 deletions(-)
diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c
@@ -4181,6 +4181,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.
* @remark To be called only from thread that process connection's
@@ -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
@@ -1858,6 +1858,54 @@ thread_main_connection_upgrade (struct MHD_Connection *con)
/**
+ * 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;