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"))