sd-event: Add exit-on-idle support

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.
This commit is contained in:
Daan De Meyer
2025-11-12 17:58:17 +01:00
parent c11e1001db
commit 5a5cb6ba50
5 changed files with 228 additions and 0 deletions

View File

@@ -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>

View 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;

View 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;

View 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);

View 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);