From mboxrd@z Thu Jan 1 00:00:00 1970 From: Carlo Zancanaro Subject: bug#23170: [PATCH shepherd] Restart dependent services on service restart Date: Sat, 25 Aug 2018 21:33:47 +1000 Message-ID: <874lfi65rv.fsf@zancanaro.id.au> References: Mime-Version: 1.0 Content-Type: multipart/signed; boundary="==-=-="; micalg=pgp-sha256; protocol="application/pgp-signature" Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:48868) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ftWqR-0002B2-6r for bug-guix@gnu.org; Sat, 25 Aug 2018 07:35:08 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ftWqM-0007q1-N1 for bug-guix@gnu.org; Sat, 25 Aug 2018 07:35:06 -0400 Received: from debbugs.gnu.org ([208.118.235.43]:55220) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1ftWqM-0007pk-Gb for bug-guix@gnu.org; Sat, 25 Aug 2018 07:35:02 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1ftWqM-0007Uv-6i for bug-guix@gnu.org; Sat, 25 Aug 2018 07:35:02 -0400 In-Reply-To: Sender: "Debbugs-submit" Resent-Message-ID: List-Id: Bug reports for GNU Guix List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-guix-bounces+gcggb-bug-guix=m.gmane.org@gnu.org Sender: "bug-Guix" To: 23170@debbugs.gnu.org --==-=-= Content-Type: multipart/mixed; boundary="=-=-=" --=-=-= Content-Type: text/plain; format=flowed I've written a patch to fix this. It's not super smart, but it should do the job. It currently targets the branch after my patch in #32408[1], but it's technically an independent change. [1]: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32408 --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: inline; filename=0001-service-Restart-dependent-services-on-service-restar.patch Content-Transfer-Encoding: quoted-printable From=2050dd3ef4888b04ea3b869da893b23ad69fad8971 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Sat, 25 Aug 2018 20:32:11 +1000 Subject: [PATCH] service: Restart dependent services on service restart * modules/shepherd/service.scm (required-by?): New procedure. (stop): Return a list of canonical-names for stopped dependent services, including transitive dependencies. (action)[restart]: Start services based on the return value of stop. (fold-services): New procedure. * tests/restart.sh: New file. * Makefile.am (TESTS): Add tests/restart.sh. =2D-- Makefile.am | 1 + modules/shepherd/service.scm | 90 ++++++++++++++++++++++-------------- tests/restart.sh | 77 ++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 35 deletions(-) create mode 100644 tests/restart.sh diff --git a/Makefile.am b/Makefile.am index 4322d7f..d9e21e9 100644 =2D-- a/Makefile.am +++ b/Makefile.am @@ -187,6 +187,7 @@ TESTS =3D \ tests/replacement.sh \ tests/respawn.sh \ tests/respawn-throttling.sh \ + tests/restart.sh \ tests/misbehaved-client.sh \ tests/no-home.sh \ tests/pid-file.sh \ diff --git a/modules/shepherd/service.scm b/modules/shepherd/service.scm index 006309c..510a5ea 100644 =2D-- a/modules/shepherd/service.scm +++ b/modules/shepherd/service.scm @@ -358,61 +358,72 @@ NEW-SERVICE." (for-each remove-service (provided-by old-service)) (register-services new-service))) =20 +(define (required-by? service dependent) + "Returns #t if DEPENDENT directly requires SERVICE in order to run. Ret= urns +#f otherwise." + (and (find (lambda (dependency) + (memq dependency (provided-by service))) + (required-by dependent)) + #t)) + ;; Stop the service, including services that depend on it. If the ;; latter fails, continue anyway. Return `#f' if it could be stopped. =2D(define-method (stop (obj ) . args) +(define-method (stop (service ) . args) + "Stop SERVICE, and any services which depend on it. Returns a list of +canonical names for all of the services which have been stopped (including +transitive dependent services). This method will print a warning if SERVI= CE +is not already running, and will return SERVICE's canonical name in a list= ." ;; Block asyncs so the SIGCHLD handler doesn't execute concurrently. =2D ;; Notably, that makes sure the handler processes the SIGCHLD for OBJ's =2D ;; process once we're done; otherwise, it could end up respawning OBJ. + ;; Notably, that makes sure the handler processes the SIGCHLD for SERVIC= E's + ;; process once we're done; otherwise, it could end up respawning SERVIC= E. (call-with-blocked-asyncs (lambda () =2D (if (not (running? obj)) =2D (local-output "Service ~a is not running." (canonical-name obj)) =2D (if (slot-ref obj 'stop-delay?) + (if (not (running? service)) + (begin + (local-output "Service ~a is not running." (canonical-name serv= ice)) + (list (canonical-name service))) + (if (slot-ref service 'stop-delay?) (begin =2D (slot-set! obj 'waiting-for-termination? #t) + (slot-set! service 'waiting-for-termination? #t) (local-output "Service ~a pending to be stopped." =2D (canonical-name obj))) =2D (begin =2D ;; Stop services that depend on it. =2D (for-each-service =2D (lambda (serv) =2D (and (running? serv) =2D (for-each (lambda (sym) =2D (and (memq sym (provided-by obj)) =2D (stop serv))) =2D (required-by serv))))) =2D + (canonical-name service)) + (list (canonical-name service))) + (let ((name (canonical-name service)) + (stopped-dependents (fold-services (lambda (other acc) + (if (and (running?= other) + (required= -by? service other)) + (append (stop = other) acc) + acc)) + '()))) ;; Stop the service itself. (catch #t (lambda () =2D (apply (slot-ref obj 'stop) =2D (slot-ref obj 'running) + (apply (slot-ref service 'stop) + (slot-ref service 'running) args)) (lambda (key . args) ;; Special case: 'root' may quit. =2D (and (eq? root-service obj) + (and (eq? root-service service) (eq? key 'quit) (apply quit args)) (caught-error key args))) =20 =2D ;; OBJ is no longer running. =2D (slot-set! obj 'running #f) + ;; SERVICE is no longer running. + (slot-set! service 'running #f) =20 ;; Reset the list of respawns. =2D (slot-set! obj 'last-respawns '()) + (slot-set! service 'last-respawns '()) =20 ;; Replace the service with its replacement, if it has one =2D (let ((replacement (slot-ref obj 'replacement))) + (let ((replacement (slot-ref service 'replacement))) (when replacement =2D (replace-service obj replacement))) + (replace-service service replacement))) =20 ;; Status message. =2D (let ((name (canonical-name obj))) =2D (if (running? obj) =2D (local-output "Service ~a could not be stopped." na= me) =2D (local-output "Service ~a has been stopped." name))= )))) =2D (slot-ref obj 'running)))) + (if (running? service) + (local-output "Service ~a could not be stopped." name) + (local-output "Service ~a has been stopped." name)) + (cons name stopped-dependents))))))) =20 ;; Call action THE-ACTION with ARGS. (define-method (action (obj ) the-action . args) @@ -423,10 +434,9 @@ NEW-SERVICE." ;; Restarting is done in the obvious way. ((restart) (lambda (running . args) =2D (if running =2D (stop obj) =2D (local-output "~a was not running." (canonical-name obj))) =2D (apply start obj args))) + (let ((stopped-services (stop obj))) + (for-each start stopped-services) + #t))) ((status) ;; Return the service itself. It is automatically converted to an = sexp ;; via 'result->sexp' and sent to the client. @@ -953,6 +963,16 @@ Return #f if service is not found." (eq? name (canonical-name service))) services)) =20 +(define (fold-services proc init) + "Apply PROC to the registered services to build a result, and return that +result. Works in a manner akin to `fold' from SRFI-1." + (hash-fold (lambda (name services acc) + (let ((service (lookup-canonical-service name services))) + (if service + (proc service acc) + acc))) + init %services)) + (define (for-each-service proc) "Call PROC for each registered service." (hash-for-each (lambda (name services) diff --git a/tests/restart.sh b/tests/restart.sh new file mode 100644 index 0000000..92a1f79 =2D-- /dev/null +++ b/tests/restart.sh @@ -0,0 +1,77 @@ +# GNU Shepherd --- Test restarting services. +# Copyright =C2=A9 2013, 2014, 2016 Ludovic Court=C3=A8s +# Copyright =C2=A9 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=3D"t-socket-$$" +conf=3D"t-conf-$$" +log=3D"t-log-$$" +pid=3D"t-pid-$$" + +herd=3D"herd -s $socket" + +trap "cat $log || true ; + rm -f $socket $conf $log; + test -f $pid && kill \`cat $pid\` || true ; rm -f $pid" EXIT + +cat > "$conf"< + #:provides '(test1) + #:start (const #t) + #:stop (const #t)) + (make + #:provides '(test2) + #:requires '(test1) + #:start (const #t) + #:stop (const #t)) + (make + #:provides '(test3) + #:requires '(test2) + #:start (const #t) + #:stop (const #t))) +EOF + +rm -f "$pid" +shepherd -I -s "$socket" -c "$conf" -l "$log" --pid=3D"$pid" & + +while ! test -f "$pid" ; do sleep 0.3 ; done + +# Start some test services, and make sure they behave how we expect +$herd start test1 +$herd start test2 +$herd status test1 | grep started +$herd status test2 | grep started + +# Restart test1 and make sure that both services are still running (ie. th= at +# test2 hasn't been stopped) +$herd restart test1 +$herd status test1 | grep started +$herd status test2 | grep started + +# Now let's test with a transitive dependency +$herd start test3 +$herd status test3 | grep started + +# After restarting test1 we want test3 to still be running +$herd restart test1 +$herd status test1 | grep started +$herd status test2 | grep started +$herd status test3 | grep started =2D-=20 2.18.0 --=-=-=-- --==-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQIzBAEBCAAdFiEE1lpncq7JnOkt+LaeqdyPv9awIbwFAluBPpsACgkQqdyPv9aw IbyeQw/7B/16oHu0N3vvfGPx1TJWfByvfjnU9jlo8p0psZqR8/fbGpqax2+/VdEP zoL4XSUZMYTAeSOqFsFshHl9oyTnMInHN3+XyDCKRg8UiOj8FjQkb74ppxGdjTbj mosb2gMl5I5cIRauH3aAq64DTeZx9PZRzN0OXXAGK0B8RrKUVu8Z0AC9q9kMZ4V+ Jp5d0hOG20WwV66thte/ctzhC7xF2/WgRzG/fwxnF2B3UEL2VdKanBwNq8rLBcvV y4tW1OCWUCrdIZZWHxf6EoXckjiEgQBgUkmCOmqvur+AkD/eGtFzwMkrP0juJoMz NiMuOwhj+SYLrhgaHcnwWRnOxiap/jzY6MDoxhbw0WFtitCHhm5Mz9oFJdw6vJht fsrmcC2X4MfzLxRW4GwC3UqbGVKDC9SllFGRJVjwEQh0emShLXYGv88fVDGF3zXX nmuQe1RHJYruNVjbzqWA+3KyBSHBPild/47dLmiFc6e+1VMYBpTUKdExOMXikEAp 3uZSATATO0Ziqcw+jzROo3IHwqhz0QsBYYjFWgV/cDdeHJn0r7BAkqWIi2j7cNSQ Y2rQB41ZV7XlFMFg2RwSwXr/8hpMnrXsl0JlYKCaSwA+CMDSGBkDrYPU400Gn7c5 J1ghS+XPWYs/dVO4ssK9zpy4hl/c9dINjd3a/d6yBMFb9xspSD4= =z5ll -----END PGP SIGNATURE----- --==-=-=--