guix-commits
[Top][All Lists]
Advanced

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

[shepherd] 02/02: service: Provide a non-blocking replacement of 'system


From: Ludovic Courtès
Subject: [shepherd] 02/02: service: Provide a non-blocking replacement of 'system*'.
Date: Sun, 13 Nov 2022 16:41:57 -0500 (EST)

civodul pushed a commit to branch master
in repository shepherd.

commit 975b0aa0d6f0a184f712c935cd1ad284cd39deaf
Author: Ludovic Courtès <ludo@gnu.org>
AuthorDate: Sun Nov 13 22:34:26 2022 +0100

    service: Provide a non-blocking replacement of 'system*'.
    
    Fixes <https://issues.guix.gnu.org/56674>.
    
    * modules/shepherd/service.scm (process-monitor): Thread 'waiters'
    through 'loop'.  In 'handle-process-termination', notify the relevant
    subset of WAITERS.  Add 'spawn' operation.
    (spawn-command): New procedure.
    * modules/shepherd.scm (main): Set 'system*' to 'spawn-command'.
    * tests/system-star.sh: New file.
    * Makefile.am (TESTS): Add it.
---
 Makefile.am                  |  1 +
 modules/shepherd.scm         |  5 +++
 modules/shepherd/service.scm | 32 +++++++++++++++--
 tests/system-star.sh         | 85 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 121 insertions(+), 2 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 81fb04d..fe071fb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -246,6 +246,7 @@ TESTS =                                             \
   tests/inetd.sh                               \
   tests/systemd.sh                             \
   tests/signals.sh                             \
+  tests/system-star.sh                         \
   tests/close-on-exec.sh
 
 TEST_EXTENSIONS = .sh
diff --git a/modules/shepherd.scm b/modules/shepherd.scm
index 2fa7f31..dfa18f6 100644
--- a/modules/shepherd.scm
+++ b/modules/shepherd.scm
@@ -416,6 +416,11 @@ already ~a threads running, disabling 'signalfd' support")
          (catch 'quit
            (lambda ()
              (with-process-monitor
+
+               ;; Replace the default 'system*' binding with one that
+               ;; cooperates instead of blocking on 'waitpid'.
+               (set! system* spawn-command)
+
                (run-daemon #:socket-file socket-file
                            #:config-file config-file
                            #:pid-file pid-file
diff --git a/modules/shepherd/service.scm b/modules/shepherd/service.scm
index 144edef..441966f 100644
--- a/modules/shepherd/service.scm
+++ b/modules/shepherd/service.scm
@@ -38,6 +38,7 @@
   #:use-module ((ice-9 control) #:select (call/ec))
   #:use-module (ice-9 match)
   #:use-module (ice-9 format)
+  #:use-module (ice-9 vlist)
   #:autoload   (ice-9 ports internal) (port-read-wait-fd)
   #:autoload   (ice-9 pretty-print) (truncated-print)
   #:use-module (shepherd support)
@@ -77,6 +78,7 @@
             respawn-service
             handle-SIGCHLD
             with-process-monitor
+            spawn-command
             %precious-signals
             register-services
             provided-by
@@ -1796,7 +1798,7 @@ otherwise by updating its state."
 
 (define (process-monitor channel)
   "Run a process monitor that handles requests received over @var{channel}."
-  (let loop ()
+  (let loop ((waiters vlist-null))
     (match (get-message channel)
       (('handle-process-termination pid status)
        ;; Handle the termination of PID.
@@ -1812,7 +1814,23 @@ otherwise by updating its state."
           #f)
          ((? service? service)
           (handle-service-termination service status)))
-       (loop)))))
+
+       ;; Notify any waiters.
+       (match (vhash-assv pid waiters)
+         (#f #f)
+         ((_ . waiter)
+          (put-message waiter status)))
+
+       ;; XXX: The call below is linear in the size of WAITERS, but WAITERS is
+       ;; usually empty or small.
+       (loop (vhash-delv pid waiters)))
+
+      (('spawn command reply)
+       ;; Spawn COMMAND; send its exit status to REPLY when it terminates.
+       ;; This operation is atomic: the WAITERS table is updated before
+       ;; termination of PID can possibly be handled.
+       (let ((pid (fork+exec-command command)))
+         (loop (vhash-consv pid reply waiters)))))))
 
 (define (spawn-process-monitor)
   "Spawn a process monitoring fiber and return a channel to communicate with
@@ -1840,6 +1858,16 @@ context.  The process monitoring fiber is responsible 
for handling
 @code{SIGCHLD} and generally dealing with process creation and termination."
   (call-with-process-monitor (lambda () exp ...)))
 
+(define (spawn-command program . arguments)
+  "Like 'system*' but do not block while waiting for PROGRAM to terminate."
+  (if (current-process-monitor)
+      (let ((reply (make-channel)))
+        (put-message (current-process-monitor)
+                     `(spawn ,(cons program arguments)
+                             ,reply))
+        (get-message reply))
+      (apply system* program arguments)))
+
 (define (handle-service-termination service status)
   "Handle the termination of the process associated with @var{service}, whose
 PID is in its @code{running} slot; @var{status} is the process's exit status
diff --git a/tests/system-star.sh b/tests/system-star.sh
new file mode 100755
index 0000000..c3bb17a
--- /dev/null
+++ b/tests/system-star.sh
@@ -0,0 +1,85 @@
+# GNU Shepherd --- Test whether 'system*' is blocking.
+# Copyright © 2022 Ludovic Courtès <ludo@gnu.org>
+#
+# 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-$$"
+stamp="t-stamp-$$"
+pid="t-pid-$$"
+
+herd="herd -s $socket"
+
+trap "cat $log || true; rm -f $socket $conf $stamp $log;
+      test -f $pid && kill \`cat $pid\` || true; rm -f $pid" EXIT
+
+script="while [ ! -f $PWD/$stamp ] ; do sleep 0.1 ; done ; exit \$(cat 
$PWD/$stamp)"
+
+cat > "$conf" <<EOF
+(register-services
+ (make <service>
+   #:provides '(test)
+   #:start (lambda _
+             (list 'exit-code
+                   (status:exit-val
+                    (system* "$SHELL" "-c" "$script"))))
+   #:stop  (lambda _
+             (delete-file "$stamp"))
+   #:respawn? #f))
+EOF
+
+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`"
+
+kill -0 $shepherd_pid
+
+# 'herd start' will block until the script exits...
+$herd start test &
+
+# ... so at this point the service is stopped.
+$herd status test | grep "stopped"
+
+# Touch $stamp.  The shell script passed to 'system*' should complete shortly
+# after that.
+echo 123 > "$stamp"
+
+n=0
+while [ $n -lt 20 ]
+do
+    if $herd status test | grep "started"
+    then
+       break
+    else
+       n=$(expr $n + 1)
+       sleep 1
+    fi
+done
+$herd status test | grep "started"
+$herd status test | grep "exit-code 123"
+
+$herd stop test
+! test -f "$stamp"
+
+$herd stop root



reply via email to

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