From 308d72dc1e2106f94ae637e2ea510e8d466d2af1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Aug 2014 17:03:15 +0200 Subject: [PATCH] core: allow informing systemd about service status changes with RELOADING=1 and STOPPING=1 sd_notify() messages --- man/sd_notify.xml | 93 +++++++++++++++++++++++------------- src/core/service.c | 105 ++++++++++++++++++++++++++++++++++++----- src/core/service.h | 13 +++++ src/test/test-daemon.c | 22 +++++++-- 4 files changed, 185 insertions(+), 48 deletions(-) diff --git a/man/sd_notify.xml b/man/sd_notify.xml index 6bf823076..fbb882dfd 100644 --- a/man/sd_notify.xml +++ b/man/sd_notify.xml @@ -46,7 +46,7 @@ sd_notify sd_notifyf - Notify service manager about start-up completion and other daemon status changes + Notify service manager about start-up completion and other service status changes @@ -70,12 +70,12 @@ Description - sd_notify() shall be called - by a daemon to notify the init system about status - changes. It can be used to send arbitrary information, - encoded in an environment-block-like string. Most - importantly it can be used for start-up completion - notification. + sd_notify() may be called + by a service to notify the service manager about + state changes. It can be used to send arbitrary + information, encoded in an environment-block-like + string. Most importantly it can be used for start-up + completion notification. If the unset_environment parameter is non-zero, sd_notify() @@ -99,58 +99,87 @@ READY=1 - Tells the init system - that daemon startup is finished. This - is only used by systemd if the service - definition file has Type=notify - set. The passed argument is a boolean - "1" or "0". Since there is little + Tells the service + manager that service startup is + finished. This is only used by systemd + if the service definition file has + Type=notify set. Since there is little value in signaling non-readiness, the - only value daemons should send is - "READY=1". + only value services should send is + READY=1 + (i.e. READY=0 is + not defined). + + + + RELOADING=1 + + Tells the service manager + that the service is reloading its + configuration. This is useful to allow + the service manager to track the service's + internal state, and present it to the + user. Note that a service that sends + this notification must also send a + READY=1 + notification when it completed + reloading its + configuration. + + + + STOPPING=1 + + Tells the service manager + that the service is beginning its + shutdown. This is useful to allow the + service manager to track the service's + internal state, and present it to the + user. STATUS=... Passes a single-line - status string back to the init system - that describes the daemon state. This + UTF-8 status string back to the service manager + that describes the service state. This is free-form and can be used for various purposes: general state feedback, fsck-like programs could pass completion percentages and failing programs could pass a human readable error message. Example: - "STATUS=Completed 66% of file system - check..." + STATUS=Completed 66% of file + system + check... ERRNO=... - If a daemon fails, the + If a service fails, the errno-style error code, formatted as - string. Example: "ERRNO=2" for + string. Example: ERRNO=2 for ENOENT. BUSERROR=... - If a daemon fails, the + If a service fails, the D-Bus error-style error code. Example: - "BUSERROR=org.freedesktop.DBus.Error.TimedOut" + BUSERROR=org.freedesktop.DBus.Error.TimedOut MAINPID=... The main pid of the - daemon, in case the init system did + service, in case the service manager did not fork off the process itself. Example: - "MAINPID=4711" + MAINPID=4711 @@ -183,7 +212,7 @@ clashes. Note that systemd will accept status data sent - from a daemon only if the + from a service only if the NotifyAccess= option is correctly set in the service definition file. See systemd.service5 @@ -222,7 +251,7 @@ $NOTIFY_SOCKET is @, the string is understood as Linux abstract namespace socket. The datagram is accompanied by the process credentials of - the sending daemon, using SCM_CREDENTIALS. + the sending service, using SCM_CREDENTIALS. @@ -232,7 +261,7 @@ $NOTIFY_SOCKET - Set by the init system + Set by the service manager for supervised processes for status and start-up completion notification. This environment variable @@ -249,9 +278,9 @@ Start-up Notification - When a daemon finished starting up, it + When a service finished starting up, it might issue the following call to notify - the init system: + the service manager: sd_notify(0, "READY=1"); @@ -259,7 +288,7 @@ Extended Start-up Notification - A daemon could send the following after + A service could send the following after completing initialization: sd_notifyf(0, "READY=1\n" @@ -271,7 +300,7 @@ Error Cause Notification - A daemon could send the following shortly before exiting, on failure + A service could send the following shortly before exiting, on failure sd_notifyf(0, "STATUS=Failed to start up: %s\n" "ERRNO=%i", diff --git a/src/core/service.c b/src/core/service.c index 262a40cc8..322193879 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -92,6 +92,7 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata); static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); +static void service_enter_reload_by_notify(Service *s); static void service_init(Unit *u) { Service *s = SERVICE(u); @@ -473,7 +474,8 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { "%sGuessMainPID: %s\n" "%sType: %s\n" "%sRestart: %s\n" - "%sNotifyAccess: %s\n", + "%sNotifyAccess: %s\n" + "%sNotifyState: %s\n", prefix, service_state_to_string(s->state), prefix, service_result_to_string(s->result), prefix, service_result_to_string(s->reload_result), @@ -483,7 +485,8 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { prefix, yes_no(s->guess_main_pid), prefix, service_type_to_string(s->type), prefix, service_restart_to_string(s->restart), - prefix, notify_access_to_string(s->notify_access)); + prefix, notify_access_to_string(s->notify_access), + prefix, notify_state_to_string(s->notify_state)); if (s->control_pid > 0) fprintf(f, @@ -1176,6 +1179,17 @@ fail: service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); } +static void service_enter_stop_by_notify(Service *s) { + assert(s); + + unit_watch_all_pids(UNIT(s)); + + if (s->timeout_stop_usec > 0) + service_arm_timer(s, s->timeout_stop_usec); + + service_set_state(s, SERVICE_STOP); +} + static void service_enter_stop(Service *s, ServiceResult f) { int r; @@ -1226,9 +1240,18 @@ static void service_enter_running(Service *s, ServiceResult f) { cgroup_ok = cgroup_good(s); if ((main_pid_ok > 0 || (main_pid_ok < 0 && cgroup_ok != 0)) && - (s->bus_name_good || s->type != SERVICE_DBUS)) - service_set_state(s, SERVICE_RUNNING); - else if (s->remain_after_exit) + (s->bus_name_good || s->type != SERVICE_DBUS)) { + + /* If there are any queued up sd_notify() + * notifications, process them now */ + if (s->notify_state == NOTIFY_RELOADING) + service_enter_reload_by_notify(s); + else if (s->notify_state == NOTIFY_STOPPING) + service_enter_stop_by_notify(s); + else + service_set_state(s, SERVICE_RUNNING); + + } else if (s->remain_after_exit) service_set_state(s, SERVICE_EXITED); else service_enter_stop(s, SERVICE_SUCCESS); @@ -1433,12 +1456,19 @@ static void service_enter_restart(Service *s) { return; fail: - log_warning_unit(UNIT(s)->id, - "%s failed to schedule restart job: %s", - UNIT(s)->id, bus_error_message(&error, -r)); + log_warning_unit(UNIT(s)->id, "%s failed to schedule restart job: %s", UNIT(s)->id, bus_error_message(&error, -r)); service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); } +static void service_enter_reload_by_notify(Service *s) { + assert(s); + + if (s->timeout_start_usec > 0) + service_arm_timer(s, s->timeout_start_usec); + + service_set_state(s, SERVICE_RELOAD); +} + static void service_enter_reload(Service *s) { int r; @@ -1667,6 +1697,8 @@ static int service_start(Unit *u) { s->status_text = NULL; s->status_errno = 0; + s->notify_state = NOTIFY_UNKNOWN; + service_enter_start_pre(s); return 0; } @@ -2504,13 +2536,15 @@ static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void static void service_notify_message(Unit *u, pid_t pid, char **tags) { Service *s = SERVICE(u); - const char *e; + _cleanup_free_ char *cc = NULL; bool notify_dbus = false; + const char *e; assert(u); - log_debug_unit(u->id, "%s: Got notification message from PID "PID_FMT" (%s...)", - u->id, pid, tags && *tags ? tags[0] : "(empty)"); + cc = strv_join(tags, ", "); + log_debug_unit(u->id, "%s: Got notification message from PID "PID_FMT" (%s)", + u->id, pid, isempty(cc) ? "n/a" : cc); if (s->notify_access == NOTIFY_NONE) { log_warning_unit(u->id, "%s: Got notification message from PID "PID_FMT", but reception is disabled.", u->id, pid); @@ -2539,10 +2573,46 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags) { } } + /* Interpret RELOADING= */ + if (strv_find(tags, "RELOADING=1")) { + + log_debug_unit(u->id, "%s: got RELOADING=1", u->id); + s->notify_state = NOTIFY_RELOADING; + + if (s->state == SERVICE_RUNNING) + service_enter_reload_by_notify(s); + + notify_dbus = true; + } + /* Interpret READY= */ - if (s->type == SERVICE_NOTIFY && s->state == SERVICE_START && strv_find(tags, "READY=1")) { + if (strv_find(tags, "READY=1")) { + log_debug_unit(u->id, "%s: got READY=1", u->id); - service_enter_start_post(s); + s->notify_state = NOTIFY_READY; + + /* Type=notify services inform us about completed + * initialization with READY=1 */ + if (s->type == SERVICE_NOTIFY && 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 && s->control_pid == 0) + service_enter_running(s, SERVICE_SUCCESS); + + notify_dbus = true; + } + + /* Interpret STOPPING= */ + if (strv_find(tags, "STOPPING=1")) { + + log_debug_unit(u->id, "%s: got STOPPING=1", u->id); + s->notify_state = NOTIFY_STOPPING; + + if (s->state == SERVICE_RUNNING) + service_enter_stop_by_notify(s); + notify_dbus = true; } @@ -2798,6 +2868,15 @@ static const char* const notify_access_table[_NOTIFY_ACCESS_MAX] = { DEFINE_STRING_TABLE_LOOKUP(notify_access, NotifyAccess); +static const char* const notify_state_table[_NOTIFY_STATE_MAX] = { + [NOTIFY_UNKNOWN] = "unknown", + [NOTIFY_READY] = "ready", + [NOTIFY_RELOADING] = "reloading", + [NOTIFY_STOPPING] = "stopping", +}; + +DEFINE_STRING_TABLE_LOOKUP(notify_state, NotifyState); + static const char* const service_result_table[_SERVICE_RESULT_MAX] = { [SERVICE_SUCCESS] = "success", [SERVICE_FAILURE_RESOURCES] = "resources", diff --git a/src/core/service.h b/src/core/service.h index 686cf4b0b..0227321d9 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -91,6 +91,15 @@ typedef enum NotifyAccess { _NOTIFY_ACCESS_INVALID = -1 } NotifyAccess; +typedef enum NotifyState { + NOTIFY_UNKNOWN, + NOTIFY_READY, + NOTIFY_RELOADING, + NOTIFY_STOPPING, + _NOTIFY_STATE_MAX, + _NOTIFY_STATE_INVALID = -1 +} NotifyState; + typedef enum ServiceResult { SERVICE_SUCCESS, SERVICE_FAILURE_RESOURCES, @@ -196,6 +205,7 @@ struct Service { PathSpec *pid_file_pathspec; NotifyAccess notify_access; + NotifyState notify_state; }; extern const UnitVTable service_vtable; @@ -219,6 +229,9 @@ ServiceExecCommand service_exec_command_from_string(const char *s) _pure_; const char* notify_access_to_string(NotifyAccess i) _const_; NotifyAccess notify_access_from_string(const char *s) _pure_; +const char* notify_state_to_string(NotifyState i) _const_; +NotifyState notify_state_from_string(const char *s) _pure_; + const char* service_result_to_string(ServiceResult i) _const_; ServiceResult service_result_from_string(const char *s) _pure_; diff --git a/src/test/test-daemon.c b/src/test/test-daemon.c index bcc049b32..7e0ac754d 100644 --- a/src/test/test-daemon.c +++ b/src/test/test-daemon.c @@ -25,13 +25,29 @@ int main(int argc, char*argv[]) { - sd_notify(0, "STATUS=Starting up"); + sd_notify(0, + "STATUS=Starting up"); + sleep(5); + + sd_notify(0, + "STATUS=Running\n" + "READY=1"); + sleep(5); + + sd_notify(0, + "STATUS=Reloading\n" + "RELOADING=1"); sleep(5); + sd_notify(0, "STATUS=Running\n" "READY=1"); - sleep(10); - sd_notify(0, "STATUS=Quitting"); + sleep(5); + + sd_notify(0, + "STATUS=Quitting\n" + "STOPPING=1"); + sleep(5); return 0; } -- 2.30.2