sd-event: introduce callback invoked when event source ratelimit expires

This commit is contained in:
Michal Sekletar
2021-11-11 17:02:56 +01:00
parent c65417a011
commit fd69f22475
6 changed files with 86 additions and 14 deletions
+17 -5
View File
@@ -19,6 +19,7 @@
<refname>sd_event_source_set_ratelimit</refname> <refname>sd_event_source_set_ratelimit</refname>
<refname>sd_event_source_get_ratelimit</refname> <refname>sd_event_source_get_ratelimit</refname>
<refname>sd_event_source_is_ratelimited</refname> <refname>sd_event_source_is_ratelimited</refname>
<refname>sd_event_source_set_ratelimit_expire_callback</refname>
<refpurpose>Configure rate limiting on event sources</refpurpose> <refpurpose>Configure rate limiting on event sources</refpurpose>
</refnamediv> </refnamediv>
@@ -46,6 +47,12 @@
<paramdef>sd_event_source *<parameter>source</parameter></paramdef> <paramdef>sd_event_source *<parameter>source</parameter></paramdef>
</funcprototype> </funcprototype>
<funcprototype>
<funcdef>int <function>sd_event_source_set_ratelimit_expire_callback</function></funcdef>
<paramdef>sd_event_source *<parameter>source</parameter></paramdef>
<paramdef>sd_event_handler_t<parameter>callback</parameter></paramdef>
</funcprototype>
</funcsynopsis> </funcsynopsis>
</refsynopsisdiv> </refsynopsisdiv>
@@ -78,6 +85,10 @@
is currently affected by rate limiting, i.e. it has recently hit the rate limit and is currently is currently affected by rate limiting, i.e. it has recently hit the rate limit and is currently
temporarily disabled due to that.</para> temporarily disabled due to that.</para>
<para><function>sd_event_source_set_ratelimit_expire_callback</function> may be used to set a callback
function that is invoked every time the event source leaves rate limited state. Note that function is
called in the same event loop iteration in which state transition occured.</para>
<para>Rate limiting is currently implemented for I/O, timer, signal, defer and inotify event <para>Rate limiting is currently implemented for I/O, timer, signal, defer and inotify event
sources.</para> sources.</para>
</refsect1> </refsect1>
@@ -85,11 +96,12 @@
<refsect1> <refsect1>
<title>Return Value</title> <title>Return Value</title>
<para>On success, <function>sd_event_source_set_ratelimit()</function> and <para>On success, <function>sd_event_source_set_ratelimit()</function>,
<function>sd_event_source_get_ratelimit()</function> return a non-negative integer. On failure, they <function>sd_event_source_set_ratelimit_expire_callback</function> and
return a negative errno-style error code. <function>sd_event_source_is_ratelimited</function> returns <function>sd_event_source_get_ratelimit()</function> return a non-negative integer. On failure, they return
zero if rate limiting is currently not in effect and greater than zero if it is in effect; it returns a a negative errno-style error code. <function>sd_event_source_is_ratelimited</function> returns zero if rate
negative errno-style error code on failure.</para> limiting is currently not in effect and greater than zero if it is in effect; it returns a negative
errno-style error code on failure.</para>
<refsect2> <refsect2>
<title>Errors</title> <title>Errors</title>
+1
View File
@@ -767,4 +767,5 @@ LIBSYSTEMD_250 {
global: global:
sd_device_get_diskseq; sd_device_get_diskseq;
sd_event_add_inotify_fd; sd_event_add_inotify_fd;
sd_event_source_set_ratelimit_expire_callback;
} LIBSYSTEMD_249; } LIBSYSTEMD_249;
+1
View File
@@ -71,6 +71,7 @@ struct sd_event_source {
uint64_t prepare_iteration; uint64_t prepare_iteration;
sd_event_destroy_t destroy_callback; sd_event_destroy_t destroy_callback;
sd_event_handler_t ratelimit_expire_callback;
LIST_FIELDS(sd_event_source, sources); LIST_FIELDS(sd_event_source, sources);
+54 -9
View File
@@ -2908,7 +2908,7 @@ fail:
return r; 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; int r;
assert(s); assert(s);
@@ -2940,6 +2940,30 @@ static int event_source_leave_ratelimit(sd_event_source *s) {
ratelimit_reset(&s->rate_limit); ratelimit_reset(&s->rate_limit);
log_debug("Event source %p (%s) left rate limit state.", s, strna(s->description)); 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, %s: %m",
strna(s->description),
event_source_type_to_string(s->type),
s->exit_on_failure ? "exiting" : "disabling");
if (s->exit_on_failure)
(void) sd_event_exit(s->event, r);
}
if (s->n_ref == 0)
source_free(s);
else if (r < 0)
sd_event_source_set_enabled(s, SD_EVENT_OFF);
return 1;
}
return 0; return 0;
fail: fail:
@@ -3139,6 +3163,7 @@ static int process_timer(
struct clock_data *d) { struct clock_data *d) {
sd_event_source *s; sd_event_source *s;
bool callback_invoked = false;
int r; int r;
assert(e); assert(e);
@@ -3156,9 +3181,11 @@ static int process_timer(
* again. */ * again. */
assert(s->ratelimited); assert(s->ratelimited);
r = event_source_leave_ratelimit(s); r = event_source_leave_ratelimit(s, /* run_callback */ true);
if (r < 0) if (r < 0)
return r; return r;
else if (r == 1)
callback_invoked = true;
continue; continue;
} }
@@ -3173,7 +3200,7 @@ static int process_timer(
event_source_time_prioq_reshuffle(s); event_source_time_prioq_reshuffle(s);
} }
return 0; return callback_invoked;
} }
static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priority) { static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priority) {
@@ -4097,6 +4124,10 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
if (r < 0) if (r < 0)
goto finish; goto finish;
r = process_inotify(e);
if (r < 0)
goto finish;
r = process_timer(e, e->timestamp.realtime, &e->realtime); r = process_timer(e, e->timestamp.realtime, &e->realtime);
if (r < 0) if (r < 0)
goto finish; goto finish;
@@ -4105,10 +4136,6 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
if (r < 0) if (r < 0)
goto finish; goto finish;
r = process_timer(e, e->timestamp.monotonic, &e->monotonic);
if (r < 0)
goto finish;
r = process_timer(e, e->timestamp.realtime, &e->realtime_alarm); r = process_timer(e, e->timestamp.realtime, &e->realtime_alarm);
if (r < 0) if (r < 0)
goto finish; goto finish;
@@ -4117,9 +4144,20 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
if (r < 0) if (r < 0)
goto finish; goto finish;
r = process_inotify(e); r = process_timer(e, e->timestamp.monotonic, &e->monotonic);
if (r < 0) if (r < 0)
goto finish; 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 (event_next_pending(e)) { if (event_next_pending(e)) {
e->state = SD_EVENT_PENDING; e->state = SD_EVENT_PENDING;
@@ -4488,7 +4526,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, /* When ratelimiting is configured we'll always reset the rate limit state first and start fresh,
* non-ratelimited. */ * non-ratelimited. */
r = event_source_leave_ratelimit(s); r = event_source_leave_ratelimit(s, /* run_callback */ false);
if (r < 0) if (r < 0)
return r; return r;
@@ -4496,6 +4534,13 @@ _public_ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval
return 0; 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) { _public_ int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval, unsigned *ret_burst) {
assert_return(s, -EINVAL); assert_return(s, -EINVAL);
+12
View File
@@ -623,6 +623,11 @@ static int ratelimit_time_handler(sd_event_source *s, uint64_t usec, void *userd
return 0; return 0;
} }
static int expired = -1;
static int ratelimit_expired(sd_event_source *s, void *userdata) {
return ++expired;
}
static void test_ratelimit(void) { static void test_ratelimit(void) {
_cleanup_close_pair_ int p[2] = {-1, -1}; _cleanup_close_pair_ int p[2] = {-1, -1};
_cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(sd_event_unrefp) sd_event *e = NULL;
@@ -686,12 +691,19 @@ static void test_ratelimit(void) {
assert_se(sd_event_source_set_ratelimit(s, 1 * USEC_PER_SEC, 10) >= 0); 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 { do {
assert_se(sd_event_run(e, UINT64_MAX) >= 0); assert_se(sd_event_run(e, UINT64_MAX) >= 0);
} while (!sd_event_source_is_ratelimited(s)); } while (!sd_event_source_is_ratelimited(s));
log_info("ratelimit_time_handler: called 10 more times, event source got ratelimited"); log_info("ratelimit_time_handler: called 10 more times, event source got ratelimited");
assert_se(count == 20); 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);
} }
static void test_simple_timeout(void) { static void test_simple_timeout(void) {
+1
View File
@@ -166,6 +166,7 @@ int sd_event_source_set_exit_on_failure(sd_event_source *s, int b);
int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval_usec, unsigned burst); 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_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_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. */ /* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref);