guix-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[shepherd] 01/01: service: Restart dependent services on service restart


From: Carlo Zancanaro
Subject: [shepherd] 01/01: service: Restart dependent services on service restart
Date: Sun, 26 Aug 2018 17:27:47 -0400 (EDT)

czan pushed a commit to branch master
in repository shepherd.

commit 0c4d8ec40bc97481eec6dab2b53bb4c33e897862
Author: Carlo Zancanaro <address@hidden>
Date:   Sat Aug 25 20:32:11 2018 +1000

    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.
    * doc/shepherd.texi (Jump Start): Clarify details about new behaviour of
    restart.
    * tests/restart.sh: New file.
    * Makefile.am (TESTS): Add tests/restart.sh.
---
 Makefile.am                  |  1 +
 doc/shepherd.texi            | 12 +++---
 modules/shepherd/service.scm | 90 +++++++++++++++++++++++++++-----------------
 tests/restart.sh             | 77 +++++++++++++++++++++++++++++++++++++
 4 files changed, 139 insertions(+), 41 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 4322d7f..d9e21e9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -187,6 +187,7 @@ TESTS =                                             \
   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/doc/shepherd.texi b/doc/shepherd.texi
index 1de6d80..f0498d6 100644
--- a/doc/shepherd.texi
+++ b/doc/shepherd.texi
@@ -267,12 +267,12 @@ this inconsistency is just to make it more intuitive to 
get
 information about the status of a service, see below).
 
 These actions are @code{restart} and @code{status}.  The default
-implementation of @code{restart} calls @code{stop} and @code{start} on
-the affected service in order, the @code{status} action displays some
-general information about the service, like what it provides, what it
-depends on and with which other services it conflicts (because they
-provide a virtual service that is also provided by that particular
-service).
+implementation of @code{restart} calls @code{stop} and @code{start} on the
+affected service, taking care to also restart any dependent services. The
+default implementation of @code{status} displays some general information
+about the service, like what it provides, what it depends on and with which
+other services it conflicts (because they provide a virtual service that is
+also provided by that particular service).
 
 Another special action is @code{list-actions}, which displays a list
 of the additional actions a service provides; obviously, it can also
diff --git a/modules/shepherd/service.scm b/modules/shepherd/service.scm
index 24c9224..be86621 100644
--- 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)))
 
+(define (required-by? service dependent)
+  "Returns #t if DEPENDENT directly requires SERVICE in order to run.  Returns
+#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.
-(define-method (stop (obj <service>) . args)
+(define-method (stop (service <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 SERVICE
+is not already running, and will return SERVICE's canonical name in a list."
   ;; Block asyncs so the SIGCHLD handler doesn't execute concurrently.
-  ;; Notably, that makes sure the handler processes the SIGCHLD for OBJ's
-  ;; process once we're done; otherwise, it could end up respawning OBJ.
+  ;; Notably, that makes sure the handler processes the SIGCHLD for SERVICE's
+  ;; process once we're done; otherwise, it could end up respawning SERVICE.
   (call-with-blocked-asyncs
    (lambda ()
-     (if (not (running? obj))
-         (local-output "Service ~a is not running." (canonical-name obj))
-         (if (slot-ref obj 'stop-delay?)
+     (if (not (running? service))
+         (begin
+           (local-output "Service ~a is not running." (canonical-name service))
+           (list (canonical-name service)))
+         (if (slot-ref service 'stop-delay?)
              (begin
-               (slot-set! obj 'waiting-for-termination? #t)
+               (slot-set! service 'waiting-for-termination? #t)
                (local-output "Service ~a pending to be stopped."
-                             (canonical-name obj)))
-             (begin
-               ;; Stop services that depend on it.
-               (for-each-service
-                (lambda (serv)
-                  (and (running? serv)
-                       (for-each (lambda (sym)
-                                   (and (memq sym (provided-by obj))
-                                        (stop serv)))
-                                 (required-by serv)))))
-
+                             (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 ()
-                   (apply (slot-ref obj 'stop)
-                          (slot-ref obj 'running)
+                   (apply (slot-ref service 'stop)
+                          (slot-ref service 'running)
                           args))
                  (lambda (key . args)
                    ;; Special case: 'root' may quit.
-                   (and (eq? root-service obj)
+                   (and (eq? root-service service)
                         (eq? key 'quit)
                         (apply quit args))
                    (caught-error key args)))
 
-               ;; OBJ is no longer running.
-               (slot-set! obj 'running #f)
+               ;; SERVICE is no longer running.
+               (slot-set! service 'running #f)
 
                ;; Reset the list of respawns.
-               (slot-set! obj 'last-respawns '())
+               (slot-set! service 'last-respawns '())
 
                ;; Replace the service with its replacement, if it has one
-               (let ((replacement (slot-ref obj 'replacement)))
+               (let ((replacement (slot-ref service 'replacement)))
                  (when replacement
-                   (replace-service obj replacement)))
+                   (replace-service service replacement)))
 
                ;; Status message.
-               (let ((name (canonical-name obj)))
-                 (if (running? obj)
-                     (local-output "Service ~a could not be stopped." name)
-                     (local-output "Service ~a has been stopped." name))))))
-     (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)))))))
 
 ;; Call action THE-ACTION with ARGS.
 (define-method (action (obj <service>) the-action . args)
@@ -423,10 +434,9 @@ NEW-SERVICE."
       ;; Restarting is done in the obvious way.
       ((restart)
        (lambda (running . args)
-         (if running
-             (stop obj)
-             (local-output "~a was not running." (canonical-name obj)))
-         (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.
@@ -960,6 +970,16 @@ Return #f if service is not found."
           (eq? name (canonical-name service)))
         services))
 
+(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
--- /dev/null
+++ b/tests/restart.sh
@@ -0,0 +1,77 @@
+# GNU Shepherd --- Test restarting services.
+# Copyright © 2013, 2014, 2016 Ludovic Courtès <address@hidden>
+# Copyright © 2018 Carlo Zancanaro <address@hidden>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+shepherd --version
+herd --version
+
+socket="t-socket-$$"
+conf="t-conf-$$"
+log="t-log-$$"
+pid="t-pid-$$"
+
+herd="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"<<EOF
+(register-services
+ (make <service>
+   #:provides '(test1)
+   #:start (const #t)
+   #:stop  (const #t))
+ (make <service>
+   #:provides '(test2)
+   #:requires '(test1)
+   #:start (const #t)
+   #:stop  (const #t))
+ (make <service>
+   #:provides '(test3)
+   #:requires '(test2)
+   #:start (const #t)
+   #:stop  (const #t)))
+EOF
+
+rm -f "$pid"
+shepherd -I -s "$socket" -c "$conf" -l "$log" --pid="$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. that
+# 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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]