From 51210a849ea7f163a1760de989756206c01dd758 Mon Sep 17 00:00:00 2001 From: Michal Sekletar Date: Mon, 4 Oct 2021 19:44:06 +0200 Subject: [PATCH] sd-event: introduce callback invoked when event source ratelimit expires (cherry picked from commit fd69f2247520b0be3190ded96d646a415edc97b7) Related: #2036608 --- src/libsystemd/libsystemd.sym | 5 +++ src/libsystemd/sd-event/sd-event.c | 61 +++++++++++++++++++++++----- src/libsystemd/sd-event/test-event.c | 12 ++++++ src/systemd/sd-event.h | 1 + 4 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 149d2e7b82..f4a1426248 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -579,3 +579,8 @@ global: sd_event_source_get_ratelimit; sd_event_source_is_ratelimited; } LIBSYSTEMD_239; + +LIBSYSTEMD_250 { +global: + sd_event_source_set_ratelimit_expire_callback; +} LIBSYSTEMD_248; diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 47cf93b3f4..0adfdd9e1a 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -125,6 +125,7 @@ struct sd_event_source { uint64_t prepare_iteration; sd_event_destroy_t destroy_callback; + sd_event_handler_t ratelimit_expire_callback; LIST_FIELDS(sd_event_source, sources); @@ -2734,7 +2735,7 @@ fail: return r; } -static int event_source_leave_ratelimit(sd_event_source *s) { +static int event_source_leave_ratelimit(sd_event_source *s, bool run_callback) { int r; assert(s); @@ -2766,6 +2767,23 @@ static int event_source_leave_ratelimit(sd_event_source *s) { ratelimit_reset(&s->rate_limit); log_debug("Event source %p (%s) left rate limit state.", s, strna(s->description)); + + if (run_callback && s->ratelimit_expire_callback) { + s->dispatching = true; + r = s->ratelimit_expire_callback(s, s->userdata); + s->dispatching = false; + + if (r < 0) { + log_debug_errno(r, "Ratelimit expiry callback of event source %s (type %s) returned error, disabling: %m", + strna(s->description), + event_source_type_to_string(s->type)); + + sd_event_source_set_enabled(s, SD_EVENT_OFF); + } + + return 1; + } + return 0; fail: @@ -2966,6 +2984,7 @@ static int process_timer( struct clock_data *d) { sd_event_source *s; + bool callback_invoked = false; int r; assert(e); @@ -2981,9 +3000,11 @@ static int process_timer( * again. */ assert(s->ratelimited); - r = event_source_leave_ratelimit(s); + r = event_source_leave_ratelimit(s, /* run_callback */ true); if (r < 0) return r; + else if (r == 1) + callback_invoked = true; continue; } @@ -2998,7 +3019,7 @@ static int process_timer( event_source_time_prioq_reshuffle(s); } - return 0; + return callback_invoked; } static int process_child(sd_event *e) { @@ -3698,15 +3719,15 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) { if (r < 0) goto finish; - r = process_timer(e, e->timestamp.realtime, &e->realtime); + r = process_inotify(e); if (r < 0) goto finish; - r = process_timer(e, e->timestamp.boottime, &e->boottime); + r = process_timer(e, e->timestamp.realtime, &e->realtime); if (r < 0) goto finish; - r = process_timer(e, e->timestamp.monotonic, &e->monotonic); + r = process_timer(e, e->timestamp.boottime, &e->boottime); if (r < 0) goto finish; @@ -3718,16 +3739,27 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) { if (r < 0) goto finish; + r = process_timer(e, e->timestamp.monotonic, &e->monotonic); + if (r < 0) + goto finish; + else if (r == 1) { + /* Ratelimit expiry callback was called. Let's postpone processing pending sources and + * put loop in the initial state in order to evaluate (in the next iteration) also sources + * there were potentially re-enabled by the callback. + * + * Wondering why we treat only this invocation of process_timer() differently? Once event + * source is ratelimited we essentially transform it into CLOCK_MONOTONIC timer hence + * ratelimit expiry callback is never called for any other timer type. */ + r = 0; + goto finish; + } + if (e->need_process_child) { r = process_child(e); if (r < 0) goto finish; } - r = process_inotify(e); - if (r < 0) - goto finish; - if (event_next_pending(e)) { e->state = SD_EVENT_PENDING; @@ -4054,7 +4086,7 @@ _public_ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval /* When ratelimiting is configured we'll always reset the rate limit state first and start fresh, * non-ratelimited. */ - r = event_source_leave_ratelimit(s); + r = event_source_leave_ratelimit(s, /* run_callback */ false); if (r < 0) return r; @@ -4062,6 +4094,13 @@ _public_ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval return 0; } +_public_ int sd_event_source_set_ratelimit_expire_callback(sd_event_source *s, sd_event_handler_t callback) { + assert_return(s, -EINVAL); + + s->ratelimit_expire_callback = callback; + return 0; +} + _public_ int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval, unsigned *ret_burst) { assert_return(s, -EINVAL); diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c index e3ee4cd5c3..9135b22839 100644 --- a/src/libsystemd/sd-event/test-event.c +++ b/src/libsystemd/sd-event/test-event.c @@ -506,6 +506,11 @@ static int ratelimit_time_handler(sd_event_source *s, uint64_t usec, void *userd return 0; } +static int expired = -1; +static int ratelimit_expired(sd_event_source *s, void *userdata) { + return ++expired; +} + static void test_ratelimit(void) { _cleanup_close_pair_ int p[2] = {-1, -1}; _cleanup_(sd_event_unrefp) sd_event *e = NULL; @@ -568,12 +573,19 @@ static void test_ratelimit(void) { assert_se(sd_event_source_set_ratelimit(s, 1 * USEC_PER_SEC, 10) >= 0); + /* Set callback that will be invoked when we leave rate limited state. */ + assert_se(sd_event_source_set_ratelimit_expire_callback(s, ratelimit_expired) >= 0); + do { assert_se(sd_event_run(e, UINT64_MAX) >= 0); } while (!sd_event_source_is_ratelimited(s)); log_info("ratelimit_time_handler: called 10 more times, event source got ratelimited"); assert_se(count == 20); + + /* Dispatch the event loop once more and check that ratelimit expiration callback got called */ + assert_se(sd_event_run(e, UINT64_MAX) >= 0); + assert_se(expired == 0); } int main(int argc, char *argv[]) { diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h index a17a9b3488..c2e9c9614d 100644 --- a/src/systemd/sd-event.h +++ b/src/systemd/sd-event.h @@ -147,6 +147,7 @@ int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval_usec, unsigned burst); int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval_usec, unsigned *ret_burst); int sd_event_source_is_ratelimited(sd_event_source *s); +int sd_event_source_set_ratelimit_expire_callback(sd_event_source *s, sd_event_handler_t callback); /* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */ _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref);