Subject: bug#77274: [Shepherd] Competing one-shot service starter gets erroneous failure

From: Ludovic Courtès <[email-masked]>
Date: Wed, 26 Mar 2025 11:14:24 +0100

As of 1.0.3, when two clients start the same one-shot service, the one
that loses the race never sees the value that was produced by the
‘start’ method.

  herd start one-shot & herd start one-shot

Here one of the ‘herd start’ processes will wrongfully fail with “failed
to start service one-shot”.

Instead, it calls ‘service-running-value’ but that always returns #f
because the one-shot service was stopped in the meantime.  I’m referring
to this bit of ‘start-service’:

      (match (get-message reply)
        (#f
         ;; We lost the race: SERVICE is already running.
         (service-running-value service))   ;<- here
        …)

Attached is a reproducer.

Ludo’.

diff --git a/tests/one-shot.sh b/tests/one-shot.sh
index eeecea7..491eeae 100644
--- a/tests/one-shot.sh
+++ b/tests/one-shot.sh
@@ -1,5 +1,5 @@
 # GNU Shepherd --- Test one-shot services.
-# Copyright © 2019, 2023-2024 Ludovic Courtès <[email-masked]>
+# Copyright © 2019, 2023-2025 Ludovic Courtès <[email-masked]>
 #
 # This file is part of the GNU Shepherd.
 #
@@ -197,4 +197,35 @@ test "$(cat "$stamp")" = "third"
 $herd start fourth && false
 $herd start fourth && false
 
+# Check the behavior of two clients competing to start the same one-shot
+# service.  Both should succeed.
+
+cat > "$conf" <<EOF
+(register-services
+  (list (service
+          '(fifth)
+          #:one-shot? #t
+          #:start (lambda ()
+                    (let loop ()
+                      (unless (file-exists? "$stamp")
+                        (sleep 0.5)
+                        (loop)))
+                    #t))))
+EOF
+
+$herd load root "$conf"
+
+rm -f "$stamp"
+
+$herd start fifth &
+herd_start_pid1=$!
+$herd start fifth &
+herd_start_pid2=$!
+until $herd status fifth | grep starting; do sleep 0.5; done
+touch "$stamp"			# trigger starting->running transition
+
+# Both 'herd start' processes should have succeeded.
+wait $herd_start_pid1
+wait $herd_start_pid2
+
 $herd stop root