From 4a0e44fadf51141039dfdf6c7a08581cb97baef8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Nov 2026 16:21:18 +0100 Subject: [PATCH] curl-util: dispatch near curl timers accurately libcurl regularly asks for very short timeouts, most prominently a timeout of 0 to kick off a new transfer. We so far armed the curl timer with the default accuracy of 250ms, which means every such kick-off might be delayed by up to 250ms. Rather than disabling the accuracy logic altogether, pick the accuracy dynamically: if the timeout is closer than the default accuracy, request maximum accuracy, otherwise go back to the default accuracy, so that longer curl timeouts can still be coalesced with other wakeups. To make this possible, export the default accuracy constant of sd-event as EVENT_DEFAULT_ACCURACY_USEC in event-util.h. This is based on the analysis and fix by Joe Kurokawa in #44544, which set the accuracy to 1ms unconditionally. The test case test_curl_glue_start_latency() is taken from that PR as-is, written by Joe Kurokawa. Fixes: #44478 Alternative to: #44544 (cherry picked from commit 07dbaa6ab0897cc031526a1a35fec6e6892a4e9b) (cherry picked from commit c1f2ab93058754a70e321c670a35b035c6943410) --- src/libsystemd/sd-event/event-util.h | 3 ++ src/libsystemd/sd-event/sd-event.c | 6 ++-- src/shared/curl-util.c | 14 +++++++-- src/test/test-curl-util.c | 46 ++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/libsystemd/sd-event/event-util.h b/src/libsystemd/sd-event/event-util.h index ce213b9c9e4..1c142a04232 100644 --- a/src/libsystemd/sd-event/event-util.h +++ b/src/libsystemd/sd-event/event-util.h @@ -5,6 +5,9 @@ #include "sd-forward.h" +/* The timer accuracy sd-event uses if 0 is specified as accuracy */ +#define EVENT_DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC) + #define PROTECT_EVENT(e) \ _unused_ _cleanup_(sd_event_unrefp) sd_event *_ref = sd_event_ref(e); diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 1973e13b5bf..231ed955b54 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -45,8 +45,6 @@ #include "strxcpyx.h" #include "time-util.h" -#define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC) - static bool EVENT_SOURCE_WATCH_PIDFD(const sd_event_source *s) { /* Returns true if this is a PID event source and can be implemented by watching EPOLLIN */ return s && @@ -1429,7 +1427,7 @@ _public_ int sd_event_add_time( return -ENOMEM; s->time.next = usec; - s->time.accuracy = accuracy == 0 ? DEFAULT_ACCURACY_USEC : accuracy; + s->time.accuracy = accuracy == 0 ? EVENT_DEFAULT_ACCURACY_USEC : accuracy; s->time.callback = callback; s->earliest_index = s->latest_index = PRIOQ_IDX_NULL; s->userdata = userdata; @@ -3216,7 +3214,7 @@ _public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec return r; if (usec == 0) - usec = DEFAULT_ACCURACY_USEC; + usec = EVENT_DEFAULT_ACCURACY_USEC; s->time.accuracy = usec; diff --git a/src/shared/curl-util.c b/src/shared/curl-util.c index 961a97ad2f7..52ffe92aa39 100644 --- a/src/shared/curl-util.c +++ b/src/shared/curl-util.c @@ -10,6 +10,7 @@ #include "alloc-util.h" #include "dlfcn-util.h" +#include "event-util.h" #include "fd-util.h" #include "hashmap.h" #include "set.h" @@ -256,7 +257,7 @@ static int curl_glue_on_timer(sd_event_source *s, uint64_t usec, void *userdata) static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata) { CurlGlue *g = ASSERT_PTR(userdata); - usec_t usec; + usec_t usec, accuracy; assert(curl); @@ -276,14 +277,23 @@ static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata usec = (usec_t) timeout_ms * USEC_PER_MSEC + USEC_PER_MSEC - 1; + /* libcurl frequently asks for very short (often zero) timeouts, for example to kick off a new + * transfer. Applying the default accuracy to those would delay them by up to that much, hence + * dispatch timers that elapse sooner than the default accuracy as accurately as possible, and use + * the default accuracy for everything else, so that we can still coalesce wakeups for those. */ + accuracy = usec < EVENT_DEFAULT_ACCURACY_USEC ? 1 : 0; + if (g->timer) { if (sd_event_source_set_time_relative(g->timer, usec) < 0) return -1; + if (sd_event_source_set_time_accuracy(g->timer, accuracy) < 0) + return -1; + if (sd_event_source_set_enabled(g->timer, SD_EVENT_ONESHOT) < 0) return -1; } else { - if (sd_event_add_time_relative(g->event, &g->timer, CLOCK_BOOTTIME, usec, 0, curl_glue_on_timer, g) < 0) + if (sd_event_add_time_relative(g->event, &g->timer, CLOCK_BOOTTIME, usec, accuracy, curl_glue_on_timer, g) < 0) return -1; (void) sd_event_source_set_description(g->timer, "curl-timer"); diff --git a/src/test/test-curl-util.c b/src/test/test-curl-util.c index fb3d2782006..7f39ed0af03 100644 --- a/src/test/test-curl-util.c +++ b/src/test/test-curl-util.c @@ -11,8 +11,11 @@ #include "io-util.h" #include "string-util.h" #include "tests.h" +#include "time-util.h" #include "tmpfile-util.h" +#define N_TRANSFERS 4U + #define ASSERT_CURL_OK(expr) \ ({ \ CURLcode _code = (expr); \ @@ -271,6 +274,49 @@ TEST(curl_concurrent) { } } +TEST(curl_glue_start_latency) { + /* libcurl asks the glue for a timeout of 0 to start a transfer, which the glue implements with an + * sd-event timer. If that timer is armed with accuracy 0, sd-event uses its default accuracy of + * 250ms and the start of every transfer is delayed by up to that much. Each transfer here uses a + * fresh event loop and glue because on_finished() calls sd_event_exit(), which leaves the loop + * SD_EVENT_FINISHED, so the old loop and the glue bound to it can't be reused for the next + * transfer; that also means each transfer hits the timer's first-creation path. A few such + * delays add up to well over the limit below, whereas reading a small file takes milliseconds. */ + + _cleanup_(unlink_and_freep) char *path = NULL; + _cleanup_free_ char *url = NULL; + ASSERT_OK(make_tmp_url(&path, &url, "latency")); + + usec_t start = now(CLOCK_MONOTONIC); + + for (unsigned i = 0; i < N_TRANSFERS; i++) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_new(&event)); + + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + ASSERT_OK(curl_glue_new(&g, event)); + + _cleanup_(context_done) Context f = { .event = sd_event_ref(event) }; + + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + ASSERT_OK(build_easy(url, &f, &easy)); + + ASSERT_OK(curl_glue_perform_async(g, easy, on_finished, &f, /* ret_slot= */ NULL)); + TAKE_PTR(easy); + + ASSERT_OK(sd_event_loop(event)); + + ASSERT_TRUE(f.finished); + ASSERT_CURL_OK(f.result); + ASSERT_STREQ(f.body, "latency"); + } + + usec_t elapsed = usec_sub_unsigned(now(CLOCK_MONOTONIC), start); + log_info("%u transfers took %s", N_TRANSFERS, FORMAT_TIMESPAN(elapsed, USEC_PER_MSEC)); + + ASSERT_LT(elapsed, N_TRANSFERS * 125 * USEC_PER_MSEC); +} + static int intro(void) { if (dlopen_curl(LOG_DEBUG) < 0) return log_tests_skipped("libcurl not available");