mirror of
https://github.com/morgan9e/systemd
synced 2026-04-15 00:47:10 +09:00
core/socket: introduce DeferTrigger= and DeferTriggerMaxSec=
Alternative to b50f6dbe57
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.
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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user