]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core/service: rework ExecReload= + Type=notify-reload interaction, add ExecReloadPost=
authorMike Yuan <me@yhndnzj.com>
Sun, 19 Oct 2025 21:23:17 +0000 (23:23 +0200)
committerMike Yuan <me@yhndnzj.com>
Tue, 4 Nov 2025 11:18:33 +0000 (12:18 +0100)
When Type=notify-reload got introduced, it wasn't intended to be
mutually exclusive with ExecReload=. However, currently ExecReload=
is immediately forked off after the service main process is signaled,
leaving states in between essentially undefined. Given so broken
it is I doubt any sane user is using this setup, hence I took a stab
to rework everything:

1.  Extensions are refreshed (unchanged)
2.  ExecReload= is forked off without signaling the process
3a. If RELOADING=1 is sent during the ExecReload= invocation,
    we'd refrain from signaling the process again, instead
    just transition to SERVICE_RELOAD_NOTIFY directly and
    wait for READY=1
3b. If not, signal the process after ExecReload= finishes
    (from now on the same as Type=notify-reload w/o ExecReload=)
4.  To accomodate the use case of performing post-reload tasks,
    ExecReloadPost= is introduced which executes after READY=1

The new model greatly simplifies things, as no control processes
will be around in SERVICE_RELOAD_SIGNAL and SERVICE_RELOAD_NOTIFY
states.

See also: https://github.com/systemd/systemd/issues/37515#issuecomment-2891229652

man/org.freedesktop.systemd1.xml
man/systemd.service.xml
src/basic/unit-def.c
src/basic/unit-def.h
src/core/dbus-service.c
src/core/load-fragment-gperf.gperf.in
src/core/service.c
src/core/service.h
src/shared/bus-unit-util.c
src/systemctl/systemctl-show.c

index 127ff766776ee88357e9e37603f3766245941b8e..f1f433bbac9c4931a457c8d9c00c188a88da4ea7 100644 (file)
@@ -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,6 +3541,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <!--property ExecReloadEx is not documented!-->
 
+    <!--property ExecReloadPostEx is not documented!-->
+
     <!--property ExecStopEx is not documented!-->
 
     <!--property ExecStopPostEx is not documented!-->
@@ -4209,6 +4215,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="ExecReloadEx"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ExecReloadPost"/>
+
+    <variablelist class="dbus-property" generated="True" extra-ref="ExecReloadPostEx"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="ExecStop"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="ExecStopEx"/>
@@ -4839,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.</para>
 
       <para><varname>ExecStartPre</varname>, <varname>ExecStart</varname>, <varname>ExecStartPost</varname>,
-      <varname>ExecReload</varname>, <varname>ExecStop</varname>, and <varname>ExecStopPost</varname> 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
+      <varname>ExecReload</varname>, <varname>ExecReloadPost</varname>, <varname>ExecStop</varname>, and
+      <varname>ExecStopPost</varname> 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
       <constant>CLOCK_REALTIME</constant>/<constant>CLOCK_MONOTONIC</constant> 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
@@ -12540,8 +12550,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>LogsDirectoryAccounting</varname>, and
       <function>KillSubgroup()</function> were added in version 258.</para>
       <para><varname>UserNamespacePath</varname>,
-      <varname>OOMKills</varname>, and
-      <varname>ManagedOOMKills</varname> were added in 259.</para>
+      <varname>OOMKills</varname>,
+      <varname>ManagedOOMKills</varname>,
+      <varname>ExecReloadPost</varname>, and
+      <varname>ExecReloadPostEx</varname> were added in version 259.</para>
     </refsect2>
     <refsect2>
       <title>Socket Unit Objects</title>
index 8c00a093f87296ab08c694cf4eed479daf6151ce..2ddeeafcec842b6ca659196984a6f256668a768c 100644 (file)
       <varlistentry>
         <term><varname>ExecReload=</varname></term>
 
-        <listitem><para>Commands to execute to trigger a configuration reload in the service. This argument
-        takes multiple command lines, following the same scheme as described for
+        <listitem><para>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
         <varname>ExecStart=</varname> above. Use of this setting is optional. Specifier and environment
         variable substitution is supported here following the same scheme as for
         <varname>ExecStart=</varname>.</para>
 
         <programlisting>ExecReload=kill -HUP $MAINPID</programlisting>
 
-        <para>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 <varname>Type=</varname><option>notify-reload</option> in place of
-        <varname>ExecReload=</varname>, or to set <varname>ExecReload=</varname> to a command that not only
-        triggers a configuration reload of the daemon, but also synchronously waits for it to complete. For
-        example, <citerefentry
+        <para>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 <varname>Type=notify-reload</varname>,
+        or to set <varname>ExecReload=</varname> to a command that not only triggers a configuration reload
+        of the daemon, but also synchronously waits for it to complete. For example, <citerefentry
         project='mankier'><refentrytitle>dbus-broker</refentrytitle><manvolnum>1</manvolnum></citerefentry>
         uses the following:</para>
 
         /org/freedesktop/DBus org.freedesktop.DBus \
         ReloadConfig
 </programlisting>
+
+        <para>This setting can be combined with <varname>Type=notify-reload</varname>, in which case
+        the service main process is signaled after all specified command lines finish execution. Specially,
+        if <literal>RELOADING=1</literal> notification is received before <varname>ExecReload=</varname>
+        completes, the signaling is skipped and the service manager immediately starts listening for
+        <literal>READY=1</literal>.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>ExecReloadPost=</varname></term>
+
+        <listitem><para>Commands to execute after a successful reload operation. Syntax for this setting
+        is exactly the same as <varname>ExecReload=</varname>.</para>
+
+        <xi:include href="version-info.xml" xpointer="v259"/>
         </listitem>
       </varlistentry>
 
 
       <varlistentry>
         <term><varname>RootDirectoryStartOnly=</varname></term>
-        <listitem><para>Takes a boolean argument. If true, the root
-        directory, as configured with the
+        <listitem><para>Takes a boolean argument. If true, the root directory, as configured with the
         <varname>RootDirectory=</varname> option (see
         <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
-        for more information), is only applied to the process started
-        with <varname>ExecStart=</varname>, and not to the various
-        other <varname>ExecStartPre=</varname>,
-        <varname>ExecStartPost=</varname>,
-        <varname>ExecReload=</varname>, <varname>ExecStop=</varname>,
-        and <varname>ExecStopPost=</varname> commands. If false, the
-        setting is applied to all configured commands the same way.
-        Defaults to false.</para></listitem>
+        for more information), is only applied to the process started with <varname>ExecStart=</varname>,
+        and not to the various other <varname>ExecStartPre=</varname>, <varname>ExecStartPost=</varname>,
+        <varname>ExecReload=</varname>, <varname>ExecReloadPost=</varname>, <varname>ExecStop=</varname>,
+        and <varname>ExecStopPost=</varname> commands. If false, the setting is applied to all
+        configured commands the same way. Defaults to false.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index a2b2f9d6f05decb9b0fb886850d6e0a0cb97fe39..9179897f3cceabd051dd3634d993becb2b378c6f 100644 (file)
@@ -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",
index f42198e08e62560bb8c8728e3f0bc25c0fc5bf1b..37539ef0189cd49b8e8411a70e0a51918c6a09db 100644 (file)
@@ -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,
index 2ff6272bd4cb6a05aedaee42287611294516cda3..aa6a587c16d6373d70c95503092b26bbea1ebcd2 100644 (file)
@@ -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),
index 5b83c95e4c9daed2f6d137e3e343b28aabf500c3..95ef508105152d0db83a0495ca159f40083f0678 100644 (file)
@@ -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)
index 8b2158c6775d7e3ee653f3b74bba059413f5c74f..5c1e6189f588248654626192cd47094b6c1e84b7 100644 (file)
@@ -68,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,
@@ -100,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,
@@ -136,7 +138,7 @@ 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);
@@ -146,7 +148,7 @@ 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,
@@ -157,7 +159,7 @@ 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_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
                       SERVICE_MOUNTING);
 }
 
@@ -1302,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,
@@ -1372,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);
 
@@ -2716,29 +2719,16 @@ 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);
 
-        usec_t ts = now(CLOCK_MONOTONIC);
-
-        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;
-        }
-
-        s->control_command = s->exec_command[SERVICE_EXEC_RELOAD];
+        s->control_command = s->exec_command[SERVICE_EXEC_RELOAD_POST];
         if (s->control_command) {
-                s->control_command_id = SERVICE_EXEC_RELOAD;
+                s->control_command_id = SERVICE_EXEC_RELOAD_POST;
 
                 r = service_spawn(s,
                                   s->control_command,
@@ -2746,31 +2736,88 @@ static void service_enter_reload_signal_exec(Service *s) {
                                   s->timeout_start_usec,
                                   &s->control_pid);
                 if (r < 0) {
-                        log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'reload' task: %m");
-                        goto fail;
+                        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);
-        } else if (killed) {
+                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;
                 }
 
+                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;
+                }
+
                 service_set_state(s, SERVICE_RELOAD_SIGNAL);
         } else
-                return service_reload_finish(s, SERVICE_SUCCESS);
+                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 = ts;
-        return;
+        s->reload_begin_usec = now(CLOCK_MONOTONIC);
 
-fail:
-        service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
+        s->control_command = s->exec_command[SERVICE_EXEC_RELOAD];
+        if (s->control_command) {
+                s->control_command_id = SERVICE_EXEC_RELOAD;
+
+                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' task: %m");
+                        return service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
+                }
+
+                service_set_state(s, SERVICE_RELOAD);
+        } else
+                service_enter_reload_signal(s);
 }
 
 static bool service_should_reload_extensions(Service *s) {
@@ -2807,9 +2854,9 @@ 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 service_enter_reload_signal_exec(s);
+                return service_enter_reload(s);
 
         service_unwatch_control_pid(s);
         s->control_command = NULL;
@@ -2891,7 +2938,11 @@ 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;
@@ -2908,7 +2959,7 @@ 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)
+                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);
@@ -3076,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);
@@ -4128,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
@@ -4247,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 &&
@@ -4334,30 +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);
-
-                                /* 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) {
-                                        s->reload_result = f;
-                                        service_set_state(s, SERVICE_RELOAD_NOTIFY);
-                                } else
+                                        /* Remounting extensions asynchronously done, proceed to reload */
+                                        service_enter_reload(s);
+                                else
                                         service_reload_finish(s, f);
                                 break;
 
-                        case SERVICE_REFRESH_EXTENSIONS:
-                                if (f == SERVICE_SUCCESS)
-                                        /* Remounting extensions asynchronously done, proceed to signal */
-                                        service_enter_reload_signal_exec(s);
-                                else
+                        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:
@@ -4455,10 +4507,11 @@ 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);
                 service_reload_finish(s, SERVICE_FAILURE_TIMEOUT);
@@ -4790,7 +4843,10 @@ static void service_notify_message_process_state(Service *s, char * const *tags)
 
         if (strv_contains(tags, "READY=1")) {
 
-                s->notify_state = NOTIFY_READY;
+                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. */
@@ -4799,7 +4855,7 @@ static void service_notify_message_process_state(Service *s, char * const *tags)
                             monotonic_usec != USEC_INFINITY &&
                             monotonic_usec >= s->reload_begin_usec)
                                 /* Valid Type=notify-reload protocol? Then we're all good. */
-                                service_reload_finish(s, SERVICE_SUCCESS);
+                                service_enter_reload_post(s);
 
                         else if (s->state == SERVICE_RUNNING) {
                                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -4820,7 +4876,7 @@ static void service_notify_message_process_state(Service *s, char * const *tags)
 
                 /* Sending READY=1 while we are reloading informs us that the reloading is complete. */
                 if (s->state == SERVICE_RELOAD_NOTIFY)
-                        service_reload_finish(s, SERVICE_SUCCESS);
+                        service_enter_reload_post(s);
 
         } else if (strv_contains(tags, "RELOADING=1")) {
 
@@ -4872,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))) {
 
@@ -5140,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);
 }
 
@@ -5325,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,
@@ -5761,33 +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_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);
index e27b382e8020aeb7c175aac4ad6ff856cb2a2afd..06fafb54822cb21d0bc828636cb2e54d1cb8b847 100644 (file)
@@ -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,
@@ -55,6 +56,7 @@ typedef enum ServiceExecCommand {
 typedef enum NotifyState {
         NOTIFY_READY,
         NOTIFY_RELOADING,
+        NOTIFY_RELOAD_READY,
         NOTIFY_STOPPING,
         _NOTIFY_STATE_MAX,
         _NOTIFY_STATE_INVALID = -EINVAL,
index 30a029716dcd50b9af1f2229739f35c4936101ff..3d49cf5415b5d21c8e58627a100e75c40e378d1e 100644 (file)
@@ -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                       },
index 79e1908a236e09101dd7fc8e4afe35a95a4577aa..f1c1ab0eff01a43e1d5098f1bab71ae53818d030 100644 (file)
@@ -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                                                           },