mirror of
https://github.com/systemd/systemd
synced 2024-07-21 18:24:38 +00:00
sd-event: introduce callback invoked when event source ratelimit expires
This commit is contained in:
parent
c65417a011
commit
fd69f22475
|
@ -19,6 +19,7 @@
|
|||
<refname>sd_event_source_set_ratelimit</refname>
|
||||
<refname>sd_event_source_get_ratelimit</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>
|
||||
</refnamediv>
|
||||
|
@ -46,6 +47,12 @@
|
|||
<paramdef>sd_event_source *<parameter>source</parameter></paramdef>
|
||||
</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>
|
||||
</refsynopsisdiv>
|
||||
|
||||
|
@ -78,6 +85,10 @@
|
|||
is currently affected by rate limiting, i.e. it has recently hit the rate limit and is currently
|
||||
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
|
||||
sources.</para>
|
||||
</refsect1>
|
||||
|
@ -85,11 +96,12 @@
|
|||
<refsect1>
|
||||
<title>Return Value</title>
|
||||
|
||||
<para>On success, <function>sd_event_source_set_ratelimit()</function> and
|
||||
<function>sd_event_source_get_ratelimit()</function> return a non-negative integer. On failure, they
|
||||
return a negative errno-style error code. <function>sd_event_source_is_ratelimited</function> returns
|
||||
zero if rate 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>
|
||||
<para>On success, <function>sd_event_source_set_ratelimit()</function>,
|
||||
<function>sd_event_source_set_ratelimit_expire_callback</function> and
|
||||
<function>sd_event_source_get_ratelimit()</function> return a non-negative integer. On failure, they return
|
||||
a negative errno-style error code. <function>sd_event_source_is_ratelimited</function> returns zero if rate
|
||||
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>
|
||||
<title>Errors</title>
|
||||
|
|
|
@ -767,4 +767,5 @@ LIBSYSTEMD_250 {
|
|||
global:
|
||||
sd_device_get_diskseq;
|
||||
sd_event_add_inotify_fd;
|
||||
sd_event_source_set_ratelimit_expire_callback;
|
||||
} LIBSYSTEMD_249;
|
||||
|
|
|
@ -71,6 +71,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);
|
||||
|
||||
|
|
|
@ -2908,7 +2908,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);
|
||||
|
@ -2940,6 +2940,30 @@ 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, %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;
|
||||
|
||||
fail:
|
||||
|
@ -3139,6 +3163,7 @@ static int process_timer(
|
|||
struct clock_data *d) {
|
||||
|
||||
sd_event_source *s;
|
||||
bool callback_invoked = false;
|
||||
int r;
|
||||
|
||||
assert(e);
|
||||
|
@ -3156,9 +3181,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;
|
||||
}
|
||||
|
@ -3173,7 +3200,7 @@ static int process_timer(
|
|||
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) {
|
||||
|
@ -4097,6 +4124,10 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
|
|||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = process_inotify(e);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = process_timer(e, e->timestamp.realtime, &e->realtime);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
@ -4105,10 +4136,6 @@ _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;
|
||||
|
||||
r = process_timer(e, e->timestamp.realtime, &e->realtime_alarm);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
@ -4117,9 +4144,20 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
|
|||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = process_inotify(e);
|
||||
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 (event_next_pending(e)) {
|
||||
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,
|
||||
* non-ratelimited. */
|
||||
r = event_source_leave_ratelimit(s);
|
||||
r = event_source_leave_ratelimit(s, /* run_callback */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -4496,6 +4534,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);
|
||||
|
||||
|
|
|
@ -623,6 +623,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;
|
||||
|
@ -686,12 +691,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);
|
||||
}
|
||||
|
||||
static void test_simple_timeout(void) {
|
||||
|
|
|
@ -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_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);
|
||||
|
|
Loading…
Reference in a new issue