guix-commits
[Top][All Lists]
Advanced

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

[shepherd] 01/02: service: One-shot services are started only once by 's


From: Ludovic Courtès
Subject: [shepherd] 01/02: service: One-shot services are started only once by 'start-in-parallel'.
Date: Sun, 19 Mar 2023 17:27:49 -0400 (EDT)

civodul pushed a commit to branch master
in repository shepherd.

commit f4c48666d41040fef8c1e9ee6c474f45139017a2
Author: Ludovic Courtès <ludo@gnu.org>
AuthorDate: Sun Mar 19 21:59:16 2023 +0100

    service: One-shot services are started only once by 'start-in-parallel'.
    
    Previously, since commit c195b87c275f39391c90a75a8843d4f6aedeb9c2, the
    same one-shot service could be started several times if several services
    depend on it.  This change ensures each one-shot service is started only
    once.
    
    * modules/shepherd/service.scm (%one-shot-services-started): New variable.
    (start-in-parallel): Parameterize it.  Call 'lookup-services' on
    elements of SERVICES that are symbols.  When starting a one-shot
    service, call 'start' if and only if it's not already in
    %ONE-SHOT-SERVICES-STARTED.
    * tests/one-shot.sh: Add test.
---
 modules/shepherd/service.scm | 71 +++++++++++++++++++++++++++++---------------
 tests/one-shot.sh            | 35 ++++++++++++++++++++--
 2 files changed, 80 insertions(+), 26 deletions(-)

diff --git a/modules/shepherd/service.scm b/modules/shepherd/service.scm
index c5b1899..1c837de 100644
--- a/modules/shepherd/service.scm
+++ b/modules/shepherd/service.scm
@@ -612,35 +612,58 @@ channel and wait for its reply."
   (disable-service obj)
   (local-output (l10n "Disabled service ~a.") (canonical-name obj)))
 
+(define %one-shot-services-started
+  ;; Bookkeeping of one-shot services already started.
+  (make-parameter #f))                            ;#f | hash table
+
 (define (start-in-parallel services)
   "Start @var{services} in parallel--i.e., without waiting for each one to be
 started before starting the next one.  Return the subset of @var{services}
 that could not be started."
-  (let ((channel (make-channel)))
-    (for-each (lambda (service)
-                (spawn-fiber
-                 (lambda ()
-                   (let ((value
-                          (guard (c ((action-runtime-error? c)
-                                     (local-output
-                                      (l10n "Exception caught \
+  ;; Use the hash table in %ONE-SHOT-SERVICES-STARTED to keep track of
+  ;; one-shot services that have been started directly or indirectly by this
+  ;; call.  That way, if several services depend on the same one-shot service,
+  ;; its 'start' method is invoked only once.
+  (parameterize ((%one-shot-services-started
+                  (or (%one-shot-services-started)
+                      (make-hash-table))))
+    (let ((services (append-map (lambda (service)
+                                  (if (symbol? service)
+                                      (lookup-services service)
+                                      (list service)))
+                                services))
+          (channel  (make-channel)))
+      (for-each (lambda (service)
+                  (spawn-fiber
+                   (lambda ()
+                     (let ((value
+                            (guard (c ((action-runtime-error? c)
+                                       (local-output
+                                        (l10n "Exception caught \
 while starting ~a: ~s")
-                                      service
-                                      (cons (action-runtime-error-key c)
-                                            (action-runtime-error-arguments 
c)))
-                                     #f))
-                            (start service))))
-                     (put-message channel (cons service value))))))
-              services)
-    (let loop ((i (length services))
-               (failures '()))
-      (if (> i 0)
-          (match (get-message channel)
-            ((service . #f)
-             (loop (- i 1) (cons service failures)))
-            ((_ . _)
-             (loop (- i 1) failures)))
-          failures))))
+                                        service
+                                        (cons (action-runtime-error-key c)
+                                              (action-runtime-error-arguments 
c)))
+                                       #f))
+                              (or (and (one-shot? service)
+                                       (hashq-ref (%one-shot-services-started)
+                                                  service))
+                                  (let ((result (start service)))
+                                    (when (one-shot? service)
+                                      (hashq-set! (%one-shot-services-started)
+                                                  service #t))
+                                    result)))))
+                       (put-message channel (cons service value))))))
+                services)
+      (let loop ((i (length services))
+                 (failures '()))
+        (if (> i 0)
+            (match (get-message channel)
+              ((service . #f)
+               (loop (- i 1) (cons service failures)))
+              ((_ . _)
+               (loop (- i 1) failures)))
+            failures)))))
 
 ;; Start the service, including dependencies.
 (define-method (start (obj <service>) . args)
diff --git a/tests/one-shot.sh b/tests/one-shot.sh
index 3d396e5..c595b38 100644
--- a/tests/one-shot.sh
+++ b/tests/one-shot.sh
@@ -1,5 +1,5 @@
 # GNU Shepherd --- Test one-shot services.
-# Copyright © 2019 Ludovic Courtès <ludo@gnu.org>
+# Copyright © 2019, 2023 Ludovic Courtès <ludo@gnu.org>
 #
 # This file is part of the GNU Shepherd.
 #
@@ -54,7 +54,25 @@ cat > "$conf"<<EOF
              #t)
    #:stop  (lambda _
              (delete-file "$stamp-2")
-             #f)))
+             #f))
+
+ ;; Several services depending on the same one-shot service.
+ (make <service>
+   #:provides '(one-shotty)
+   #:start (const #t)
+   #:one-shot? #t)
+ (make <service>
+   #:provides '(a)
+   #:requires '(one-shotty)
+   #:start (const #t))
+ (make <service>
+   #:provides '(b)
+   #:requires '(a one-shotty)
+   #:start (const #t))
+ (make <service>
+   #:provides '(c)
+   #:requires '(a b one-shotty)
+   #:start (const #t)))
 EOF
 
 rm -f "$pid"
@@ -92,3 +110,16 @@ $herd status test | grep stopped.*one-shot
 $herd status test-2 | grep started
 $herd stop test-2
 if test -f "$stamp-2"; then false; else true; fi
+
+# In the course of starting C, ONE-SHOTTY should be started only once.
+$herd start c
+test $(grep "Starting service one-shotty" "$log" | wc -l) -eq 1
+
+# But we can still start it a second time, indirectly...
+$herd stop a
+$herd start c
+test $(grep "Starting service one-shotty" "$log" | wc -l) -eq 2
+
+# ... and a third time, directly.
+$herd start one-shotty
+test $(grep "Starting service one-shotty" "$log" | wc -l) -eq 3



reply via email to

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