From 1b4ab5a209c4874e01cf9577c740b82567458b09 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Fri, 16 May 2025 18:10:46 +0200 Subject: [PATCH] core/socket: introduce DeferTrigger= and DeferTriggerMaxSec= Alternative to b50f6dbe574b0421db7dbf200ad951186382277d The commit naively returned early from socket_enter_running(), which however is quite problematic, as the socket will be woken up over and over again without doing a thing, until we eventually hit Poll/TriggerLimit*=. On top of that it requires hacks to hold the start job for initrd-switch-root.service up. Overall I doubt that is the right approach. Let's instead hook this into our job engine, and try to activate the service again when some other units are stopped. If all installed jobs have been run yet we're still seeing the conflict or the manually selected timeout is reached, fail the socket as before. --- man/org.freedesktop.systemd1.xml | 18 +++- man/systemd.socket.xml | 31 ++++++ src/basic/unit-def.c | 1 + src/basic/unit-def.h | 1 + src/core/dbus-socket.c | 10 ++ src/core/job.c | 4 + src/core/load-fragment-gperf.gperf.in | 2 + src/core/load-fragment.c | 1 + src/core/load-fragment.h | 1 + src/core/manager.c | 29 ++++++ src/core/manager.h | 4 + src/core/socket.c | 136 ++++++++++++++++++++++++-- src/core/socket.h | 14 +++ src/core/unit.c | 27 +++++ src/core/unit.h | 9 ++ src/shared/bus-unit-util.c | 6 +- 16 files changed, 282 insertions(+), 12 deletions(-) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 95f9a4f449..3cf0e18709 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -4962,6 +4962,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { readonly t PollLimitIntervalUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly u PollLimitBurst = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s DeferTrigger = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly t DeferTriggerMaxUSec = ...; readonly u UID = ...; readonly u GID = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") @@ -5626,6 +5630,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + @@ -6244,6 +6252,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + @@ -12112,8 +12124,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ProtectHostnameEx, PassPIDFD, AcceptFileDescriptors, - DelegateNamespaces, and - RemoveSubgroup() were added in version 258. + DelegateNamespaces, + RemoveSubgroup(), + DeferTrigger, and + DeferTriggerMaxUSec were added in version 258. Mount Unit Objects diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml index 91552d691f..213ceffcdb 100644 --- a/man/systemd.socket.xml +++ b/man/systemd.socket.xml @@ -956,6 +956,37 @@ + + DeferTrigger= + + Takes a boolean argument, or patient. May only be used when Accept=no. + If enabled, job mode lenient instead of replace is used when + triggering the service, which means currently activating/running units that conflict with the service + won't be disturbed/brought down. Furthermore, if a conflict exists, the socket unit will wait for + current job queue to complete and potentially defer the activation by then. An upper limit of total time + to wait can be configured via DeferTriggerMaxSec=. If set to , + the socket unit will fail if all jobs have finished or the timeout has been reached but the conflict remains. + If , always wait until DeferTriggerMaxSec= elapses. + Defaults to no. + + This setting is particularly useful if the socket unit should stay active across switch-root/soft-reboot + operations while the triggered service is stopped. + + + + + + DeferTriggerMaxSec= + + Configures the maximum time to defer the triggering when DeferTrigger= + is enabled. If the service cannot be activated within the specified time, the socket will be considered + failed and get terminated. Takes a unit-less value in seconds, or a time span value such as "5min 20s". + Pass 0 or infinity to disable the timeout logic (the default). + + + + + PassFileDescriptorsToExec= diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index 45911fb190..8bc5f56532 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -250,6 +250,7 @@ static const char* const socket_state_table[_SOCKET_STATE_MAX] = { [SOCKET_START_CHOWN] = "start-chown", [SOCKET_START_POST] = "start-post", [SOCKET_LISTENING] = "listening", + [SOCKET_DEFERRED] = "deferred", [SOCKET_RUNNING] = "running", [SOCKET_STOP_PRE] = "stop-pre", [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm", diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index f82d072ed5..fd55950ea6 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -169,6 +169,7 @@ typedef enum SocketState { SOCKET_START_CHOWN, SOCKET_START_POST, SOCKET_LISTENING, + SOCKET_DEFERRED, SOCKET_RUNNING, SOCKET_STOP_PRE, SOCKET_STOP_PRE_SIGTERM, diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index de96f00a15..02039dd6db 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -21,6 +21,7 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, socket_result, SocketRe static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_bind_ipv6_only, socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); static BUS_DEFINE_PROPERTY_GET(property_get_fdname, "s", Socket, socket_fdname); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_timestamping, socket_timestamping, SocketTimestamping); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_defer_trigger, socket_defer_trigger, SocketDeferTrigger); static int property_get_listen( sd_bus *bus, @@ -115,6 +116,8 @@ const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, trigger_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PollLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, poll_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PollLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, poll_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DeferTrigger", "s", property_get_defer_trigger, offsetof(Socket, defer_trigger), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DeferTriggerMaxUSec", "t", bus_property_get_usec, offsetof(Socket, defer_trigger_max_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("PassFileDescriptorsToExec", "b", bus_property_get_bool, offsetof(Socket, pass_fds_to_exec), SD_BUS_VTABLE_PROPERTY_CONST), @@ -148,6 +151,7 @@ static BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(ifname, ifname_valid); static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(ip_tos, "i", int32_t, int, "%" PRIi32, ip_tos_to_string_alloc); static BUS_DEFINE_SET_TRANSIENT_TO_STRING(socket_protocol, "i", int32_t, int, "%" PRIi32, socket_protocol_to_string); static BUS_DEFINE_SET_TRANSIENT_PARSE(socket_timestamping, SocketTimestamping, socket_timestamping_from_string_harder); +static BUS_DEFINE_SET_TRANSIENT_PARSE(socket_defer_trigger, SocketDeferTrigger, socket_defer_trigger_from_string); static int bus_socket_set_transient_property( Socket *s, @@ -274,6 +278,12 @@ static int bus_socket_set_transient_property( if (streq(name, "PollLimitIntervalUSec")) return bus_set_transient_usec(u, name, &s->poll_limit.interval, message, flags, error); + if (streq(name, "DeferTrigger")) + return bus_set_transient_socket_defer_trigger(u, name, &s->defer_trigger, message, flags, error); + + if (streq(name, "DeferTriggerMaxUSec")) + return bus_set_transient_usec_fix_0(u, name, &s->defer_trigger_max_usec, message, flags, error); + if (streq(name, "SmackLabel")) return bus_set_transient_string(u, name, &s->smack, message, flags, error); diff --git a/src/core/job.c b/src/core/job.c index a60025cffc..bac12542dc 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -1108,6 +1108,10 @@ finish: unit_submit_to_stop_when_bound_queue(u); unit_submit_to_stop_when_unneeded_queue(u); + /* All jobs might have finished, let's see */ + if (u->manager->may_dispatch_stop_notify_queue == 0) + u->manager->may_dispatch_stop_notify_queue = -1; + manager_check_finished(u->manager); return 0; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index aacc76d053..96fe04aaf4 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -528,6 +528,8 @@ Socket.TriggerLimitIntervalSec, config_parse_sec, Socket.TriggerLimitBurst, config_parse_unsigned, 0, offsetof(Socket, trigger_limit.burst) Socket.PollLimitIntervalSec, config_parse_sec, 0, offsetof(Socket, poll_limit.interval) Socket.PollLimitBurst, config_parse_unsigned, 0, offsetof(Socket, poll_limit.burst) +Socket.DeferTrigger, config_parse_socket_defer_trigger, 0, offsetof(Socket, defer_trigger) +Socket.DeferTriggerMaxSec, config_parse_sec_fix_0, 0, offsetof(Socket, defer_trigger_max_usec) {% if ENABLE_SMACK %} Socket.SmackLabel, config_parse_unit_string_printf, 0, offsetof(Socket, smack) Socket.SmackLabelIPIn, config_parse_unit_string_printf, 0, offsetof(Socket, smack_ip_in) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index bed01029dc..a6cb3c7e88 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -159,6 +159,7 @@ DEFINE_CONFIG_PARSE_PTR(config_parse_exec_mount_propagation_flag, mount_propagat DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_numa_policy, mpol, int, -1); DEFINE_CONFIG_PARSE_ENUM(config_parse_status_unit_format, status_unit_format, StatusUnitFormat); DEFINE_CONFIG_PARSE_ENUM_FULL(config_parse_socket_timestamping, socket_timestamping_from_string_harder, SocketTimestamping); +DEFINE_CONFIG_PARSE_ENUM(config_parse_socket_defer_trigger, socket_defer_trigger, SocketDeferTrigger); bool contains_instance_specifier_superset(const char *s) { const char *p, *q; diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index d87bff0996..e2fe7dce1b 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -148,6 +148,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_timeout_abort); CONFIG_PARSER_PROTOTYPE(config_parse_swap_priority); CONFIG_PARSER_PROTOTYPE(config_parse_mount_images); CONFIG_PARSER_PROTOTYPE(config_parse_socket_timestamping); +CONFIG_PARSER_PROTOTYPE(config_parse_socket_defer_trigger); CONFIG_PARSER_PROTOTYPE(config_parse_extension_images); CONFIG_PARSER_PROTOTYPE(config_parse_bpf_foreign_program); CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_socket_bind); diff --git a/src/core/manager.c b/src/core/manager.c index 000f19d6f6..aa43c9d79b 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -1597,6 +1597,32 @@ static unsigned manager_dispatch_stop_when_bound_queue(Manager *m) { return n; } +static unsigned manager_dispatch_stop_notify_queue(Manager *m) { + unsigned n = 0; + + assert(m); + + if (m->may_dispatch_stop_notify_queue < 0) + m->may_dispatch_stop_notify_queue = hashmap_isempty(m->jobs); + + if (!m->may_dispatch_stop_notify_queue) + return 0; + + m->may_dispatch_stop_notify_queue = false; + + LIST_FOREACH(stop_notify_queue, u, m->stop_notify_queue) { + assert(u->in_stop_notify_queue); + + assert(UNIT_VTABLE(u)->stop_notify); + if (UNIT_VTABLE(u)->stop_notify(u)) { + assert(!u->in_stop_notify_queue); + n++; + } + } + + return n; +} + static void manager_clear_jobs_and_units(Manager *m) { Unit *u; @@ -3274,6 +3300,9 @@ int manager_loop(Manager *m) { if (manager_dispatch_release_resources_queue(m) > 0) continue; + if (manager_dispatch_stop_notify_queue(m) > 0) + continue; + if (manager_dispatch_dbus_queue(m) > 0) continue; diff --git a/src/core/manager.h b/src/core/manager.h index 39f822f4e2..c267ebe7ee 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -211,6 +211,9 @@ typedef struct Manager { /* Units that have resources open, and where it might be good to check if they can be released now */ LIST_HEAD(Unit, release_resources_queue); + /* Units that perform certain actions after some other unit deactivates */ + LIST_HEAD(Unit, stop_notify_queue); + sd_event *event; /* This maps PIDs we care about to units that are interested in them. We allow multiple units to be @@ -358,6 +361,7 @@ typedef struct Manager { /* Flags */ bool dispatching_load_queue; + int may_dispatch_stop_notify_queue; /* tristate */ /* Have we already sent out the READY=1 notification? */ bool ready_sent; diff --git a/src/core/socket.c b/src/core/socket.c index 1765e6f9c6..727840e678 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -12,6 +12,7 @@ #include "alloc-util.h" #include "bpf-program.h" +#include "bus-common-errors.h" #include "bus-error.h" #include "copy.h" #include "dbus-socket.h" @@ -69,6 +70,7 @@ static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = { [SOCKET_START_CHOWN] = UNIT_ACTIVATING, [SOCKET_START_POST] = UNIT_ACTIVATING, [SOCKET_LISTENING] = UNIT_ACTIVE, + [SOCKET_DEFERRED] = UNIT_ACTIVE, [SOCKET_RUNNING] = UNIT_ACTIVE, [SOCKET_STOP_PRE] = UNIT_DEACTIVATING, [SOCKET_STOP_PRE_SIGTERM] = UNIT_DEACTIVATING, @@ -144,6 +146,8 @@ static void socket_init(Unit *u) { s->trigger_limit = RATELIMIT_OFF; s->poll_limit = RATELIMIT_OFF; + + s->defer_trigger_max_usec = USEC_INFINITY; } static void socket_unwatch_control_pid(Socket *s) { @@ -427,6 +431,9 @@ static int socket_verify(Socket *s) { if (s->accept && UNIT_ISSET(s->service)) return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Explicit service configuration for accepting socket units not supported. Refusing."); + if (s->accept && s->defer_trigger != SOCKET_DEFER_NO) + return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Socket unit is configured to be accepting with DeferTrigger= enabled. Refusing."); + if (!strv_isempty(s->symlinks) && !socket_find_symlink_target(s)) return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Unit has symlinks set but none or more than one node in the file system. Refusing."); @@ -672,8 +679,12 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { prefix, s->max_connections_per_source); else fprintf(f, - "%sFlushPending: %s\n", - prefix, yes_no(s->flush_pending)); + "%sFlushPending: %s\n" + "%sDeferTrigger: %s\n" + "%sDeferTriggerMaxSec: %s\n", + prefix, yes_no(s->flush_pending), + prefix, socket_defer_trigger_to_string(s->defer_trigger), + prefix, FORMAT_TIMESPAN(s->defer_trigger_max_usec, USEC_PER_SEC)); if (s->priority >= 0) fprintf(f, @@ -1852,8 +1863,10 @@ static void socket_set_state(Socket *s, SocketState state) { old_state = s->state; s->state = state; - if (!SOCKET_STATE_WITH_PROCESS(state)) { + if (!SOCKET_STATE_WITH_PROCESS(state) && state != SOCKET_DEFERRED) s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source); + + if (!SOCKET_STATE_WITH_PROCESS(state)) { socket_unwatch_control_pid(s); s->control_command = NULL; s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; @@ -1867,12 +1880,16 @@ static void socket_set_state(Socket *s, SocketState state) { SOCKET_START_CHOWN, SOCKET_START_POST, SOCKET_LISTENING, + SOCKET_DEFERRED, SOCKET_RUNNING, SOCKET_STOP_PRE, SOCKET_STOP_PRE_SIGTERM, SOCKET_STOP_PRE_SIGKILL)) socket_close_fds(s); + if (state != SOCKET_DEFERRED) + unit_remove_from_stop_notify_queue(UNIT(s)); + if (state != old_state) log_unit_debug(UNIT(s), "Changed %s -> %s", socket_state_to_string(old_state), socket_state_to_string(state)); @@ -1888,6 +1905,11 @@ static int socket_coldplug(Unit *u) { if (s->deserialized_state == s->state) return 0; + /* Patch "deferred" back to "listening" and let socket_enter_running() figure out what to do. + * This saves us the trouble of handling flipping of DeferTrigger= vs Accept= during reload. */ + if (s->deserialized_state == SOCKET_DEFERRED) + s->deserialized_state = SOCKET_LISTENING; + if (pidref_is_set(&s->control_pid) && pidref_is_unwaited(&s->control_pid) > 0 && SOCKET_STATE_WITH_PROCESS(s->deserialized_state)) { @@ -2347,6 +2369,76 @@ static void socket_enter_start_pre(Socket *s) { socket_enter_start_open(s); } +static bool socket_may_defer(Socket *s) { + assert(s); + + switch (s->defer_trigger) { + + case SOCKET_DEFER_NO: + return false; + + case SOCKET_DEFER_YES: + return !hashmap_isempty(UNIT(s)->manager->jobs); + + case SOCKET_DEFER_PATIENT: + assert(s->defer_trigger_max_usec > 0); + return true; + + default: + assert_not_reached(); + } +} + +static bool socket_stop_notify(Unit *u) { + Socket *s = ASSERT_PTR(SOCKET(u)); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(s->state == SOCKET_DEFERRED); + + r = manager_add_job(u->manager, JOB_START, UNIT_DEREF(s->service), JOB_LENIENT, &error, /* ret = */ NULL); + if (r >= 0) { /* Yay! */ + socket_set_state(s, SOCKET_RUNNING); + return true; /* changed */ + } + if (sd_bus_error_has_name(&error, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE)) { + if (s->defer_trigger == SOCKET_DEFER_PATIENT || !hashmap_isempty(u->manager->jobs)) + /* Wait for some more */ + return false; + + log_unit_warning_errno(u, r, "Service conflicts with active units even after all jobs have completed, giving up."); + } else + log_unit_warning_errno(u, r, "Failed to queue service startup job: %s", bus_error_message(&error, r)); + + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + return true; /* changed */ +} + +static void socket_enter_deferred(Socket *s) { + int r; + + assert(s); + assert(socket_may_defer(s)); + + /* So here's the thing: if there're currently units conflicting with the service we shall be + * triggering, and the previous transaction is still running (job pool is not empty), let's + * defer the activation a bit, and recheck upon any unit stop. IOW, the trigger in question + * becomes bound to the conflicting dependency, and not the socket IO because we never process them. + * Put a safety net around all this though, i.e. give up if the service still can't be started + * even after all existing jobs have completed, or DeferTriggerMaxSec= is reached. */ + + r = socket_arm_timer(s, /* relative = */ true, s->defer_trigger_max_usec); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m"); + return socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + } + + unit_add_to_stop_notify_queue(UNIT(s)); + + /* Disable IO event sources */ + socket_set_state(s, SOCKET_DEFERRED); +} + static void socket_enter_running(Socket *s, int cfd_in) { /* Note that this call takes possession of the connection fd passed. It either has to assign it * somewhere or close it. */ @@ -2368,6 +2460,11 @@ static void socket_enter_running(Socket *s, int cfd_in) { return; } + if (s->state == SOCKET_DEFERRED) { + assert(cfd < 0); + return; + } + if (!ratelimit_below(&s->trigger_limit)) { log_unit_warning(UNIT(s), "Trigger limit hit, refusing further activation."); socket_enter_stop_pre(s, SOCKET_FAILURE_TRIGGER_LIMIT_HIT); @@ -2392,9 +2489,17 @@ static void socket_enter_running(Socket *s, int cfd_in) { goto fail; } - r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, &error, /* ret = */ NULL); - if (r == -EDEADLK) - return (void) log_unit_debug_errno(UNIT(s), r, "Failed to queue service startup job, ignoring: %s", bus_error_message(&error, r)); + if (s->defer_trigger != SOCKET_DEFER_NO) { + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_LENIENT, &error, /* ret = */ NULL); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE) && socket_may_defer(s)) + /* We only check BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE here, not + * BUS_ERROR_TRANSACTION_JOBS_CONFLICTING or BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, + * since those are errors in a single transaction, which are most likely + * caused by dependency issues in the unit configuration. + * Deferring activation probabaly won't help. */ + return socket_enter_deferred(s); + } else + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, &error, /* ret = */ NULL); if (r < 0) goto queue_error; } @@ -2598,7 +2703,7 @@ static int socket_stop(Unit *u) { return 0; } - assert(IN_SET(s->state, SOCKET_LISTENING, SOCKET_RUNNING)); + assert(IN_SET(s->state, SOCKET_LISTENING, SOCKET_DEFERRED, SOCKET_RUNNING)); socket_enter_stop_pre(s, SOCKET_SUCCESS); return 1; @@ -3285,6 +3390,11 @@ static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *use socket_enter_stop_pre(s, SOCKET_FAILURE_TIMEOUT); break; + case SOCKET_DEFERRED: + log_unit_warning(UNIT(s), "DeferTriggerMaxSec= elapsed. Stopping."); + socket_enter_stop_pre(s, SOCKET_FAILURE_TIMEOUT); + break; + case SOCKET_STOP_PRE: log_unit_warning(UNIT(s), "Stopping timed out. Terminating."); socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_FAILURE_TIMEOUT); @@ -3414,7 +3524,7 @@ static void socket_trigger_notify(Unit *u, Unit *other) { Service *service = ASSERT_PTR(SERVICE(other)); /* Don't propagate state changes from the service if we are already down */ - if (!IN_SET(s->state, SOCKET_RUNNING, SOCKET_LISTENING)) + if (!IN_SET(s->state, SOCKET_RUNNING, SOCKET_LISTENING, SOCKET_DEFERRED)) return; /* We don't care for the service state if we are in Accept=yes mode */ @@ -3610,6 +3720,14 @@ SocketTimestamping socket_timestamping_from_string_harder(const char *p) { return r ? SOCKET_TIMESTAMPING_NS : SOCKET_TIMESTAMPING_OFF; /* If boolean yes, default to ns accuracy */ } +static const char* const socket_defer_trigger_table[_SOCKET_DEFER_MAX] = { + [SOCKET_DEFER_NO] = "no", + [SOCKET_DEFER_YES] = "yes", + [SOCKET_DEFER_PATIENT] = "patient", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(socket_defer_trigger, SocketDeferTrigger, SOCKET_DEFER_YES); + const UnitVTable socket_vtable = { .object_size = sizeof(Socket), .exec_context_offset = offsetof(Socket, exec_context), @@ -3659,6 +3777,8 @@ const UnitVTable socket_vtable = { .trigger_notify = socket_trigger_notify, + .stop_notify = socket_stop_notify, + .reset_failed = socket_reset_failed, .notify_handoff_timestamp = socket_handoff_timestamp, diff --git a/src/core/socket.h b/src/core/socket.h index 70291fb0d2..cec10dece9 100644 --- a/src/core/socket.h +++ b/src/core/socket.h @@ -66,6 +66,14 @@ typedef enum SocketTimestamping { _SOCKET_TIMESTAMPING_INVALID = -EINVAL, } SocketTimestamping; +typedef enum SocketDeferTrigger { + SOCKET_DEFER_NO, + SOCKET_DEFER_YES, + SOCKET_DEFER_PATIENT, + _SOCKET_DEFER_MAX, + _SOCKET_DEFER_INVALID = -EINVAL, +} SocketDeferTrigger; + typedef struct Socket { Unit meta; @@ -165,6 +173,9 @@ typedef struct Socket { RateLimit trigger_limit; RateLimit poll_limit; + + usec_t defer_trigger_max_usec; + SocketDeferTrigger defer_trigger; } Socket; SocketPeer *socket_peer_ref(SocketPeer *p); @@ -205,4 +216,7 @@ const char* socket_timestamping_to_string(SocketTimestamping p) _const_; SocketTimestamping socket_timestamping_from_string(const char *p) _pure_; SocketTimestamping socket_timestamping_from_string_harder(const char *p) _pure_; +const char* socket_defer_trigger_to_string(SocketDeferTrigger i) _const_; +SocketDeferTrigger socket_defer_trigger_from_string(const char *s) _pure_; + DEFINE_CAST(SOCKET, Socket); diff --git a/src/core/unit.c b/src/core/unit.c index 2906675202..9b95834263 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -617,6 +617,28 @@ void unit_submit_to_release_resources_queue(Unit *u) { u->in_release_resources_queue = true; } +void unit_add_to_stop_notify_queue(Unit *u) { + assert(u); + + if (u->in_stop_notify_queue) + return; + + assert(UNIT_VTABLE(u)->stop_notify); + + LIST_PREPEND(stop_notify_queue, u->manager->stop_notify_queue, u); + u->in_stop_notify_queue = true; +} + +void unit_remove_from_stop_notify_queue(Unit *u) { + assert(u); + + if (!u->in_stop_notify_queue) + return; + + LIST_REMOVE(stop_notify_queue, u->manager->stop_notify_queue, u); + u->in_stop_notify_queue = false; +} + static void unit_clear_dependencies(Unit *u) { assert(u); @@ -842,6 +864,8 @@ Unit* unit_free(Unit *u) { if (u->in_release_resources_queue) LIST_REMOVE(release_resources_queue, u->manager->release_resources_queue, u); + unit_remove_from_stop_notify_queue(u); + condition_free_list(u->conditions); condition_free_list(u->asserts); @@ -2804,6 +2828,9 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su /* Maybe the concurrency limits now allow dispatching of another start job in this slice? */ unit_check_concurrency_limit(u); + /* Maybe someone else has been waiting for us to stop? */ + m->may_dispatch_stop_notify_queue = true; + } else if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { /* Start uphold units regardless if going up was expected or not */ check_uphold_dependencies(u); diff --git a/src/core/unit.h b/src/core/unit.h index d508865748..4c10514726 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -346,6 +346,9 @@ typedef struct Unit { /* Queue of units that should be checked if they can release resources now */ LIST_FIELDS(Unit, release_resources_queue); + /* Queue of units that should be informed when other units stop */ + LIST_FIELDS(Unit, stop_notify_queue); + /* PIDs we keep an eye on. Note that a unit might have many more, but these are the ones we care * enough about to process SIGCHLD for */ Set *pids; /* → PidRef* */ @@ -446,6 +449,7 @@ typedef struct Unit { bool in_start_when_upheld_queue:1; bool in_stop_when_bound_queue:1; bool in_release_resources_queue:1; + bool in_stop_notify_queue:1; bool sent_dbus_new_signal:1; @@ -670,6 +674,9 @@ typedef struct UnitVTable { * state or gains/loses a job */ void (*trigger_notify)(Unit *u, Unit *trigger); + /* Invoked when some other units stop */ + bool (*stop_notify)(Unit *u); + /* Called whenever CLOCK_REALTIME made a jump */ void (*time_change)(Unit *u); @@ -847,6 +854,8 @@ void unit_submit_to_stop_when_unneeded_queue(Unit *u); void unit_submit_to_start_when_upheld_queue(Unit *u); void unit_submit_to_stop_when_bound_queue(Unit *u); void unit_submit_to_release_resources_queue(Unit *u); +void unit_add_to_stop_notify_queue(Unit *u); +void unit_remove_from_stop_notify_queue(Unit *u); int unit_merge(Unit *u, Unit *other); int unit_merge_by_name(Unit *u, const char *other); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index e51646a294..cc0449dd96 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2623,7 +2623,8 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons "KeepAliveIntervalSec", "DeferAcceptSec", "TriggerLimitIntervalSec", - "PollLimitIntervalSec")) + "PollLimitIntervalSec", + "DeferTriggerMaxSec")) return bus_append_parse_sec_rename(m, field, eq); if (STR_IN_SET(field, "ReceiveBuffer", @@ -2646,7 +2647,8 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons "FileDescriptorName", "SocketUser", "SocketGroup", - "Timestamping")) + "Timestamping", + "DeferTrigger")) return bus_append_string(m, field, eq); if (streq(field, "Symlinks"))