<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>
<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>
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>
<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>
global:
sd_device_get_diskseq;
sd_event_add_inotify_fd;
+ sd_event_source_set_ratelimit_expire_callback;
} LIBSYSTEMD_249;
uint64_t prepare_iteration;
sd_event_destroy_t destroy_callback;
+ sd_event_handler_t ratelimit_expire_callback;
LIST_FIELDS(sd_event_source, sources);
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);
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:
struct clock_data *d) {
sd_event_source *s;
+ bool callback_invoked = false;
int r;
assert(e);
* 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;
}
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) {
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;
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;
/* 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;
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);
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;
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) {
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);