From 5f26da2ce6a26c8412368900987ac5438f3e70cd Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Sat, 3 Mar 2018 17:26:05 +1100 Subject: [PATCH 1/2] Handle forked process SIGCHLD signals * Makefile.am (TESTS): Add tests/forking-service.sh. * configure.ac: Detect and substitute PR_SET_CHILD_SUBREAPER. * modules/shepherd.scm: Set the child subreaper attribute of main shepherd process (as long as we're not pid 1). * modules/shepherd/service.scm (root-service)[daemonize]: Set the child subreaper attribute of newly forked shepherd process. * modules/shepherd/system.scm.in (PR_SET_CHILD_SUBREAPER): Add new variable and export it. (prctl): Add new procedure and export it. --- Makefile.am | 1 + configure.ac | 4 ++ modules/shepherd.scm | 2 + modules/shepherd/service.scm | 4 +- modules/shepherd/system.scm.in | 17 ++++++- tests/forking-service.sh | 111 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 tests/forking-service.sh diff --git a/Makefile.am b/Makefile.am index eafa308..8dad006 100644 --- a/Makefile.am +++ b/Makefile.am @@ -190,6 +190,7 @@ TESTS = \ tests/no-home.sh \ tests/pid-file.sh \ tests/status-sexp.sh \ + tests/forking-service.sh \ tests/signals.sh TEST_EXTENSIONS = .sh diff --git a/configure.ac b/configure.ac index bb5058d..fbe16f4 100644 --- a/configure.ac +++ b/configure.ac @@ -72,7 +72,11 @@ esac AC_SUBST([RB_AUTOBOOT]) AC_SUBST([RB_HALT_SYSTEM]) AC_SUBST([RB_POWER_OFF]) +AC_MSG_RESULT([done]) +AC_MSG_CHECKING([ constants]) +AC_COMPUTE_INT([PR_SET_CHILD_SUBREAPER], [PR_SET_CHILD_SUBREAPER], [#include ]) +AC_SUBST([PR_SET_CHILD_SUBREAPER]) AC_MSG_RESULT([done]) dnl Manual pages. diff --git a/modules/shepherd.scm b/modules/shepherd.scm index df5420f..ab59e08 100644 --- a/modules/shepherd.scm +++ b/modules/shepherd.scm @@ -50,6 +50,8 @@ ;; Main program. (define (main . args) (initialize-cli) + (when (not (= 1 (getpid))) + (catch-system-error (prctl PR_SET_CHILD_SUBREAPER 1))) (let ((config-file #f) (socket-file default-socket-file) diff --git a/modules/shepherd/service.scm b/modules/shepherd/service.scm index 2224932..b6394f2 100644 --- a/modules/shepherd/service.scm +++ b/modules/shepherd/service.scm @@ -1274,7 +1274,9 @@ we want to receive these signals." (local-output "Running as PID 1, so not daemonizing.")) (else (if (zero? (primitive-fork)) - #t + (begin + (catch-system-error (prctl PR_SET_CHILD_SUBREAPER 1)) + #t) (primitive-exit 0)))))) (persistency "Safe the current state of running and non-running services. diff --git a/modules/shepherd/system.scm.in b/modules/shepherd/system.scm.in index a54dca7..55806cb 100644 --- a/modules/shepherd/system.scm.in +++ b/modules/shepherd/system.scm.in @@ -23,7 +23,9 @@ #:export (reboot halt power-off - max-file-descriptors)) + max-file-descriptors + prctl + PR_SET_CHILD_SUBREAPER)) ;; The constants. (define RB_AUTOBOOT @RB_AUTOBOOT@) @@ -130,6 +132,19 @@ the returned procedure is called." (list err)) result))))) +(define PR_SET_CHILD_SUBREAPER @PR_SET_CHILD_SUBREAPER@) + +(define prctl + (let ((proc (syscall->procedure long "prctl" (list int int)))) + (lambda (process operation) + "Perform an operation on the given process" + (let-values (((result err) (proc process operation))) + (if (= -1 result) + (throw 'system-error "prctl" "~A: ~S" + (list (strerror err) name) + (list err)) + result))))) + (define (max-file-descriptors) "Return the maximum number of open file descriptors allowed." (sysconf _SC_OPEN_MAX)) diff --git a/tests/forking-service.sh b/tests/forking-service.sh new file mode 100644 index 0000000..90c684a --- /dev/null +++ b/tests/forking-service.sh @@ -0,0 +1,111 @@ +# GNU Shepherd --- Test detecting a forked process' termination +# Copyright © 2016 Ludovic Courtès +# Copyright © 2018 Carlo Zancanaro +# +# This file is part of the GNU Shepherd. +# +# The GNU Shepherd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or (at +# your option) any later version. +# +# The GNU Shepherd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with the GNU Shepherd. If not, see . + +shepherd --version +herd --version + +socket="t-socket-$$" +conf="t-conf-$$" +log="t-log-$$" +pid="t-pid-$$" +service_pid="t-service-pid-$$" +service2_pid="t-service2-pid-$$" +service2_started="t-service2-starts-$$" + +herd="herd -s $socket" + +function cleanup() { + cat $log || true + rm -f $socket $conf $log $service2_started + test -f $pid && kill "$(cat $pid)" || true + rm -f $pid + test -f $service_pid && kill "$(cat $service_pid)" || true + rm -f $service_pid + test -f $service2_pid && kill "$(cat $service2_pid)" || true + rm -f $service2_pid +} + +trap cleanup EXIT + +cat > "$conf"< $PWD/$service_pid")) + +(register-services + (make + ;; A service that forks into a different process. + #:provides '(test) + #:start (make-forkexec-constructor %command + #:pid-file "$PWD/$service_pid") + #:stop (make-kill-destructor) + #:respawn? #f)) + +(define %command2 + '("$SHELL" "-c" "echo started >> $PWD/$service2_started; sleep 600 & echo \$! > $PWD/$service2_pid")) + +(register-services + (make + ;; A service that forks into a different process. + #:provides '(test2) + #:start (make-forkexec-constructor %command2 + #:pid-file "$PWD/$service2_pid") + #:stop (make-kill-destructor) + #:respawn? #t)) +EOF +cat $conf + +rm -f "$pid" +shepherd -I -s "$socket" -c "$conf" -l "$log" --pid="$pid" & + +# Wait till it's ready. +while ! test -f "$pid" ; do sleep 0.3 ; done + +shepherd_pid="$(cat $pid)" + +# start both of the services +$herd start test +$herd start test2 + +# make sure "test" is started +until $herd status test | grep started; do sleep 0.3; done +test -f "$service_pid" +service_pid_value="$(cat $service_pid)" +# now kill it +kill "$service_pid_value" +while kill -0 "$service_pid_value"; do sleep 0.3; done +# shepherd should notice that the service has stopped within one second +sleep 1 +$herd status test | grep stopped + + + +# make sure "test2" has started +until $herd status test2 | grep started; do sleep 0.3; done +test -f "$service2_pid" +service2_pid_value="$(cat $service2_pid)" +test "$(cat $PWD/$service2_started)" = "started" +# now kill it +rm -f "$service2_pid" +kill $service2_pid_value +while kill -0 "$service2_pid_value"; do sleep 0.3; done +# shepherd should notice that the service has stopped, and restart it, within one second +sleep 1; +$herd status test2 | grep started +test "$(cat $PWD/$service2_started)" = "started +started" -- 2.16.1