From fd69f2247520b0be3190ded96d646a415edc97b7 Mon Sep 17 00:00:00 2001
From: Michal Sekletar <msekleta@redhat.com>
Date: Mon, 4 Oct 2021 19:44:06 +0200
Subject: [PATCH] sd-event: introduce callback invoked when event source
 ratelimit expires

Conflict:code context adaptation
Reference:https://github.com/systemd/systemd-stable/commit/fd69f2247520b0be3190ded96d646a415edc97b7
---
 man/sd_event_source_set_ratelimit.xml  | 22 +++++++---
 src/libsystemd/libsystemd.sym          |  1 +
 src/libsystemd/sd-event/event-source.h |  1 +
 src/libsystemd/sd-event/sd-event.c     | 61 ++++++++++++++++++++++----
 src/libsystemd/sd-event/test-event.c   | 12 +++++
 src/systemd/sd-event.h                 |  1 +
 6 files changed, 85 insertions(+), 13 deletions(-)

diff --git a/man/sd_event_source_set_ratelimit.xml b/man/sd_event_source_set_ratelimit.xml
index ac8529074a..37354a09f6 100644
--- a/man/sd_event_source_set_ratelimit.xml
+++ b/man/sd_event_source_set_ratelimit.xml
@@ -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>
diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym
index 5e2fc9e231..2178668d11 100644
--- a/src/libsystemd/libsystemd.sym
+++ b/src/libsystemd/libsystemd.sym
@@ -767,4 +767,5 @@ LIBSYSTEMD_249 {
         sd_device_get_trigger_uuid;
         sd_device_new_from_ifname;
         sd_device_new_from_ifindex;
+        sd_event_source_set_ratelimit_expire_callback;
 } LIBSYSTEMD_248;
diff --git a/src/libsystemd/sd-event/event-source.h b/src/libsystemd/sd-event/event-source.h
index 41845c0bb5..74cbc26962 100644
--- a/src/libsystemd/sd-event/event-source.h
+++ b/src/libsystemd/sd-event/event-source.h
@@ -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);
 
diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c
index 0ca0248510..d8f84d9ba7 100644
--- a/src/libsystemd/sd-event/sd-event.c
+++ b/src/libsystemd/sd-event/sd-event.c
@@ -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,15 +4124,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;
 
@@ -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);
 
diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c
index b637639f26..0ac23c1118 100644
--- a/src/libsystemd/sd-event/test-event.c
+++ b/src/libsystemd/sd-event/test-event.c
@@ -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) {
diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h
index f4357aa593..63984eef15 100644
--- a/src/systemd/sd-event.h
+++ b/src/systemd/sd-event.h
@@ -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);
-- 
2.33.0

