From: Daan De Meyer Date: Wed, 12 Nov 2025 16:58:17 +0000 (+0100) Subject: sd-event: Add exit-on-idle support X-Git-Tag: v259-rc1~59^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=5a5cb6ba50594355734ff58487d2272a86b741b1;p=thirdparty%2Fsystemd.git 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. --- diff --git a/man/sd_event_set_exit_on_idle.xml b/man/sd_event_set_exit_on_idle.xml new file mode 100644 index 00000000000..1dd1042ad42 --- /dev/null +++ b/man/sd_event_set_exit_on_idle.xml @@ -0,0 +1,116 @@ + + + + + + + + sd_event_set_exit_on_idle + systemd + + + + sd_event_set_exit_on_idle + 3 + + + + sd_event_set_exit_on_idle + sd_event_get_exit_on_idle + + Enable event loop exit-on-idle support + + + + + #include <systemd/sd-event.h> + + + int sd_event_set_exit_on_idle + sd_event *event + int b + + + + int sd_event_get_exit_on_idle + sd_event *event + + + + + + + Description + + sd_event_set_exit_on_idle() may be used to + enable or disable the exit-on-idle support in the + event loop object specified in the event + parameter. If enabled, the event loop will exit with a zero exit code + there are no more enabled (SD_EVENT_ON, SD_EVENT_ONESHOT), + non-exit, non-post event sources. + + sd_event_get_exit_on_idle() may be used to + determine whether exit-on-idle support was previously requested by a + call to sd_event_set_exit_on_idle() with a true + b parameter and successfully enabled. + + + + Return Value + + On success, sd_event_set_exit_on_idle() and + sd_event_get_exit_on_idle() 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 b parameter. On failure, they return a negative errno-style error + code. + + + Errors + + Returned errors may indicate the following problems: + + + + + -ECHILD + + The event loop has been created in a different process, library or module instance. + + + + -EINVAL + + The passed event loop object was invalid. + + + + + + + + + + History + sd_event_set_exit_on_idle() and + sd_event_get_exit_on_idle() were added in version 259. + + + + See Also + + + systemd1 + sd-event3 + sd_event_new3 + sd_event_add_io3 + sd_event_add_time3 + sd_event_add_signal3 + sd_event_add_child3 + sd_event_add_inotify3 + sd_event_add_defer3 + systemd.service5 + + + + diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index d0e000bd0ca..a1fa7942642 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -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; diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 5794b63aaaf..42b5ba1f7eb 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -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; diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c index 52bdcf7ae22..e85fa2e4314 100644 --- a/src/libsystemd/sd-event/test-event.c +++ b/src/libsystemd/sd-event/test-event.c @@ -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); diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h index ac98b67da45..7f0c444cfa1 100644 --- a/src/systemd/sd-event.h +++ b/src/systemd/sd-event.h @@ -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);