]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-event: Add exit-on-idle support
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 12 Nov 2025 16:58:17 +0000 (17:58 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 14 Nov 2025 10:38:56 +0000 (11:38 +0100)
Sometimes it's hard to assign responsibility to a specific event source
for exiting when there's no more work to be done. So let's add exit-on-idle
support where we exit when there are no more event sources.

man/sd_event_set_exit_on_idle.xml [new file with mode: 0644]
src/libsystemd/libsystemd.sym
src/libsystemd/sd-event/sd-event.c
src/libsystemd/sd-event/test-event.c
src/systemd/sd-event.h

diff --git a/man/sd_event_set_exit_on_idle.xml b/man/sd_event_set_exit_on_idle.xml
new file mode 100644 (file)
index 0000000..1dd1042
--- /dev/null
@@ -0,0 +1,116 @@
+<?xml version='1.0'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="sd_event_set_exit_on_idle" xmlns:xi="http://www.w3.org/2001/XInclude">
+
+  <refentryinfo>
+    <title>sd_event_set_exit_on_idle</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>sd_event_set_exit_on_idle</refentrytitle>
+    <manvolnum>3</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>sd_event_set_exit_on_idle</refname>
+    <refname>sd_event_get_exit_on_idle</refname>
+
+    <refpurpose>Enable event loop exit-on-idle support</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <funcsynopsis>
+      <funcsynopsisinfo>#include &lt;systemd/sd-event.h&gt;</funcsynopsisinfo>
+
+      <funcprototype>
+        <funcdef>int <function>sd_event_set_exit_on_idle</function></funcdef>
+        <paramdef>sd_event *<parameter>event</parameter></paramdef>
+        <paramdef>int b</paramdef>
+      </funcprototype>
+
+      <funcprototype>
+        <funcdef>int <function>sd_event_get_exit_on_idle</function></funcdef>
+        <paramdef>sd_event *<parameter>event</parameter></paramdef>
+      </funcprototype>
+
+    </funcsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><function>sd_event_set_exit_on_idle()</function> may be used to
+    enable or disable the exit-on-idle support in the
+    event loop object specified in the <parameter>event</parameter>
+    parameter. If enabled, the event loop will exit with a zero exit code
+    there are no more enabled (<constant>SD_EVENT_ON</constant>, <constant>SD_EVENT_ONESHOT</constant>),
+    non-exit, non-post event sources.</para>
+
+    <para><function>sd_event_get_exit_on_idle()</function> may be used to
+    determine whether exit-on-idle support was previously requested by a
+    call to <function>sd_event_set_exit_on_idle()</function> with a true
+    <parameter>b</parameter> parameter and successfully enabled.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Return Value</title>
+
+    <para>On success, <function>sd_event_set_exit_on_idle()</function> and
+    <function>sd_event_get_exit_on_idle()</function> return a non-zero positive integer if the exit-on-idle
+    support was successfully enabled. They return zero if the exit-on-idle support was explicitly disabled
+    with a false <parameter>b</parameter> parameter. On failure, they return a negative errno-style error
+    code.</para>
+
+    <refsect2>
+      <title>Errors</title>
+
+      <para>Returned errors may indicate the following problems:</para>
+
+      <variablelist>
+
+        <varlistentry>
+          <term><constant>-ECHILD</constant></term>
+
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><constant>-EINVAL</constant></term>
+
+          <listitem><para>The passed event loop object was invalid.</para></listitem>
+        </varlistentry>
+
+      </variablelist>
+    </refsect2>
+  </refsect1>
+
+  <xi:include href="libsystemd-pkgconfig.xml" />
+
+  <refsect1>
+    <title>History</title>
+    <para><function>sd_event_set_exit_on_idle()</function> and
+    <function>sd_event_get_exit_on_idle()</function> were added in version 259.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+
+    <para><simplelist type="inline">
+      <member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>sd-event</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>sd_event_new</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>sd_event_add_io</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>sd_event_add_time</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>sd_event_add_signal</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>sd_event_add_child</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>sd_event_add_inotify</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>sd_event_add_defer</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
+    </simplelist></para>
+  </refsect1>
+
+</refentry>
index d0e000bd0ca54ab0b4a0222cead3f0d5fd546efb..a1fa7942642c7ea04366d7fa113314b57625be76 100644 (file)
@@ -1081,5 +1081,7 @@ global:
 
 LIBSYSTEMD_259 {
 global:
+        sd_event_set_exit_on_idle;
+        sd_event_get_exit_on_idle;
         sd_varlink_is_connected;
 } LIBSYSTEMD_258;
index 5794b63aaaf6d476d7ec94733daa08ee8d5ae01c..42b5ba1f7eb9ee95aa0535db561a8e316425c8c2 100644 (file)
@@ -157,6 +157,7 @@ struct sd_event {
         bool need_process_child:1;
         bool watchdog:1;
         bool profile_delays:1;
+        bool exit_on_idle:1;
 
         int exit_code;
 
@@ -4422,6 +4423,28 @@ static int event_memory_pressure_write_list(sd_event *e) {
         return 0;
 }
 
+static bool event_loop_idle(sd_event *e) {
+        assert(e);
+
+        LIST_FOREACH(sources, s, e->sources) {
+                /* Exit sources only trigger on exit, so whether they're enabled or not doesn't matter when
+                 * we're deciding if the event loop is idle or not. */
+                if (s->type == SOURCE_EXIT)
+                        continue;
+
+                if (s->enabled == SD_EVENT_OFF)
+                        continue;
+
+                /* Post event sources always need another active event source to become pending. */
+                if (s->type == SOURCE_POST && !s->pending)
+                        continue;
+
+                return false;
+        }
+
+        return true;
+}
+
 _public_ int sd_event_prepare(sd_event *e) {
         int r;
 
@@ -4439,6 +4462,9 @@ _public_ int sd_event_prepare(sd_event *e) {
         /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */
         PROTECT_EVENT(e);
 
+        if (!e->exit_requested && e->exit_on_idle && event_loop_idle(e))
+                (void) sd_event_exit(e, 0);
+
         if (e->exit_requested)
                 goto pending;
 
@@ -5243,6 +5269,22 @@ _public_ int sd_event_set_signal_exit(sd_event *e, int b) {
         return change;
 }
 
+_public_ int sd_event_set_exit_on_idle(sd_event *e, int b) {
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_origin_changed(e), -ECHILD);
+
+        return e->exit_on_idle = b;
+}
+
+_public_ int sd_event_get_exit_on_idle(sd_event *e) {
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_origin_changed(e), -ECHILD);
+
+        return e->exit_on_idle;
+}
+
 _public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty) {
         _cleanup_free_ char *b = NULL;
         _cleanup_free_ void *w = NULL;
index 52bdcf7ae224a888e164c1b21ac6b25295cace66..e85fa2e4314a114667153bf7d390afa3a317aa7a 100644 (file)
@@ -1065,4 +1065,70 @@ TEST(child_pidfd_wnowait) {
         ASSERT_EQ(si.si_status, 42);
 }
 
+static int exit_on_idle_defer_handler(sd_event_source *s, void *userdata) {
+        unsigned *c = ASSERT_PTR(userdata);
+
+        /* Should not be reached on third call because the event loop should exit before */
+        ASSERT_LT(*c, 2u);
+
+        (*c)++;
+
+        /* Disable ourselves, which should trigger exit-on-idle after the second iteration */
+        if (*c == 2)
+                sd_event_source_set_enabled(s, SD_EVENT_OFF);
+
+        return 0;
+}
+
+static int exit_on_idle_post_handler(sd_event_source *s, void *userdata) {
+        unsigned *c = ASSERT_PTR(userdata);
+
+        /* Should not be reached on third call because the event loop should exit before */
+        ASSERT_LT(*c, 2u);
+
+        (*c)++;
+        return 0;
+}
+
+static int exit_on_idle_exit_handler(sd_event_source *s, void *userdata) {
+        return 0;
+}
+
+TEST(exit_on_idle) {
+        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+        ASSERT_OK(sd_event_new(&e));
+        ASSERT_OK(sd_event_set_exit_on_idle(e, true));
+        ASSERT_OK_POSITIVE(sd_event_get_exit_on_idle(e));
+
+        /* Create a recurring defer event source. */
+        _cleanup_(sd_event_source_unrefp) sd_event_source *d = NULL;
+        unsigned dc = 0;
+        ASSERT_OK(sd_event_add_defer(e, &d, exit_on_idle_defer_handler, &dc));
+        ASSERT_OK(sd_event_source_set_enabled(d, SD_EVENT_ON));
+
+        /* This post event source should not keep the event loop running after the defer source is disabled. */
+        _cleanup_(sd_event_source_unrefp) sd_event_source *p = NULL;
+        unsigned pc = 0;
+        ASSERT_OK(sd_event_add_post(e, &p, exit_on_idle_post_handler, &pc));
+        ASSERT_OK(sd_event_source_set_enabled(p, SD_EVENT_ON));
+        ASSERT_OK(sd_event_source_set_priority(p, SD_EVENT_PRIORITY_IMPORTANT));
+
+        /* And neither should this exit event source. */
+        ASSERT_OK(sd_event_add_exit(e, NULL, exit_on_idle_exit_handler, NULL));
+
+        /* Run the event loop - it should exit after we disable the event source */
+        ASSERT_OK(sd_event_loop(e));
+        ASSERT_EQ(dc, 2u);
+        ASSERT_EQ(pc, 2u);
+}
+
+TEST(exit_on_idle_no_sources) {
+        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+        ASSERT_OK(sd_event_new(&e));
+        ASSERT_OK(sd_event_set_exit_on_idle(e, true));
+
+        /* Running loop with no sources should return immediately with success */
+        ASSERT_OK(sd_event_loop(e));
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);
index ac98b67da4509a771a4484ac030dddb57213d2e8..7f0c444cfa13ab21a4f39d0cf237057974145d1d 100644 (file)
@@ -116,6 +116,8 @@ int sd_event_set_watchdog(sd_event *e, int b);
 int sd_event_get_watchdog(sd_event *e);
 int sd_event_get_iteration(sd_event *e, uint64_t *ret);
 int sd_event_set_signal_exit(sd_event *e, int b);
+int sd_event_set_exit_on_idle(sd_event *e, int b);
+int sd_event_get_exit_on_idle(sd_event *e);
 
 sd_event_source* sd_event_source_ref(sd_event_source *s);
 sd_event_source* sd_event_source_unref(sd_event_source *s);