core/socket: introduce DeferTrigger= (#37505)
Follow-up for #37222 and #37421
This commit is contained in:
@@ -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 {
|
||||
|
||||
<!--property TriggerLimitBurst is not documented!-->
|
||||
|
||||
<!--property DeferTrigger is not documented!-->
|
||||
|
||||
<!--property DeferTriggerMaxUSec is not documented!-->
|
||||
|
||||
<!--property UID is not documented!-->
|
||||
|
||||
<!--property GID is not documented!-->
|
||||
@@ -6244,6 +6252,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="PollLimitBurst"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="DeferTrigger"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="DeferTriggerMaxUSec"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="UID"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="GID"/>
|
||||
@@ -12112,8 +12124,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<para><varname>ProtectHostnameEx</varname>,
|
||||
<varname>PassPIDFD</varname>,
|
||||
<varname>AcceptFileDescriptors</varname>,
|
||||
<varname>DelegateNamespaces</varname>, and
|
||||
<function>RemoveSubgroup()</function> were added in version 258.</para>
|
||||
<varname>DelegateNamespaces</varname>,
|
||||
<function>RemoveSubgroup()</function>,
|
||||
<varname>DeferTrigger</varname>, and
|
||||
<varname>DeferTriggerMaxUSec</varname> were added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Mount Unit Objects</title>
|
||||
|
||||
+7
-4
@@ -2197,6 +2197,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
|
||||
<listitem>
|
||||
<para>When queuing a new job, this option controls how to deal with
|
||||
already queued jobs. It takes one of <literal>fail</literal>,
|
||||
<literal>lenient</literal>,
|
||||
<literal>replace</literal>,
|
||||
<literal>replace-irreversibly</literal>,
|
||||
<literal>isolate</literal>,
|
||||
@@ -2209,10 +2210,12 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
|
||||
<command>isolate</command> command is used which implies the
|
||||
<literal>isolate</literal> job mode.</para>
|
||||
|
||||
<para>If <literal>fail</literal> is specified and a requested
|
||||
operation conflicts with a pending job (more specifically:
|
||||
causes an already pending start job to be reversed into a stop
|
||||
job or vice versa), cause the operation to fail.</para>
|
||||
<para>If <literal>fail</literal> is specified and a requested operation on weak dependencies
|
||||
conflicts with a pending job (more specifically: causes an already pending start job to be reversed
|
||||
into a stop job or vice versa), cause the operation to fail.</para>
|
||||
|
||||
<para>If <literal>lenient</literal> is specified and a requested operation conflicts with any
|
||||
active/activating unit, cause the operation to fail.</para>
|
||||
|
||||
<para>If <literal>replace</literal> (the default) is
|
||||
specified, any conflicting pending job will be replaced, as
|
||||
|
||||
@@ -956,6 +956,37 @@
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>DeferTrigger=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument, or <literal>patient</literal>. May only be used when <varname>Accept=no</varname>.
|
||||
If enabled, job mode <literal>lenient</literal> instead of <literal>replace</literal> 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 <varname>DeferTriggerMaxSec=</varname>. If set to <option>yes</option>,
|
||||
the socket unit will fail if all jobs have finished or the timeout has been reached but the conflict remains.
|
||||
If <option>patient</option>, always wait until <varname>DeferTriggerMaxSec=</varname> elapses.
|
||||
Defaults to no.</para>
|
||||
|
||||
<para>This setting is particularly useful if the socket unit should stay active across switch-root/soft-reboot
|
||||
operations while the triggered service is stopped.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>DeferTriggerMaxSec=</varname></term>
|
||||
|
||||
<listitem><para>Configures the maximum time to defer the triggering when <varname>DeferTrigger=</varname>
|
||||
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 <literal>0</literal> or <literal>infinity</literal> to disable the timeout logic (the default).
|
||||
</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>PassFileDescriptorsToExec=</varname></term>
|
||||
|
||||
|
||||
@@ -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",
|
||||
@@ -341,6 +342,7 @@ DEFINE_STRING_TABLE_LOOKUP(notify_access, NotifyAccess);
|
||||
|
||||
static const char* const job_mode_table[_JOB_MODE_MAX] = {
|
||||
[JOB_FAIL] = "fail",
|
||||
[JOB_LENIENT] = "lenient",
|
||||
[JOB_REPLACE] = "replace",
|
||||
[JOB_REPLACE_IRREVERSIBLY] = "replace-irreversibly",
|
||||
[JOB_ISOLATE] = "isolate",
|
||||
|
||||
@@ -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,
|
||||
@@ -282,6 +283,7 @@ typedef enum NotifyAccess {
|
||||
|
||||
typedef enum JobMode {
|
||||
JOB_FAIL, /* Fail if a conflicting job is already queued */
|
||||
JOB_LENIENT, /* Fail if any conflicting unit is active (even weaker than JOB_FAIL) */
|
||||
JOB_REPLACE, /* Replace an existing conflicting job */
|
||||
JOB_REPLACE_IRREVERSIBLY, /* Like JOB_REPLACE + produce irreversible jobs */
|
||||
JOB_ISOLATE, /* Start a unit, and stop all others */
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+131
-12
@@ -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;
|
||||
}
|
||||
@@ -2412,12 +2517,11 @@ static void socket_enter_running(Socket *s, int cfd_in) {
|
||||
|
||||
if (s->max_connections_per_source > 0) {
|
||||
r = socket_acquire_peer(s, cfd, &p);
|
||||
if (r < 0) {
|
||||
if (ERRNO_IS_DISCONNECT(r))
|
||||
return;
|
||||
if (ERRNO_IS_NEG_DISCONNECT(r))
|
||||
return;
|
||||
if (r < 0)
|
||||
/* We didn't have enough resources to acquire peer information, let's fail. */
|
||||
goto fail;
|
||||
}
|
||||
if (r > 0 && p->n_ref > s->max_connections_per_source) {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
|
||||
@@ -2599,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;
|
||||
@@ -3286,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);
|
||||
@@ -3415,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 */
|
||||
@@ -3611,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),
|
||||
@@ -3660,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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
+21
-8
@@ -565,7 +565,7 @@ static int transaction_is_destructive(Transaction *tr, JobMode mode, sd_bus_erro
|
||||
assert(!j->transaction_prev);
|
||||
assert(!j->transaction_next);
|
||||
|
||||
if (j->unit->job && (mode == JOB_FAIL || j->unit->job->irreversible) &&
|
||||
if (j->unit->job && (IN_SET(mode, JOB_FAIL, JOB_LENIENT) || j->unit->job->irreversible) &&
|
||||
job_type_is_conflicting(j->unit->job->type, j->type))
|
||||
return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE,
|
||||
"Transaction for %s/%s is destructive (%s has '%s' job queued, but '%s' is included in transaction).",
|
||||
@@ -576,7 +576,7 @@ static int transaction_is_destructive(Transaction *tr, JobMode mode, sd_bus_erro
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void transaction_minimize_impact(Transaction *tr) {
|
||||
static int transaction_minimize_impact(Transaction *tr, JobMode mode, sd_bus_error *e) {
|
||||
Job *head;
|
||||
|
||||
assert(tr);
|
||||
@@ -584,13 +584,16 @@ static void transaction_minimize_impact(Transaction *tr) {
|
||||
/* Drops all unnecessary jobs that reverse already active jobs
|
||||
* or that stop a running service. */
|
||||
|
||||
if (!IN_SET(mode, JOB_FAIL, JOB_LENIENT))
|
||||
return 0;
|
||||
|
||||
rescan:
|
||||
HASHMAP_FOREACH(head, tr->jobs) {
|
||||
LIST_FOREACH(transaction, j, head) {
|
||||
bool stops_running_service, changes_existing_job;
|
||||
|
||||
/* If it matters, we shouldn't drop it */
|
||||
if (j->matters_to_anchor)
|
||||
if (j->matters_to_anchor && mode != JOB_LENIENT)
|
||||
continue;
|
||||
|
||||
/* Would this stop a running service?
|
||||
@@ -607,6 +610,13 @@ rescan:
|
||||
if (!stops_running_service && !changes_existing_job)
|
||||
continue;
|
||||
|
||||
if (j->matters_to_anchor) {
|
||||
assert(mode == JOB_LENIENT);
|
||||
return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE,
|
||||
"%s/%s would stop a running unit or change existing job, bailing",
|
||||
j->unit->id, job_type_to_string(j->type));
|
||||
}
|
||||
|
||||
if (stops_running_service)
|
||||
log_unit_debug(j->unit,
|
||||
"%s/%s would stop a running service.",
|
||||
@@ -626,6 +636,8 @@ rescan:
|
||||
goto rescan;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int transaction_apply(
|
||||
@@ -734,11 +746,12 @@ int transaction_activate(
|
||||
/* First step: figure out which jobs matter */
|
||||
transaction_find_jobs_that_matter_to_anchor(tr->anchor_job, generation++);
|
||||
|
||||
/* Second step: Try not to stop any running services if
|
||||
* we don't have to. Don't try to reverse running
|
||||
* jobs if we don't have to. */
|
||||
if (mode == JOB_FAIL)
|
||||
transaction_minimize_impact(tr);
|
||||
/* Second step: Try not to stop any running services if we don't have to. Don't try to reverse
|
||||
* running jobs if we don't have to. */
|
||||
r = transaction_minimize_impact(tr, mode, e);
|
||||
if (r < 0)
|
||||
return r; /* Note that we don't log here, because for JOB_LENIENT conflicts are very much expected
|
||||
and shouldn't appear to be fatal for the unit. Only inform the caller via bus error. */
|
||||
|
||||
/* Third step: Drop redundant jobs */
|
||||
transaction_drop_redundant(tr);
|
||||
|
||||
+28
-1
@@ -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);
|
||||
@@ -6332,7 +6359,7 @@ void unit_next_freezer_state(Unit *u, FreezerAction action, FreezerState *ret_ne
|
||||
* if its parent is frozen. So we instead "demote" a normal freeze into a freeze
|
||||
* initiated by parent if the parent is frozen */
|
||||
if (IN_SET(current, FREEZER_RUNNING, FREEZER_THAWING,
|
||||
FREEZER_FREEZING_BY_PARENT, FREEZER_FROZEN_BY_PARENT)) /* Should usually be refused by unit_freezer_action */
|
||||
FREEZER_FREEZING_BY_PARENT, FREEZER_FROZEN_BY_PARENT)) /* Should usually be refused by unit_freezer_action */
|
||||
next = current;
|
||||
else if (current == FREEZER_FREEZING) {
|
||||
if (IN_SET(parent, FREEZER_RUNNING, FREEZER_THAWING))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"))
|
||||
|
||||
Executable
+151
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# shellcheck disable=SC2016
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/util.sh
|
||||
. "$(dirname "$0")"/util.sh
|
||||
|
||||
UNIT_NAME="TEST-07-PID1-socket-defer-$RANDOM"
|
||||
|
||||
at_exit() {
|
||||
systemctl stop "$UNIT_NAME*"
|
||||
rm -f /run/systemd/system/"$UNIT_NAME".socket \
|
||||
/run/systemd/system/"$UNIT_NAME"{,-conflict1,-conflict2,-forever}.service
|
||||
}
|
||||
|
||||
trap at_exit EXIT
|
||||
|
||||
mkdir -p "/tmp/$UNIT_NAME"
|
||||
|
||||
cat >/run/systemd/system/"$UNIT_NAME-conflict1.service" <<EOF
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=bash -c 'until [[ -f /tmp/$UNIT_NAME/flag ]]; do sleep 1; done'
|
||||
TimeoutStartSec=infinity
|
||||
EOF
|
||||
|
||||
cat >/run/systemd/system/"$UNIT_NAME-conflict2.service" <<EOF
|
||||
[Service]
|
||||
ExecStart=bash -c 'while [[ -f /tmp/$UNIT_NAME/flag ]]; do sleep 1; done'
|
||||
EOF
|
||||
|
||||
cat >/run/systemd/system/"$UNIT_NAME-forever.service" <<EOF
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=sleep infinity
|
||||
TimeoutSec=infinity
|
||||
EOF
|
||||
|
||||
cat >/run/systemd/system/"$UNIT_NAME.socket" <<EOF
|
||||
[Socket]
|
||||
ListenStream=/tmp/$UNIT_NAME/test
|
||||
FlushPending=yes
|
||||
EOF
|
||||
|
||||
cat >/run/systemd/system/"$UNIT_NAME.service" <<EOF
|
||||
[Unit]
|
||||
Conflicts=$UNIT_NAME-conflict1.service
|
||||
Conflicts=$UNIT_NAME-conflict2.service
|
||||
|
||||
[Service]
|
||||
ExecStart=sleep infinity
|
||||
EOF
|
||||
|
||||
wait_for_start() {
|
||||
timeout 30 bash -c "until systemctl -q is-active '$UNIT_NAME.service'; do sleep .5; done"
|
||||
assert_eq "$(systemctl show "$UNIT_NAME.socket" -P SubState)" "running"
|
||||
}
|
||||
|
||||
wait_for_stop() {
|
||||
timeout 30 bash -c "while systemctl -q is-active '$UNIT_NAME.service'; do sleep .5; done"
|
||||
assert_eq "$(systemctl show "$UNIT_NAME.socket" -P SubState)" "listening"
|
||||
}
|
||||
|
||||
# DeferTrigger=no: job mode replace
|
||||
|
||||
systemctl start "$UNIT_NAME.socket"
|
||||
[[ -S "/tmp/$UNIT_NAME/test" ]]
|
||||
assert_eq "$(systemctl show "$UNIT_NAME.socket" -P SubState)" "listening"
|
||||
(! systemctl -q is-active "$UNIT_NAME.service")
|
||||
|
||||
socat -u - UNIX-CONNECT:"/tmp/$UNIT_NAME/test" &
|
||||
wait_for_start
|
||||
kill %%
|
||||
|
||||
touch "/tmp/$UNIT_NAME/flag"
|
||||
systemctl start "$UNIT_NAME-conflict2.service"
|
||||
wait_for_stop
|
||||
|
||||
socat -u - UNIX-CONNECT:"/tmp/$UNIT_NAME/test" &
|
||||
wait_for_start
|
||||
kill %%
|
||||
(! systemctl -q is-active "$UNIT_NAME-conflict2.service")
|
||||
|
||||
# DeferTrigger=yes
|
||||
|
||||
echo "DeferTrigger=yes" >>/run/systemd/system/"$UNIT_NAME.socket"
|
||||
systemctl daemon-reload
|
||||
|
||||
assert_eq "$(systemctl show "$UNIT_NAME.socket" -P SubState)" "running"
|
||||
rm "/tmp/$UNIT_NAME/flag"
|
||||
systemctl start --no-block "$UNIT_NAME-conflict1.service"
|
||||
wait_for_stop
|
||||
assert_eq "$(systemctl show "$UNIT_NAME-conflict1.service" -P SubState)" "start"
|
||||
|
||||
# Wait in "deferred" state
|
||||
|
||||
socat -u - UNIX-CONNECT:"/tmp/$UNIT_NAME/test" &
|
||||
timeout 30 bash -c "until [[ \$(systemctl show '$UNIT_NAME.socket' -P SubState) == 'deferred' ]]; do sleep .5; done"
|
||||
(! systemctl -q is-active "$UNIT_NAME.service")
|
||||
kill %%
|
||||
assert_eq "$(systemctl show "$UNIT_NAME-conflict1.service" -P SubState)" "start"
|
||||
|
||||
systemctl daemon-reload
|
||||
timeout 10 bash -c "until [[ \$(systemctl show '$UNIT_NAME.socket' -P SubState) == 'deferred' ]]; do sleep .2; done"
|
||||
(! systemctl -q is-active "$UNIT_NAME.service")
|
||||
assert_eq "$(systemctl show "$UNIT_NAME-conflict1.service" -P SubState)" "start"
|
||||
|
||||
# Activate after conflicting unit exits
|
||||
|
||||
touch "/tmp/$UNIT_NAME/flag"
|
||||
timeout 30 bash -c "while systemctl -q is-active '$UNIT_NAME-conflict1.service'; do sleep .2; done"
|
||||
wait_for_start
|
||||
|
||||
# An irrelevant job running, during which one of the conflicting units exits
|
||||
|
||||
systemctl start --no-block "$UNIT_NAME-forever.service"
|
||||
systemctl start "$UNIT_NAME-conflict2.service"
|
||||
wait_for_stop
|
||||
|
||||
socat -u - UNIX-CONNECT:"/tmp/$UNIT_NAME/test" &
|
||||
timeout 30 bash -c "until [[ \$(systemctl show '$UNIT_NAME.socket' -P SubState) == 'deferred' ]]; do sleep .5; done"
|
||||
(! systemctl -q is-active "$UNIT_NAME.service")
|
||||
kill %%
|
||||
|
||||
rm "/tmp/$UNIT_NAME/flag"
|
||||
timeout 30 bash -c "while systemctl -q is-active '$UNIT_NAME-conflict2.service'; do sleep .2; done"
|
||||
wait_for_start
|
||||
assert_eq "$(systemctl show "$UNIT_NAME-forever.service" -P SubState)" "start"
|
||||
|
||||
# Fail if DeferTriggerMaxSec= is reached
|
||||
|
||||
systemctl start --no-block "$UNIT_NAME-conflict1.service"
|
||||
wait_for_stop
|
||||
assert_eq "$(systemctl show "$UNIT_NAME-conflict1.service" -P SubState)" "start"
|
||||
|
||||
socat -u - UNIX-CONNECT:"/tmp/$UNIT_NAME/test" &
|
||||
timeout 30 bash -c "until [[ \$(systemctl show '$UNIT_NAME.socket' -P SubState) == 'deferred' ]]; do sleep .5; done"
|
||||
(! systemctl -q is-active "$UNIT_NAME.service")
|
||||
kill %%
|
||||
|
||||
echo "DeferTriggerMaxSec=20s" >>/run/systemd/system/"$UNIT_NAME.socket"
|
||||
systemctl daemon-reload
|
||||
|
||||
timeout 10 bash -c "until [[ \$(systemctl show '$UNIT_NAME.socket' -P SubState) == 'deferred' ]]; do sleep .2; done"
|
||||
(! systemctl -q is-active "$UNIT_NAME.service")
|
||||
|
||||
sleep 10
|
||||
timeout 30 bash -c "until systemctl -q is-failed '$UNIT_NAME.socket'; do sleep .5; done"
|
||||
assert_eq "$(systemctl show "$UNIT_NAME-conflict1.service" -P SubState)" "start"
|
||||
@@ -18,4 +18,3 @@ After=initrd-root-fs.target initrd-fs.target initrd.target
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=systemctl --no-block isolate initrd-switch-root.target
|
||||
ExecStart=systemctl --no-block --job-mode=replace-irreversibly start initrd-switch-root.service
|
||||
|
||||
@@ -20,6 +20,7 @@ Service=systemd-journald.service
|
||||
ReceiveBuffer=128M
|
||||
ListenNetlink=audit 1
|
||||
PassCredentials=yes
|
||||
DeferTrigger=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
|
||||
@@ -26,6 +26,7 @@ Service=systemd-journald.service
|
||||
SocketMode=0666
|
||||
Symlinks=/dev/log
|
||||
Timestamping=us
|
||||
DeferTrigger=yes
|
||||
|
||||
# Increase both the send and receive buffer, so that things don't
|
||||
# block early. Note that journald internally uses the this socket both
|
||||
|
||||
@@ -27,3 +27,4 @@ ReceiveBuffer=8M
|
||||
Service=systemd-journald.service
|
||||
SocketMode=0666
|
||||
Timestamping=us
|
||||
DeferTrigger=yes
|
||||
|
||||
@@ -20,3 +20,4 @@ ListenSequentialPacket=/run/udev/control
|
||||
SocketMode=0600
|
||||
PassCredentials=yes
|
||||
RemoveOnStop=yes
|
||||
DeferTrigger=yes
|
||||
|
||||
@@ -24,3 +24,4 @@ Service=systemd-udevd.service
|
||||
ReceiveBuffer=128M
|
||||
ListenNetlink=kobject-uevent 1
|
||||
PassCredentials=yes
|
||||
DeferTrigger=yes
|
||||
|
||||
@@ -20,3 +20,4 @@ ListenStream=/run/udev/io.systemd.Udev
|
||||
FileDescriptorName=varlink
|
||||
SocketMode=0600
|
||||
RemoveOnStop=yes
|
||||
DeferTrigger=yes
|
||||
|
||||
Reference in New Issue
Block a user