There is a lot of services with incorrectly implemented double-forking. Their original process exits before the child writes the PID file. With SysVinit it was rarely a problem, because the PID file was usually read only when the initscript was called again.
systemd wants to read the PID file after receiving SIGCHLD for the original process. With a buggy service it may be too early. systemd warns about the failure to read the PID file and falls back to guessing the main PID. Guessing often fails in a bad way for such double-forking services. systemd is likely to see the first forked child of the service and assume it is the main PID. This child will fork the second child and exit itself immediately. systemd will then kill the service. I see a few options: (1) Never guess, remove the code completely. Forking services with incorrect PID file handling would simply not have their main PID known and systemd would depend solely on the notification about an empty cgroup as the mechanism to detect the exit of such services. (2) Make the guessing opt-in. Make GuessMainPID=no the default for both native and SysV units. We could add support for a custom SysV header in the chkconfig section: # x-systemd-GuessMainPID: yes for the benefit of users who like the supervision provided by systemd, but are unable to fix their services properly for whatever reason. (On a tangent... It may be a good idea to allow opting in/out of main PID supervision in general, even when the PID is known reliably.) (3) Improve the guessing so that it is much more likely to give the right PID. This is what the patch implements. When no PIDFile= is declared or when the PID file is missing after the SIGCHLD, give the service some slack time to put things in order. Then retry the read of the PID file and if it still fails, do the guessing. I chose 1 second as the delay. It should be more than enough to finish the daemonization in the service. It is also on the order of magnitude that someone doing testing of SysV initscripts by starting and stopping would have used for sleep in between, thus masking the bug. We could actually add (2) in addition to this later. --- src/service.c | 38 +++++++++++++++++++++++++++----------- 1 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/service.c b/src/service.c index c2053ce..d2b168b 100644 --- a/src/service.c +++ b/src/service.c @@ -1318,7 +1318,8 @@ static int service_load_pid_file(Service *s, bool warn_if_missing) { (unsigned long) s->main_pid, (unsigned long) pid); service_unwatch_main_pid(s); s->main_pid_known = false; - } + } else + log_debug("Main PID loaded: %lu", (unsigned long) pid); if ((r = service_set_main_pid(s, pid)) < 0) return r; @@ -1349,6 +1350,8 @@ static int service_search_main_pid(Service *s) { if ((pid = cgroup_bonding_search_main_pid_list(s->meta.cgroup_bondings)) <= 0) return -ENOENT; + log_debug("Main PID guessed: %lu", (unsigned long) pid); + if ((r = service_set_main_pid(s, pid)) < 0) return r; @@ -1451,6 +1454,7 @@ static void service_set_state(Service *s, ServiceState state) { if (state != SERVICE_START_PRE && state != SERVICE_START && state != SERVICE_START_POST && + state != SERVICE_RUNNING && state != SERVICE_RELOAD && state != SERVICE_STOP && state != SERVICE_STOP_SIGTERM && @@ -2003,7 +2007,7 @@ fail: } static void service_enter_running(Service *s, bool success) { - int main_pid_ok, cgroup_ok; + int main_pid_ok, cgroup_ok, r; assert(s); if (!success) @@ -2012,13 +2016,24 @@ static void service_enter_running(Service *s, bool success) { main_pid_ok = main_pid_good(s); cgroup_ok = cgroup_good(s); + unit_unwatch_timer(UNIT(s), &s->timer_watch); + if ((main_pid_ok > 0 || (main_pid_ok < 0 && cgroup_ok != 0)) && - (s->bus_name_good || s->type != SERVICE_DBUS)) + (s->bus_name_good || s->type != SERVICE_DBUS)) { + if (!s->main_pid_known && s->guess_main_pid) { + log_debug("Setting up timer to guess main PID in 1 s."); + if ((r = unit_watch_timer(UNIT(s), 1*USEC_PER_SEC, &s->timer_watch)) < 0) + goto fail; + } service_set_state(s, SERVICE_RUNNING); - else if (s->remain_after_exit) + } else if (s->remain_after_exit) service_set_state(s, SERVICE_EXITED); else service_enter_stop(s, true); + return; +fail: + log_warning("%s failed to install guess PID timer: %s", s->meta.id, strerror(-r)); + service_enter_stop(s, false); } static void service_enter_start_post(Service *s) { @@ -2741,7 +2756,6 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { if (success) { service_load_pid_file(s, !s->exec_command[SERVICE_EXEC_START_POST]); - service_search_main_pid(s); service_enter_start_post(s); } else @@ -2750,20 +2764,16 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { break; case SERVICE_START_POST: - if (success) { + if (success) service_load_pid_file(s, true); - service_search_main_pid(s); - } s->reload_failure = !success; service_enter_running(s, true); break; case SERVICE_RELOAD: - if (success) { + if (success) service_load_pid_file(s, true); - service_search_main_pid(s); - } s->reload_failure = !success; service_enter_running(s, true); @@ -2820,6 +2830,12 @@ static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) { service_enter_stop(s, false); break; + case SERVICE_RUNNING: + log_debug("It is time to detect main PID of %s.", u->meta.id); + service_load_pid_file(s, true); + service_search_main_pid(s); + break; + case SERVICE_RELOAD: log_warning("%s operation timed out. Stopping.", u->meta.id); s->reload_failure = true; -- 1.7.4.4 _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel