diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index cb56d664c4..f1f433bbac 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -2856,6 +2856,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
readonly a(sasasttttuii) ExecReloadEx = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
+ readonly a(sasbttttuii) ExecReloadPost = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
+ readonly a(sasasttttuii) ExecReloadPostEx = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
readonly a(sasbttttuii) ExecStop = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
readonly a(sasasttttuii) ExecStopEx = [...];
@@ -3537,9 +3541,9 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
-
+
-
+
@@ -4211,6 +4215,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+
+
+
@@ -4841,10 +4849,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
last watchdog ping received from the service, or 0 if none was ever received.
ExecStartPre, ExecStart, ExecStartPost,
- ExecReload, ExecStop, and ExecStop are arrays
- of structures where each struct contains: the binary path to execute; an array with all arguments to
- pass to the executed command, starting with argument 0; a boolean whether it should be considered a
- failure if the process exits uncleanly; two pairs of
+ ExecReload, ExecReloadPost, ExecStop, and
+ ExecStopPost are arrays of structures where each struct contains: the binary path
+ to execute; an array with all arguments to pass to the executed command, starting with argument 0;
+ a boolean whether it should be considered a failure if the process exits uncleanly; two pairs of
CLOCK_REALTIME/CLOCK_MONOTONIC microsecond timestamps when
the process began and finished running the last time, or 0 if it never ran or never finished running;
the PID of the process, or 0 if it has not run yet; the exit code and status of the last run. This
@@ -5814,8 +5822,6 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
-
-
@@ -12544,8 +12550,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
LogsDirectoryAccounting, and
KillSubgroup() were added in version 258.
UserNamespacePath,
- OOMKills, and
- ManagedOOMKills were added in 259.
+ OOMKills,
+ ManagedOOMKills,
+ ExecReloadPost, and
+ ExecReloadPostEx were added in version 259.
Socket Unit Objects
diff --git a/man/systemd.service.xml b/man/systemd.service.xml
index 8c00a093f8..2ddeeafcec 100644
--- a/man/systemd.service.xml
+++ b/man/systemd.service.xml
@@ -475,8 +475,8 @@
ExecReload=
- Commands to execute to trigger a configuration reload in the service. This argument
- takes multiple command lines, following the same scheme as described for
+ Commands to execute to trigger a configuration reload in the service. This setting
+ may take multiple command lines, following the same scheme as described for
ExecStart= above. Use of this setting is optional. Specifier and environment
variable substitution is supported here following the same scheme as for
ExecStart=.
@@ -486,13 +486,12 @@
ExecReload=kill -HUP $MAINPID
- Note however that reloading a daemon by enqueuing a signal (as with the example line above) is
- usually not a good choice, because this is an asynchronous operation and hence not suitable when
- ordering reloads of multiple services against each other. It is thus strongly recommended to either
- use Type= in place of
- ExecReload=, or to set ExecReload= to a command that not only
- triggers a configuration reload of the daemon, but also synchronously waits for it to complete. For
- example, Note however that reloading a daemon by enqueuing a signal without completion notification
+ (as is the case with the example line above) is usually not a good choice, because this is an
+ asynchronous operation and hence not suitable when ordering reloads of multiple services against
+ each other. It is thus strongly recommended to either use Type=notify-reload,
+ or to set ExecReload= to a command that not only triggers a configuration reload
+ of the daemon, but also synchronously waits for it to complete. For example, dbus-broker1
uses the following:
@@ -500,6 +499,22 @@
/org/freedesktop/DBus org.freedesktop.DBus \
ReloadConfig
+
+ This setting can be combined with Type=notify-reload, in which case
+ the service main process is signaled after all specified command lines finish execution. Specially,
+ if RELOADING=1 notification is received before ExecReload=
+ completes, the signaling is skipped and the service manager immediately starts listening for
+ READY=1.
+
+
+
+
+ ExecReloadPost=
+
+ Commands to execute after a successful reload operation. Syntax for this setting
+ is exactly the same as ExecReload=.
+
+
@@ -1072,18 +1087,14 @@
RootDirectoryStartOnly=
- Takes a boolean argument. If true, the root
- directory, as configured with the
+ Takes a boolean argument. If true, the root directory, as configured with the
RootDirectory= option (see
systemd.exec5
- for more information), is only applied to the process started
- with ExecStart=, and not to the various
- other ExecStartPre=,
- ExecStartPost=,
- ExecReload=, ExecStop=,
- and ExecStopPost= commands. If false, the
- setting is applied to all configured commands the same way.
- Defaults to false.
+ for more information), is only applied to the process started with ExecStart=,
+ and not to the various other ExecStartPre=, ExecStartPost=,
+ ExecReload=, ExecReloadPost=, ExecStop=,
+ and ExecStopPost= commands. If false, the setting is applied to all
+ configured commands the same way. Defaults to false.
diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c
index a2b2f9d6f0..9179897f3c 100644
--- a/src/basic/unit-def.c
+++ b/src/basic/unit-def.c
@@ -224,10 +224,11 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = {
[SERVICE_START_POST] = "start-post",
[SERVICE_RUNNING] = "running",
[SERVICE_EXITED] = "exited",
+ [SERVICE_REFRESH_EXTENSIONS] = "refresh-extensions",
[SERVICE_RELOAD] = "reload",
[SERVICE_RELOAD_SIGNAL] = "reload-signal",
[SERVICE_RELOAD_NOTIFY] = "reload-notify",
- [SERVICE_REFRESH_EXTENSIONS] = "refresh-extensions",
+ [SERVICE_RELOAD_POST] = "reload-post",
[SERVICE_STOP] = "stop",
[SERVICE_STOP_WATCHDOG] = "stop-watchdog",
[SERVICE_STOP_SIGTERM] = "stop-sigterm",
diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h
index f42198e08e..37539ef018 100644
--- a/src/basic/unit-def.h
+++ b/src/basic/unit-def.h
@@ -131,10 +131,11 @@ typedef enum ServiceState {
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */
+ SERVICE_REFRESH_EXTENSIONS, /* Refreshing extensions for a reload request */
SERVICE_RELOAD, /* Reloading via ExecReload= */
SERVICE_RELOAD_SIGNAL, /* Reloading via SIGHUP requested */
SERVICE_RELOAD_NOTIFY, /* Waiting for READY=1 after RELOADING=1 notify */
- SERVICE_REFRESH_EXTENSIONS, /* Refreshing extensions for a reload request */
+ SERVICE_RELOAD_POST,
SERVICE_MOUNTING, /* Performing a live mount into the namespace of the service */
SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */
SERVICE_STOP_WATCHDOG,
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c
index 2ff6272bd4..aa6a587c16 100644
--- a/src/core/dbus-service.c
+++ b/src/core/dbus-service.c
@@ -383,6 +383,8 @@ const sd_bus_vtable bus_service_vtable[] = {
BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecStartPostEx", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecReload", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecReloadEx", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
+ BUS_EXEC_COMMAND_LIST_VTABLE("ExecReloadPost", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
+ BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecReloadPostEx", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecStop", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecStopEx", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
diff --git a/src/core/job.c b/src/core/job.c
index 42bca3a253..5e53512c9d 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -436,13 +436,11 @@ bool job_type_is_redundant(JobType a, UnitActiveState b) {
switch (a) {
case JOB_START:
- return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING, UNIT_REFRESHING);
+ case JOB_VERIFY_ACTIVE:
+ return UNIT_IS_ACTIVE_OR_RELOADING(b);
case JOB_STOP:
- return IN_SET(b, UNIT_INACTIVE, UNIT_FAILED);
-
- case JOB_VERIFY_ACTIVE:
- return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING, UNIT_REFRESHING);
+ return UNIT_IS_INACTIVE_OR_FAILED(b);
case JOB_RELOAD:
/* Reload jobs are never considered redundant/duplicate. Refer to jobs_may_late_merge() for
diff --git a/src/core/job.h b/src/core/job.h
index d8be0b652e..4c2f78815b 100644
--- a/src/core/job.h
+++ b/src/core/job.h
@@ -162,17 +162,17 @@ int job_coldplug(Job *j);
JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts);
void job_dependency_free(JobDependency *l);
-JobType job_type_lookup_merge(JobType a, JobType b) _pure_;
+JobType job_type_lookup_merge(JobType a, JobType b) _const_;
-_pure_ static inline bool job_type_is_mergeable(JobType a, JobType b) {
+static inline bool job_type_is_mergeable(JobType a, JobType b) {
return job_type_lookup_merge(a, b) >= 0;
}
-_pure_ static inline bool job_type_is_conflicting(JobType a, JobType b) {
+static inline bool job_type_is_conflicting(JobType a, JobType b) {
return a != JOB_NOP && b != JOB_NOP && !job_type_is_mergeable(a, b);
}
-_pure_ static inline bool job_type_is_superset(JobType a, JobType b) {
+static inline bool job_type_is_superset(JobType a, JobType b) {
/* Checks whether operation a is a "superset" of b in its actions */
if (b == JOB_NOP)
return true;
@@ -181,7 +181,7 @@ _pure_ static inline bool job_type_is_superset(JobType a, JobType b) {
return a == job_type_lookup_merge(a, b);
}
-bool job_type_is_redundant(JobType a, UnitActiveState b) _pure_;
+bool job_type_is_redundant(JobType a, UnitActiveState b) _const_;
/* Collapses a state-dependent job type into a simpler type by observing
* the state of the unit which it is going to be applied to. */
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 5b83c95e4c..95ef508105 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -430,8 +430,9 @@ Service.PIDFile, config_parse_pid_file,
Service.ExecCondition, config_parse_exec, SERVICE_EXEC_CONDITION, offsetof(Service, exec_command)
Service.ExecStartPre, config_parse_exec, SERVICE_EXEC_START_PRE, offsetof(Service, exec_command)
Service.ExecStart, config_parse_exec, SERVICE_EXEC_START, offsetof(Service, exec_command)
-Service.ExecReload, config_parse_exec, SERVICE_EXEC_RELOAD, offsetof(Service, exec_command)
Service.ExecStartPost, config_parse_exec, SERVICE_EXEC_START_POST, offsetof(Service, exec_command)
+Service.ExecReload, config_parse_exec, SERVICE_EXEC_RELOAD, offsetof(Service, exec_command)
+Service.ExecReloadPost, config_parse_exec, SERVICE_EXEC_RELOAD_POST, offsetof(Service, exec_command)
Service.ExecStop, config_parse_exec, SERVICE_EXEC_STOP, offsetof(Service, exec_command)
Service.ExecStopPost, config_parse_exec, SERVICE_EXEC_STOP_POST, offsetof(Service, exec_command)
Service.RestartSec, config_parse_sec, 0, offsetof(Service, restart_usec)
diff --git a/src/core/service.c b/src/core/service.c
index f45d0c4801..5c1e6189f5 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -56,6 +56,8 @@
#include "unit-name.h"
#include "utf8.h"
+#define STATUS_TEXT_MAX (16U*1024U)
+
#define service_spawn(...) service_spawn_internal(__func__, __VA_ARGS__)
static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
@@ -66,10 +68,11 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
[SERVICE_START_POST] = UNIT_ACTIVATING,
[SERVICE_RUNNING] = UNIT_ACTIVE,
[SERVICE_EXITED] = UNIT_ACTIVE,
+ [SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_RELOAD] = UNIT_RELOADING,
[SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING,
[SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING,
- [SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
+ [SERVICE_RELOAD_POST] = UNIT_RELOADING,
[SERVICE_MOUNTING] = UNIT_REFRESHING,
[SERVICE_STOP] = UNIT_DEACTIVATING,
[SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING,
@@ -98,10 +101,11 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
[SERVICE_START_POST] = UNIT_ACTIVE,
[SERVICE_RUNNING] = UNIT_ACTIVE,
[SERVICE_EXITED] = UNIT_ACTIVE,
+ [SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_RELOAD] = UNIT_RELOADING,
[SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING,
[SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING,
- [SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
+ [SERVICE_RELOAD_POST] = UNIT_RELOADING,
[SERVICE_MOUNTING] = UNIT_REFRESHING,
[SERVICE_STOP] = UNIT_DEACTIVATING,
[SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING,
@@ -126,13 +130,15 @@ static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void
static int service_dispatch_exec_io(sd_event_source *source, int fd, uint32_t events, void *userdata);
static void service_enter_signal(Service *s, ServiceState state, ServiceResult f);
+
+static void service_reload_finish(Service *s, ServiceResult f);
static void service_enter_reload_by_notify(Service *s);
static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) {
return IN_SET(state,
SERVICE_START, SERVICE_START_POST,
SERVICE_RUNNING,
- SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS,
+ SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL);
@@ -142,13 +148,21 @@ static bool SERVICE_STATE_WITH_CONTROL_PROCESS(ServiceState state) {
return IN_SET(state,
SERVICE_CONDITION,
SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
- SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS,
+ SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
SERVICE_CLEANING);
}
+static bool SERVICE_STATE_WITH_WATCHDOG(ServiceState state) {
+ return IN_SET(state,
+ SERVICE_START_POST,
+ SERVICE_RUNNING,
+ SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
+ SERVICE_MOUNTING);
+}
+
static void service_init(Unit *u) {
Service *s = SERVICE(u);
@@ -175,6 +189,7 @@ static void service_init(Unit *u) {
EXEC_KEYRING_PRIVATE : EXEC_KEYRING_INHERIT;
s->notify_access_override = _NOTIFY_ACCESS_INVALID;
+ s->notify_state = _NOTIFY_STATE_INVALID;
s->watchdog_original_usec = USEC_INFINITY;
@@ -1289,7 +1304,7 @@ static void service_set_state(Service *s, ServiceState state) {
if (!IN_SET(state,
SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_RUNNING,
- SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS,
+ SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
@@ -1317,7 +1332,7 @@ static void service_set_state(Service *s, ServiceState state) {
if (state != SERVICE_START)
s->exec_fd_event_source = sd_event_source_disable_unref(s->exec_fd_event_source);
- if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS, SERVICE_MOUNTING))
+ if (!SERVICE_STATE_WITH_WATCHDOG(state))
service_stop_watchdog(s);
if (state != SERVICE_MOUNTING) /* Just in case */
@@ -1359,10 +1374,11 @@ static usec_t service_coldplug_timeout(Service *s) {
case SERVICE_START_PRE:
case SERVICE_START:
case SERVICE_START_POST:
+ case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
- case SERVICE_REFRESH_EXTENSIONS:
+ case SERVICE_RELOAD_POST:
case SERVICE_MOUNTING:
return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_start_usec);
@@ -1429,7 +1445,7 @@ static int service_coldplug(Unit *u) {
SERVICE_DEAD_RESOURCES_PINNED))
(void) unit_setup_exec_runtime(u);
- if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS, SERVICE_MOUNTING) &&
+ if (SERVICE_STATE_WITH_WATCHDOG(s->deserialized_state) &&
freezer_state_objective(u->freezer_state) == FREEZER_RUNNING)
service_start_watchdog(s);
@@ -2167,8 +2183,9 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
/* The next restart might not be a manual stop, hence reset the flag indicating manual stops */
s->forbid_restart = false;
- /* Reset NotifyAccess override */
+ /* Reset notify states */
s->notify_access_override = _NOTIFY_ACCESS_INVALID;
+ s->notify_state = _NOTIFY_STATE_INVALID;
/* We want fresh tmpdirs and ephemeral snapshots in case the service is started again immediately. */
s->exec_runtime = exec_runtime_destroy(s->exec_runtime);
@@ -2205,7 +2222,6 @@ static void service_enter_stop_post(Service *s, ServiceResult f) {
s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST];
if (s->control_command) {
s->control_command_id = SERVICE_EXEC_STOP_POST;
- pidref_done(&s->control_pid);
r = service_spawn(s,
s->control_command,
@@ -2318,7 +2334,6 @@ static void service_enter_stop(Service *s, ServiceResult f) {
s->control_command = s->exec_command[SERVICE_EXEC_STOP];
if (s->control_command) {
s->control_command_id = SERVICE_EXEC_STOP;
- pidref_done(&s->control_pid);
r = service_spawn(s,
s->control_command,
@@ -2404,7 +2419,6 @@ static void service_enter_start_post(Service *s) {
s->control_command = s->exec_command[SERVICE_EXEC_START_POST];
if (s->control_command) {
s->control_command_id = SERVICE_EXEC_START_POST;
- pidref_done(&s->control_pid);
r = service_spawn(s,
s->control_command,
@@ -2538,7 +2552,6 @@ static void service_enter_start(Service *s) {
case SERVICE_FORKING:
/* For forking services we wait until the start process exited. */
- pidref_done(&s->control_pid);
s->control_pid = TAKE_PIDREF(pidref);
return service_set_state(s, SERVICE_START);
@@ -2612,7 +2625,6 @@ static void service_enter_condition(Service *s) {
goto fail;
s->control_command_id = SERVICE_EXEC_CONDITION;
- pidref_done(&s->control_pid);
r = service_spawn(s,
s->control_command,
@@ -2696,9 +2708,7 @@ static void service_enter_reload_by_notify(Service *s) {
r = service_arm_timer(s, /* relative= */ true, s->timeout_start_usec);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
- s->reload_result = SERVICE_FAILURE_RESOURCES;
- service_enter_running(s, SERVICE_SUCCESS);
- return;
+ return service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
}
service_set_state(s, SERVICE_RELOAD_NOTIFY);
@@ -2709,31 +2719,91 @@ static void service_enter_reload_by_notify(Service *s) {
log_unit_warning(UNIT(s), "Failed to schedule propagation of reload, ignoring: %s", bus_error_message(&error, r));
}
-static void service_enter_reload_signal_exec(Service *s) {
- bool killed = false;
+static void service_enter_reload_post(Service *s) {
int r;
assert(s);
service_unwatch_control_pid(s);
- s->reload_result = SERVICE_SUCCESS;
- usec_t ts = now(CLOCK_MONOTONIC);
+ s->control_command = s->exec_command[SERVICE_EXEC_RELOAD_POST];
+ if (s->control_command) {
+ s->control_command_id = SERVICE_EXEC_RELOAD_POST;
+
+ r = service_spawn(s,
+ s->control_command,
+ service_exec_flags(s->control_command_id, /* cred_flag = */ 0),
+ s->timeout_start_usec,
+ &s->control_pid);
+ if (r < 0) {
+ log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'reload-post' task: %m");
+ return service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
+ }
+
+ service_set_state(s, SERVICE_RELOAD_POST);
+ } else
+ service_reload_finish(s, SERVICE_SUCCESS);
+}
+
+static void service_enter_reload_signal(Service *s) {
+ int r;
+
+ assert(s);
+
+ if (s->type != SERVICE_NOTIFY_RELOAD)
+ return service_enter_reload_post(s);
+
+ if (s->state == SERVICE_RELOAD) {
+ /* We executed ExecReload=, and the service has already notified us the result?
+ * Directly transition to next state. */
+ if (s->notify_state == NOTIFY_RELOADING)
+ return service_set_state(s, SERVICE_RELOAD_NOTIFY);
+ if (s->notify_state == NOTIFY_RELOAD_READY)
+ return service_enter_reload_post(s);
+ }
+
+ if (pidref_is_set(&s->main_pid)) {
+ r = service_arm_timer(s, /* relative= */ true, s->timeout_start_usec);
+ if (r < 0) {
+ log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
+ goto fail;
+ }
- if (s->type == SERVICE_NOTIFY_RELOAD && pidref_is_set(&s->main_pid)) {
r = pidref_kill_and_sigcont(&s->main_pid, s->reload_signal);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to send reload signal: %m");
goto fail;
}
- killed = true;
- }
+ service_set_state(s, SERVICE_RELOAD_SIGNAL);
+ } else
+ service_enter_reload_post(s);
+
+ return;
+
+fail:
+ service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
+}
+
+static void service_enter_reload(Service *s) {
+ int r;
+
+ assert(s);
+
+ service_unwatch_control_pid(s);
+
+ if (IN_SET(s->notify_state, NOTIFY_RELOADING, NOTIFY_RELOAD_READY))
+ s->notify_state = _NOTIFY_STATE_INVALID;
+
+ /* Store the timestamp when we started reloading: when reloading via SIGHUP we won't leave the reload
+ * state until we received both RELOADING=1 and READY=1 with MONOTONIC_USEC= set to a value above
+ * this. Thus we know for sure the reload cycle was executed *after* we requested it, and is not one
+ * that was already in progress before. */
+ s->reload_begin_usec = now(CLOCK_MONOTONIC);
s->control_command = s->exec_command[SERVICE_EXEC_RELOAD];
if (s->control_command) {
s->control_command_id = SERVICE_EXEC_RELOAD;
- pidref_done(&s->control_pid);
r = service_spawn(s,
s->control_command,
@@ -2742,33 +2812,12 @@ static void service_enter_reload_signal_exec(Service *s) {
&s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'reload' task: %m");
- goto fail;
+ return service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
}
service_set_state(s, SERVICE_RELOAD);
- } else if (killed) {
- r = service_arm_timer(s, /* relative= */ true, s->timeout_start_usec);
- if (r < 0) {
- log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
- goto fail;
- }
-
- service_set_state(s, SERVICE_RELOAD_SIGNAL);
- } else {
- service_enter_running(s, SERVICE_SUCCESS);
- return;
- }
-
- /* Store the timestamp when we started reloading: when reloading via SIGHUP we won't leave the reload
- * state until we received both RELOADING=1 and READY=1 with MONOTONIC_USEC= set to a value above
- * this. Thus we know for sure the reload cycle was executed *after* we requested it, and is not one
- * that was already in progress before. */
- s->reload_begin_usec = ts;
- return;
-
-fail:
- s->reload_result = SERVICE_FAILURE_RESOURCES;
- service_enter_running(s, SERVICE_SUCCESS);
+ } else
+ service_enter_reload_signal(s);
}
static bool service_should_reload_extensions(Service *s) {
@@ -2805,15 +2854,20 @@ static void service_enter_refresh_extensions(Service *s) {
assert(s);
- /* If we don't have extensions to reload, immediately go to the signal step */
+ /* If we don't have extensions to refresh, immediately transition to reload state */
if (!service_should_reload_extensions(s))
- return (void) service_enter_reload_signal_exec(s);
+ return service_enter_reload(s);
service_unwatch_control_pid(s);
- s->reload_result = SERVICE_SUCCESS;
s->control_command = NULL;
s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
+ r = service_arm_timer(s, /* relative= */ true, s->timeout_start_usec);
+ if (r < 0) {
+ log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
+ goto fail;
+ }
+
/* Given we are running from PID1, avoid doing potentially heavy I/O operations like opening images
* directly, and instead fork a worker process. */
r = unit_fork_helper_process(UNIT(s), "(sd-refresh-extensions)", /* into_cgroup= */ false, &worker);
@@ -2868,28 +2922,7 @@ static void service_enter_refresh_extensions(Service *s) {
return;
fail:
- s->reload_result = SERVICE_FAILURE_RESOURCES;
- service_enter_running(s, SERVICE_SUCCESS);
-}
-
-static void service_enter_reload_mounting(Service *s) {
- int r;
-
- assert(s);
-
- usec_t ts = now(CLOCK_MONOTONIC);
-
- r = service_arm_timer(s, /* relative= */ true, s->timeout_start_usec);
- if (r < 0) {
- log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
- s->reload_result = SERVICE_FAILURE_RESOURCES;
- service_enter_running(s, SERVICE_SUCCESS);
- return;
- }
-
- s->reload_begin_usec = ts;
-
- service_enter_refresh_extensions(s);
+ service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
}
static void service_run_next_control(Service *s) {
@@ -2905,13 +2938,15 @@ static void service_run_next_control(Service *s) {
s->control_command = s->control_command->command_next;
service_unwatch_control_pid(s);
- if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD))
+ if (IN_SET(s->state,
+ SERVICE_CONDITION,
+ SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
+ SERVICE_RUNNING,
+ SERVICE_RELOAD, SERVICE_RELOAD_POST))
timeout = s->timeout_start_usec;
else
timeout = s->timeout_stop_usec;
- pidref_done(&s->control_pid);
-
r = service_spawn(s,
s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag = */ 0),
@@ -2924,10 +2959,9 @@ static void service_run_next_control(Service *s) {
service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES);
else if (s->state == SERVICE_STOP_POST)
service_enter_dead(s, SERVICE_FAILURE_RESOURCES, /* allow_restart= */ true);
- else if (s->state == SERVICE_RELOAD) {
- s->reload_result = SERVICE_FAILURE_RESOURCES;
- service_enter_running(s, SERVICE_SUCCESS);
- } else
+ else if (IN_SET(s->state, SERVICE_RELOAD, SERVICE_RELOAD_POST))
+ service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
+ else
service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
}
}
@@ -2979,8 +3013,6 @@ static int service_start(Unit *u) {
if (r < 0)
return r;
- s->result = SERVICE_SUCCESS;
- s->reload_result = SERVICE_SUCCESS;
s->main_pid_known = false;
s->main_pid_alien = false;
s->forbid_restart = false;
@@ -2989,13 +3021,17 @@ static int service_start(Unit *u) {
if (s->state != SERVICE_AUTO_RESTART_QUEUED)
s->n_restarts = 0;
+ s->result = SERVICE_SUCCESS;
+ s->reload_result = SERVICE_SUCCESS;
+ s->reload_begin_usec = USEC_INFINITY;
+
s->status_text = mfree(s->status_text);
s->status_errno = 0;
s->status_bus_error = mfree(s->status_bus_error);
s->status_varlink_error = mfree(s->status_varlink_error);
s->notify_access_override = _NOTIFY_ACCESS_INVALID;
- s->notify_state = NOTIFY_UNKNOWN;
+ s->notify_state = _NOTIFY_STATE_INVALID;
s->watchdog_original_usec = s->watchdog_usec;
s->watchdog_override_enable = false;
@@ -3040,6 +3076,20 @@ static void service_live_mount_finish(Service *s, ServiceResult f, const char *e
s->mount_request = sd_bus_message_unref(s->mount_request);
}
+static void service_reload_finish(Service *s, ServiceResult f) {
+ assert(s);
+
+ s->reload_result = f;
+ s->reload_begin_usec = USEC_INFINITY;
+
+ /* If notify state is still in dangling NOTIFY_RELOADING, reset it so service_enter_running()
+ * won't get confused (see #37515) */
+ if (s->notify_state == NOTIFY_RELOADING)
+ s->notify_state = _NOTIFY_STATE_INVALID;
+
+ service_enter_running(s, SERVICE_SUCCESS);
+}
+
static int service_stop(Unit *u) {
Service *s = ASSERT_PTR(SERVICE(u));
@@ -3077,6 +3127,7 @@ static int service_stop(Unit *u) {
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
+ case SERVICE_RELOAD_POST:
case SERVICE_STOP_WATCHDOG:
/* If there's already something running we go directly into kill mode. */
service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
@@ -3108,7 +3159,9 @@ static int service_reload(Unit *u) {
assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
- service_enter_reload_mounting(s);
+ s->reload_result = SERVICE_SUCCESS;
+
+ service_enter_refresh_extensions(s);
return 1;
}
@@ -3308,6 +3361,8 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
if (s->notify_access_override >= 0)
(void) serialize_item(f, "notify-access-override", notify_access_to_string(s->notify_access_override));
+ if (s->notify_state >= 0)
+ (void) serialize_item(f, "notify-state", notify_state_to_string(s->notify_state));
r = serialize_item_escaped(f, "status-text", s->status_text);
if (r < 0)
@@ -3618,6 +3673,16 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
log_unit_debug(u, "Failed to parse notify-access-override value: %s", value);
else
s->notify_access_override = notify_access;
+
+ } else if (streq(key, "notify-state")) {
+ NotifyState notify_state;
+
+ notify_state = notify_state_from_string(value);
+ if (notify_state < 0)
+ log_unit_debug(u, "Failed to parse notify-state value: %s", value);
+ else
+ s->notify_state = notify_state;
+
} else if (streq(key, "n-restarts")) {
r = safe_atou(value, &s->n_restarts);
if (r < 0)
@@ -4115,10 +4180,11 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
switch (s->state) {
case SERVICE_START_POST:
+ case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
- case SERVICE_REFRESH_EXTENSIONS:
+ case SERVICE_RELOAD_POST:
case SERVICE_MOUNTING:
/* If neither main nor control processes are running then the current
* state can never exit cleanly, hence immediately terminate the
@@ -4234,7 +4300,8 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
success,
code, status);
- if (!IN_SET(s->state, SERVICE_RELOAD, SERVICE_MOUNTING) && s->result == SERVICE_SUCCESS)
+ if (!IN_SET(s->state, SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_POST, SERVICE_MOUNTING) &&
+ s->result == SERVICE_SUCCESS)
s->result = f;
if (s->control_command &&
@@ -4321,28 +4388,28 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
service_enter_running(s, SERVICE_SUCCESS);
break;
- case SERVICE_RELOAD:
- case SERVICE_RELOAD_SIGNAL:
- case SERVICE_RELOAD_NOTIFY:
+ case SERVICE_REFRESH_EXTENSIONS:
if (f == SERVICE_SUCCESS)
- if (service_load_pid_file(s, true) < 0)
- service_search_main_pid(s);
-
- s->reload_result = f;
-
- /* If the last notification we received from the service process indicates
- * we are still reloading, then don't leave reloading state just yet, just
- * transition into SERVICE_RELOAD_NOTIFY, to wait for the READY=1 coming,
- * too. */
- if (s->notify_state == NOTIFY_RELOADING)
- service_set_state(s, SERVICE_RELOAD_NOTIFY);
+ /* Remounting extensions asynchronously done, proceed to reload */
+ service_enter_reload(s);
else
- service_enter_running(s, SERVICE_SUCCESS);
+ service_reload_finish(s, f);
break;
- case SERVICE_REFRESH_EXTENSIONS:
- /* Remounting extensions asynchronously done, proceed to signal */
- service_enter_reload_signal_exec(s);
+ case SERVICE_RELOAD:
+ if (f != SERVICE_SUCCESS) {
+ service_reload_finish(s, f);
+ break;
+ }
+
+ if (service_load_pid_file(s, true) < 0)
+ service_search_main_pid(s);
+
+ service_enter_reload_signal(s);
+ break;
+
+ case SERVICE_RELOAD_POST:
+ service_reload_finish(s, f);
break;
case SERVICE_MOUNTING:
@@ -4440,14 +4507,14 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
service_enter_stop(s, SERVICE_FAILURE_TIMEOUT);
break;
+ case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
- case SERVICE_REFRESH_EXTENSIONS:
+ case SERVICE_RELOAD_POST:
log_unit_warning(UNIT(s), "Reload operation timed out. Killing reload process.");
service_kill_control_process(s);
- s->reload_result = SERVICE_FAILURE_TIMEOUT;
- service_enter_running(s, SERVICE_SUCCESS);
+ service_reload_finish(s, SERVICE_FAILURE_TIMEOUT);
break;
case SERVICE_MOUNTING:
@@ -4747,6 +4814,90 @@ finish:
return 1;
}
+static void service_notify_message_process_state(Service *s, char * const *tags) {
+ usec_t monotonic_usec = USEC_INFINITY;
+ int r;
+
+ assert(s);
+
+ const char *e = strv_find_startswith(tags, "MONOTONIC_USEC=");
+ if (e) {
+ r = safe_atou64(e, &monotonic_usec);
+ if (r < 0)
+ log_unit_warning_errno(UNIT(s), r, "Failed to parse MONOTONIC_USEC= field in notification message, ignoring: %s", e);
+ }
+
+ /* Interpret READY=/STOPPING=/RELOADING=. STOPPING= wins over the others, and READY= over RELOADING= */
+ if (strv_contains(tags, "STOPPING=1")) {
+ s->notify_state = NOTIFY_STOPPING;
+
+ if (IN_SET(s->state, SERVICE_RUNNING, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS))
+ service_enter_stop_by_notify(s);
+
+ return;
+ }
+
+ /* Disallow resurrecting a dying service */
+ if (s->notify_state == NOTIFY_STOPPING)
+ return;
+
+ if (strv_contains(tags, "READY=1")) {
+
+ if (s->notify_state == NOTIFY_RELOADING)
+ s->notify_state = NOTIFY_RELOAD_READY;
+ else
+ s->notify_state = NOTIFY_READY;
+
+ /* Combined RELOADING=1 and READY=1? Then this is indication that the service started and
+ * immediately finished reloading. */
+ if (strv_contains(tags, "RELOADING=1")) {
+ if (s->state == SERVICE_RELOAD_SIGNAL &&
+ monotonic_usec != USEC_INFINITY &&
+ monotonic_usec >= s->reload_begin_usec)
+ /* Valid Type=notify-reload protocol? Then we're all good. */
+ service_enter_reload_post(s);
+
+ else if (s->state == SERVICE_RUNNING) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ /* Propagate a reload explicitly for plain RELOADING=1 (semantically equivalent to
+ * service_enter_reload_by_notify() call in below) */
+ r = manager_propagate_reload(UNIT(s)->manager, UNIT(s), JOB_FAIL, &error);
+ if (r < 0)
+ log_unit_warning(UNIT(s), "Failed to schedule propagation of reload, ignoring: %s",
+ bus_error_message(&error, r));
+ }
+ }
+
+ /* Type=notify(-reload) services inform us about completed initialization with READY=1 */
+ if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) &&
+ s->state == SERVICE_START)
+ service_enter_start_post(s);
+
+ /* Sending READY=1 while we are reloading informs us that the reloading is complete. */
+ if (s->state == SERVICE_RELOAD_NOTIFY)
+ service_enter_reload_post(s);
+
+ } else if (strv_contains(tags, "RELOADING=1")) {
+
+ s->notify_state = NOTIFY_RELOADING;
+
+ /* Sending RELOADING=1 after we send SIGHUP to request a reload will transition
+ * things to "reload-notify" state, where we'll wait for READY=1 to let us know the
+ * reload is done. Note that we insist on a timestamp being sent along here, so that
+ * we know for sure this is a reload cycle initiated *after* we sent the signal */
+ if (s->state == SERVICE_RELOAD_SIGNAL &&
+ monotonic_usec != USEC_INFINITY &&
+ monotonic_usec >= s->reload_begin_usec)
+ /* Note, we don't call service_enter_reload_by_notify() here, because we
+ * don't need reload propagation nor do we want to restart the timeout. */
+ service_set_state(s, SERVICE_RELOAD_NOTIFY);
+
+ if (s->state == SERVICE_RUNNING)
+ service_enter_reload_by_notify(s);
+ }
+}
+
static void service_notify_message(
Unit *u,
PidRef *pidref,
@@ -4768,7 +4919,6 @@ static void service_notify_message(
log_unit_debug(u, "Got notification message from PID "PID_FMT": %s", pidref->pid, empty_to_na(cc));
}
- usec_t monotonic_usec = USEC_INFINITY;
bool notify_dbus = false;
const char *e;
@@ -4778,7 +4928,7 @@ static void service_notify_message(
r = service_notify_message_parse_new_pid(u, tags, fds, &new_main_pid);
if (r > 0 &&
IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING,
- SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS,
+ SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_STOP, SERVICE_STOP_SIGTERM) &&
(!s->main_pid_known || !pidref_equal(&new_main_pid, &s->main_pid))) {
@@ -4803,79 +4953,7 @@ static void service_notify_message(
}
}
- /* Parse MONOTONIC_USEC= */
- e = strv_find_startswith(tags, "MONOTONIC_USEC=");
- if (e) {
- r = safe_atou64(e, &monotonic_usec);
- if (r < 0)
- log_unit_warning_errno(u, r, "Failed to parse MONOTONIC_USEC= field in notification message, ignoring: %s", e);
- }
-
- /* Interpret READY=/STOPPING=/RELOADING=. STOPPING= wins over the others, and READY= over RELOADING= */
- if (strv_contains(tags, "STOPPING=1")) {
- s->notify_state = NOTIFY_STOPPING;
-
- if (IN_SET(s->state, SERVICE_RUNNING, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS))
- service_enter_stop_by_notify(s);
-
- notify_dbus = true;
-
- } else if (strv_contains(tags, "READY=1")) {
-
- s->notify_state = NOTIFY_READY;
-
- /* Combined RELOADING=1 and READY=1? Then this is indication that the service started and
- * immediately finished reloading. */
- if (strv_contains(tags, "RELOADING=1")) {
- if (s->state == SERVICE_RELOAD_SIGNAL &&
- monotonic_usec != USEC_INFINITY &&
- monotonic_usec >= s->reload_begin_usec)
- /* Valid Type=notify-reload protocol? Then we're all good. */
- service_enter_running(s, SERVICE_SUCCESS);
-
- else if (s->state == SERVICE_RUNNING) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-
- /* Propagate a reload explicitly for plain RELOADING=1 (semantically equivalent to
- * service_enter_reload_mounting() call in below) */
- r = manager_propagate_reload(UNIT(s)->manager, UNIT(s), JOB_FAIL, &error);
- if (r < 0)
- log_unit_warning(UNIT(s), "Failed to schedule propagation of reload, ignoring: %s",
- bus_error_message(&error, r));
- }
- }
-
- /* Type=notify(-reload) services inform us about completed initialization with READY=1 */
- if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) &&
- s->state == SERVICE_START)
- service_enter_start_post(s);
-
- /* Sending READY=1 while we are reloading informs us that the reloading is complete. */
- if (s->state == SERVICE_RELOAD_NOTIFY)
- service_enter_running(s, SERVICE_SUCCESS);
-
- notify_dbus = true;
-
- } else if (strv_contains(tags, "RELOADING=1")) {
-
- s->notify_state = NOTIFY_RELOADING;
-
- /* Sending RELOADING=1 after we send SIGHUP to request a reload will transition
- * things to "reload-notify" state, where we'll wait for READY=1 to let us know the
- * reload is done. Note that we insist on a timestamp being sent along here, so that
- * we know for sure this is a reload cycle initiated *after* we sent the signal */
- if (s->state == SERVICE_RELOAD_SIGNAL &&
- monotonic_usec != USEC_INFINITY &&
- monotonic_usec >= s->reload_begin_usec)
- /* Note, we don't call service_enter_reload_by_notify() here, because we
- * don't need reload propagation nor do we want to restart the timeout. */
- service_set_state(s, SERVICE_RELOAD_NOTIFY);
-
- if (s->state == SERVICE_RUNNING)
- service_enter_reload_by_notify(s);
-
- notify_dbus = true;
- }
+ service_notify_message_process_state(s, tags);
/* Interpret STATUS= */
e = strv_find_startswith(tags, "STATUS=");
@@ -5118,10 +5196,11 @@ static bool pick_up_pid_from_bus_name(Service *s) {
SERVICE_START,
SERVICE_START_POST,
SERVICE_RUNNING,
+ SERVICE_REFRESH_EXTENSIONS,
SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL,
SERVICE_RELOAD_NOTIFY,
- SERVICE_REFRESH_EXTENSIONS,
+ SERVICE_RELOAD_POST,
SERVICE_MOUNTING);
}
@@ -5303,10 +5382,11 @@ static bool service_needs_console(Unit *u) {
SERVICE_START,
SERVICE_START_POST,
SERVICE_RUNNING,
+ SERVICE_REFRESH_EXTENSIONS,
SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL,
SERVICE_RELOAD_NOTIFY,
- SERVICE_REFRESH_EXTENSIONS,
+ SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
SERVICE_STOP,
SERVICE_STOP_WATCHDOG,
@@ -5739,34 +5819,36 @@ static const char* const service_exit_type_table[_SERVICE_EXIT_TYPE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(service_exit_type, ServiceExitType);
static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = {
- [SERVICE_EXEC_CONDITION] = "ExecCondition",
- [SERVICE_EXEC_START_PRE] = "ExecStartPre",
- [SERVICE_EXEC_START] = "ExecStart",
- [SERVICE_EXEC_START_POST] = "ExecStartPost",
- [SERVICE_EXEC_RELOAD] = "ExecReload",
- [SERVICE_EXEC_STOP] = "ExecStop",
- [SERVICE_EXEC_STOP_POST] = "ExecStopPost",
+ [SERVICE_EXEC_CONDITION] = "ExecCondition",
+ [SERVICE_EXEC_START_PRE] = "ExecStartPre",
+ [SERVICE_EXEC_START] = "ExecStart",
+ [SERVICE_EXEC_START_POST] = "ExecStartPost",
+ [SERVICE_EXEC_RELOAD] = "ExecReload",
+ [SERVICE_EXEC_RELOAD_POST] = "ExecReloadPost",
+ [SERVICE_EXEC_STOP] = "ExecStop",
+ [SERVICE_EXEC_STOP_POST] = "ExecStopPost",
};
DEFINE_STRING_TABLE_LOOKUP(service_exec_command, ServiceExecCommand);
static const char* const service_exec_ex_command_table[_SERVICE_EXEC_COMMAND_MAX] = {
- [SERVICE_EXEC_CONDITION] = "ExecConditionEx",
- [SERVICE_EXEC_START_PRE] = "ExecStartPreEx",
- [SERVICE_EXEC_START] = "ExecStartEx",
- [SERVICE_EXEC_START_POST] = "ExecStartPostEx",
- [SERVICE_EXEC_RELOAD] = "ExecReloadEx",
- [SERVICE_EXEC_STOP] = "ExecStopEx",
- [SERVICE_EXEC_STOP_POST] = "ExecStopPostEx",
+ [SERVICE_EXEC_CONDITION] = "ExecConditionEx",
+ [SERVICE_EXEC_START_PRE] = "ExecStartPreEx",
+ [SERVICE_EXEC_START] = "ExecStartEx",
+ [SERVICE_EXEC_START_POST] = "ExecStartPostEx",
+ [SERVICE_EXEC_RELOAD] = "ExecReloadEx",
+ [SERVICE_EXEC_RELOAD_POST] = "ExecReloadPostEx",
+ [SERVICE_EXEC_STOP] = "ExecStopEx",
+ [SERVICE_EXEC_STOP_POST] = "ExecStopPostEx",
};
DEFINE_STRING_TABLE_LOOKUP(service_exec_ex_command, ServiceExecCommand);
static const char* const notify_state_table[_NOTIFY_STATE_MAX] = {
- [NOTIFY_UNKNOWN] = "unknown",
- [NOTIFY_READY] = "ready",
- [NOTIFY_RELOADING] = "reloading",
- [NOTIFY_STOPPING] = "stopping",
+ [NOTIFY_READY] = "ready",
+ [NOTIFY_RELOADING] = "reloading",
+ [NOTIFY_RELOAD_READY] = "reload-ready",
+ [NOTIFY_STOPPING] = "stopping",
};
DEFINE_STRING_TABLE_LOOKUP(notify_state, NotifyState);
diff --git a/src/core/service.h b/src/core/service.h
index b69f3008de..06fafb5482 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -46,6 +46,7 @@ typedef enum ServiceExecCommand {
SERVICE_EXEC_START,
SERVICE_EXEC_START_POST,
SERVICE_EXEC_RELOAD,
+ SERVICE_EXEC_RELOAD_POST,
SERVICE_EXEC_STOP,
SERVICE_EXEC_STOP_POST,
_SERVICE_EXEC_COMMAND_MAX,
@@ -53,9 +54,9 @@ typedef enum ServiceExecCommand {
} ServiceExecCommand;
typedef enum NotifyState {
- NOTIFY_UNKNOWN,
NOTIFY_READY,
NOTIFY_RELOADING,
+ NOTIFY_RELOAD_READY,
NOTIFY_STOPPING,
_NOTIFY_STATE_MAX,
_NOTIFY_STATE_INVALID = -EINVAL,
@@ -298,7 +299,5 @@ ServiceTimeoutFailureMode service_timeout_failure_mode_from_string(const char *s
DEFINE_CAST(SERVICE, Service);
-#define STATUS_TEXT_MAX (16U*1024U)
-
/* Only exported for unit tests */
int service_deserialize_exec_command(Unit *u, const char *key, const char *value);
diff --git a/src/core/socket.c b/src/core/socket.c
index d57e2302de..cc339d631d 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -2148,8 +2148,6 @@ static void socket_enter_stop_post(Socket *s, SocketResult f) {
s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST];
if (s->control_command) {
- pidref_done(&s->control_pid);
-
r = socket_spawn(s, s->control_command, &s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'stop-post' task: %m");
@@ -2226,8 +2224,6 @@ static void socket_enter_stop_pre(Socket *s, SocketResult f) {
s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE];
if (s->control_command) {
- pidref_done(&s->control_pid);
-
r = socket_spawn(s, s->control_command, &s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'stop-pre' task: %m");
@@ -2289,8 +2285,6 @@ static void socket_enter_start_post(Socket *s) {
s->control_command = s->exec_command[SOCKET_EXEC_START_POST];
if (s->control_command) {
- pidref_done(&s->control_pid);
-
r = socket_spawn(s, s->control_command, &s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'start-post' task: %m");
@@ -2363,8 +2357,6 @@ static void socket_enter_start_pre(Socket *s) {
s->control_command = s->exec_command[SOCKET_EXEC_START_PRE];
if (s->control_command) {
- pidref_done(&s->control_pid);
-
r = socket_spawn(s, s->control_command, &s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'start-pre' task: %m");
@@ -2612,8 +2604,6 @@ static void socket_run_next(Socket *s) {
s->control_command = s->control_command->command_next;
- pidref_done(&s->control_pid);
-
r = socket_spawn(s, s->control_command, &s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn next task: %m");
diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h
index ba2551cd9e..8fc23c5e6a 100644
--- a/src/fundamental/macro-fundamental.h
+++ b/src/fundamental/macro-fundamental.h
@@ -350,11 +350,12 @@ assert_cc(sizeof(long long) == sizeof(intmax_t));
#define CASE_F_18(X, ...) case X: CASE_F_17( __VA_ARGS__)
#define CASE_F_19(X, ...) case X: CASE_F_18( __VA_ARGS__)
#define CASE_F_20(X, ...) case X: CASE_F_19( __VA_ARGS__)
+#define CASE_F_21(X, ...) case X: CASE_F_20( __VA_ARGS__)
-#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME
+#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,NAME,...) NAME
#define FOR_EACH_MAKE_CASE(...) \
- GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \
- CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \
+ GET_CASE_F(__VA_ARGS__,CASE_F_21,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12, \
+ CASE_F_11,CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \
(__VA_ARGS__)
#define IN_SET(x, first, ...) \
@@ -363,7 +364,7 @@ assert_cc(sizeof(long long) == sizeof(intmax_t));
/* If the build breaks in the line below, you need to extend the case macros. We use typeof(+x) \
* here to widen the type of x if it is a bit-field as this would otherwise be illegal. */ \
static const typeof(+x) __assert_in_set[] _unused_ = { first, __VA_ARGS__ }; \
- assert_cc(ELEMENTSOF(__assert_in_set) <= 20); \
+ assert_cc(ELEMENTSOF(__assert_in_set) <= 21); \
switch (x) { \
FOR_EACH_MAKE_CASE(first, __VA_ARGS__) \
_found = true; \
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 30a029716d..3d49cf5415 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -2649,6 +2649,8 @@ static const BusProperty service_properties[] = {
{ "ExecStartPostEx", bus_append_exec_command }, /* compat */
{ "ExecReload", bus_append_exec_command },
{ "ExecReloadEx", bus_append_exec_command }, /* compat */
+ { "ExecReloadPost", bus_append_exec_command },
+ { "ExecReloadPostEx", bus_append_exec_command }, /* compat */
{ "ExecStop", bus_append_exec_command },
{ "ExecStopEx", bus_append_exec_command }, /* compat */
{ "ExecStopPost", bus_append_exec_command },
diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c
index 79e1908a23..f1c1ab0eff 100644
--- a/src/systemctl/systemctl-show.c
+++ b/src/systemctl/systemctl-show.c
@@ -2284,6 +2284,8 @@ static int show_one(
{ "ExecStartPostEx", "a(sasasttttuii)", map_exec, 0 },
{ "ExecReload", "a(sasbttttuii)", map_exec, 0 },
{ "ExecReloadEx", "a(sasasttttuii)", map_exec, 0 },
+ { "ExecReloadPost", "a(sasbttttuii)", map_exec, 0 },
+ { "ExecReloadPostEx", "a(sasasttttuii)", map_exec, 0 },
{ "ExecStopPre", "a(sasbttttuii)", map_exec, 0 },
{ "ExecStop", "a(sasbttttuii)", map_exec, 0 },
{ "ExecStopEx", "a(sasasttttuii)", map_exec, 0 },
diff --git a/test/integration-tests/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/reload-timeout.service b/test/integration-tests/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/reload-timeout.service
new file mode 100644
index 0000000000..95a501e37a
--- /dev/null
+++ b/test/integration-tests/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/reload-timeout.service
@@ -0,0 +1,4 @@
+[Service]
+Type=notify-reload
+TimeoutStartSec=40
+ExecStart=/usr/lib/systemd/tests/testdata/TEST-80-NOTIFYACCESS.units/reload-timeout.sh
diff --git a/test/integration-tests/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/reload-timeout.sh b/test/integration-tests/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/reload-timeout.sh
new file mode 100755
index 0000000000..266c3cfef2
--- /dev/null
+++ b/test/integration-tests/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/reload-timeout.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+COUNTER=0
+
+sync_in() {
+ read -r x < /tmp/syncfifo2
+ test "$x" = "$1"
+}
+
+wait_for_signal() {
+ sleep infinity &
+ wait "$!" || :
+}
+
+sighup_handler() {
+ echo "hup$(( ++COUNTER ))" > /tmp/syncfifo1
+}
+
+trap sighup_handler SIGHUP
+
+export SYSTEMD_LOG_LEVEL=debug
+
+systemd-notify --ready
+
+wait_for_signal
+systemd-notify --reloading
+
+wait_for_signal
+systemd-notify --reloading
+sync_in ready
+systemd-notify --ready
+
+wait_for_signal
+systemd-notify --reloading --ready
+
+exec sleep infinity
diff --git a/test/units/TEST-80-NOTIFYACCESS.sh b/test/units/TEST-80-NOTIFYACCESS.sh
index a8ecd9fa70..22a5a3af80 100755
--- a/test/units/TEST-80-NOTIFYACCESS.sh
+++ b/test/units/TEST-80-NOTIFYACCESS.sh
@@ -62,6 +62,33 @@ assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "none"
systemctl stop notify.service
assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
+# Timeout of READY=1 for Type=notify-reload services (issue #37515)
+
+systemctl start reload-timeout.service
+
+systemctl reload --no-block reload-timeout.service
+timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-signal" ]]; do sleep .5; done'
+sync_in hup1
+timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-notify" ]]; do sleep .5; done'
+timeout 80 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "running" ]]; do sleep 5; done'
+assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "timeout"
+
+systemctl reload --no-block reload-timeout.service
+timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-signal" ]]; do sleep .5; done'
+assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "success"
+sync_in hup2
+timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-notify" ]]; do sleep .5; done'
+sync_out ready
+timeout 40 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "running" ]]; do sleep 1; done'
+assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "success"
+
+systemctl reload --no-block reload-timeout.service
+sync_in hup3
+timeout 40 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "running" ]]; do sleep 1; done'
+assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "success"
+
+systemctl stop reload-timeout.service
+
rm /tmp/syncfifo1 /tmp/syncfifo2
# Explicitly test busctl's BUSERROR= reporting and systemctl status should show it