]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Feature] libev: add fake-clock and time-resync hooks for tests
authorVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 9 May 2026 13:37:58 +0000 (14:37 +0100)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 9 May 2026 13:37:58 +0000 (14:37 +0100)
Three local extensions on top of stock libev:

  * ev_set_fake_time_cb / ev_get_fake_time_cb — process-global hook;
    when set, replaces both ev_time() and the internal monotonic
    clock so timers and ev_now() advance under test control.
  * ev_now_resync — force-resync the loop's cached realtime/monotonic
    state from the current sources, discarding interpolation. Required
    after installing or removing a fake clock; also useful after any
    other large clock discontinuity.

Default cb is NULL, so production cost is one predicted-false branch
in each clock read.

Local style follows libev's (GNU-ish, two-space, space-before-paren),
not the rspamd tree style — bypassing clang-format here intentionally.

contrib/libev/Changes
contrib/libev/ev.c
contrib/libev/ev.h

index 04e58ba0ff8d31529cebfebb55449a18149e442d..3d59f3843527eda1f1d1861e7813bb4ba64bc86e 100644 (file)
@@ -1,5 +1,18 @@
 Revision history for libev, a high-performance and full-featured event loop.
 
+Rspamd local extensions (not present in upstream libev):
+        - ev_active_cnt: returns the loop's activecnt counter.
+        - ev_now_update_if_cheap: cheap variant of ev_now_update for paths
+          that need a fresh cached time but cannot afford a realtime read.
+        - ev_now_resync: force-resync the loop's cached realtime/monotonic
+          state from the current time sources, discarding interpolation.
+          Use after installing/removing a fake clock.
+        - ev_set_fake_time_cb / ev_get_fake_time_cb: process-global fake
+          clock hook for unit tests; when set, replaces both ev_time() and
+          the internal monotonic clock so timers and ev_now() advance under
+          test control. NULL by default; cost in production is one
+          predicted-false branch.
+
 4.25 Fri Dec 21 07:49:20 CET 2018
         - INCOMPATIBLE CHANGE: EV_THROW was renamed to EV_NOEXCEPT
           (EV_THROW sitll provided) and now uses noexcept on C++11 or newer.
index 63ab289302ad1bd3321a5fac65b0d2c279073891..c52a35c2525ae4c32360857be879c9c8fc18c493 100644 (file)
@@ -2075,6 +2075,26 @@ ev_set_allocator (void *(*cb)(void *ptr, long size) EV_NOEXCEPT) EV_NOEXCEPT
   alloc = cb;
 }
 
+/* RSPAMD LOCAL EXTENSION: process-global fake-clock hook for unit tests.
+ * When non-NULL, both ev_time() and get_clock() return the callback's value
+ * instead of reading the system clocks. See ev.h for the public API.
+ */
+static ev_fake_time_cb fake_time_cb = 0;
+
+ecb_cold
+void
+ev_set_fake_time_cb (ev_fake_time_cb cb) EV_NOEXCEPT
+{
+  fake_time_cb = cb;
+}
+
+ecb_cold
+ev_fake_time_cb
+ev_get_fake_time_cb (void) EV_NOEXCEPT
+{
+  return fake_time_cb;
+}
+
 inline_speed void *
 ev_realloc (void *ptr, long size)
 {
@@ -2202,6 +2222,10 @@ typedef struct
 ev_tstamp
 ev_time (void) EV_NOEXCEPT
 {
+  /* RSPAMD LOCAL EXTENSION: fake clock hook for unit tests. */
+  if (ecb_expect_false (fake_time_cb != 0))
+    return fake_time_cb ();
+
 #if EV_USE_REALTIME
   if (ecb_expect_true (have_realtime))
     {
@@ -2222,6 +2246,10 @@ ev_time (void) EV_NOEXCEPT
 inline_size ev_tstamp
 get_clock (void)
 {
+  /* RSPAMD LOCAL EXTENSION: fake clock hook for unit tests. */
+  if (ecb_expect_false (fake_time_cb != 0))
+    return fake_time_cb ();
+
 #if EV_USE_MONOTONIC
   if (ecb_expect_true (have_monotonic))
     {
@@ -5689,6 +5717,16 @@ ev_now_update_if_cheap (EV_P) EV_NOEXCEPT
        if (have_cheap_timer) time_update (EV_A_ 1e100);
 }
 
+/* RSPAMD LOCAL EXTENSION: see ev.h. */
+void
+ev_now_resync (EV_P) EV_NOEXCEPT
+{
+       ev_rt_now = ev_time ();
+       mn_now    = get_clock ();
+       now_floor = mn_now;
+       rtmn_diff = ev_rt_now - mn_now;
+}
+
 int
 ev_active_cnt (EV_P) EV_NOEXCEPT
 {
index 7135a08a57dff7ae477a3a7f5a8887bc4c437d8d..a2a9715824cec8a66bd57ce33278ecc1e23a3c2b 100644 (file)
@@ -553,6 +553,17 @@ EV_API_DECL void ev_set_allocator (void *(*cb)(void *ptr, long size) EV_NOEXCEPT
  */
 EV_API_DECL void ev_set_syserr_cb (void (*cb)(const char *msg) EV_NOEXCEPT) EV_NOEXCEPT;
 
+/* RSPAMD LOCAL EXTENSION:
+ * Install a process-global fake clock callback used by ev_time() and the
+ * internal monotonic clock read. When set, the callback's return value
+ * replaces both the realtime and monotonic clock sources, so timers and
+ * ev_now()/ev_now_update() advance under test control. Pass NULL to
+ * restore the system clocks. Intended for unit tests only.
+ */
+typedef ev_tstamp (*ev_fake_time_cb)(void);
+EV_API_DECL void ev_set_fake_time_cb (ev_fake_time_cb cb) EV_NOEXCEPT;
+EV_API_DECL ev_fake_time_cb ev_get_fake_time_cb (void) EV_NOEXCEPT;
+
 #if EV_MULTIPLICITY
 
 /* the default loop is the only one that handles signals and child watchers */
@@ -602,6 +613,13 @@ EV_API_DECL void ev_now_update (EV_P) EV_NOEXCEPT; /* update event loop time */
  * are used in system.
  */
 EV_API_DECL void ev_now_update_if_cheap (EV_P) EV_NOEXCEPT;
+/* RSPAMD LOCAL EXTENSION:
+ * Force-resync the loop's cached realtime/monotonic state from the current
+ * time sources, discarding any interpolation state. Use after installing
+ * or removing a fake clock via ev_set_fake_time_cb(), or after any other
+ * large discontinuity in the clock source.
+ */
+EV_API_DECL void ev_now_resync (EV_P) EV_NOEXCEPT;
 
 #if EV_WALK_ENABLE
 /* walk (almost) all watchers in the loop of a given type, invoking the */