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 07dbaa6ab0)
(cherry picked from commit c1f2ab9305)
This commit is contained in:
Lennart Poettering authored and Luca Boccassi committed 2026-11-05 14:48:10 +00:00
1 parent 2866438df3
commit 4a0e44fadf
4 files changed
+63 -6

No files matched your search

+3
View File
@@ -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);
+2 -4
View File
@@ -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;
+12 -2
View File
@@ -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");
+46
View File
@@ -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");