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");