guix-commits
[Top][All Lists]
Advanced

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

[shepherd] 01/04: service: For inetd and systemd, retry 'bind' upon EADD


From: Ludovic Courtès
Subject: [shepherd] 01/04: service: For inetd and systemd, retry 'bind' upon EADDRINUSE.
Date: Fri, 28 Apr 2023 09:07:49 -0400 (EDT)

civodul pushed a commit to branch master
in repository shepherd.

commit 41789ee8d0e164967f9ca196db4e9601400a462e
Author: Ludovic Courtès <ludo@gnu.org>
AuthorDate: Fri Apr 28 11:21:53 2023 +0200

    service: For inetd and systemd, retry 'bind' upon EADDRINUSE.
    
    Reported by Lars-Dominik Braun <ldb@leibniz-psychology.org>
    at <https://issues.guix.gnu.org/58485#13>.
    
    * modules/shepherd/service.scm (bind/retry-if-in-use): New procedure.
    (endpoint->listening-socket): Use it instead of 'bind'.
    * tests/inetd.sh: Test it.
---
 modules/shepherd/service.scm | 24 ++++++++++++++++++++++--
 tests/inetd.sh               | 19 +++++++++++++++++++
 2 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/modules/shepherd/service.scm b/modules/shepherd/service.scm
index 719d744..570bc72 100644
--- a/modules/shepherd/service.scm
+++ b/modules/shepherd/service.scm
@@ -1750,6 +1750,26 @@ permissions for its parent directory."
                  socket-owner socket-group
                  socket-directory-permissions))
 
+(define* (bind/retry-if-in-use sock address
+                               #:key (max-attempts 5))
+  "Bind @var{sock} to @var{address}.  Retry up to @var{max-attempts} times upon
+EADDRINUSE."
+  (let loop ((attempts 1))
+    (catch 'system-error
+      (lambda ()
+        (bind sock address))
+      (lambda args
+        (if (and (= EADDRINUSE (system-error-errno args))
+                 (< attempts max-attempts))
+            (begin
+              (local-output
+               (l10n "Address ~a is in use; \
+retrying to bind it in one second.")
+               (socket-address->string address))
+              (sleep 1)
+              (loop (+ attempts 1)))
+            (apply throw args))))))
+
 (define (endpoint->listening-socket endpoint)
   "Return a listening socket for ENDPOINT."
   (match endpoint
@@ -1767,7 +1787,6 @@ permissions for its parent directory."
             (group   (if (integer? group)
                          group
                          (group:gid (getgrnam group)))))
-       (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
        (when (= AF_INET6 (sockaddr:fam address))
          ;; Interpret AF_INET6 endpoints as IPv6-only.  This is contrary to
          ;; the Linux defaults where listening on an IPv6 address also listens
@@ -1778,7 +1797,8 @@ permissions for its parent directory."
          (chown (dirname (sockaddr:path address)) owner group)
          (catch-system-error (delete-file (sockaddr:path address))))
 
-       (bind sock address)
+       (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
+       (bind/retry-if-in-use sock address)
        (listen sock backlog)
 
        (when (= AF_UNIX (sockaddr:fam address))
diff --git a/tests/inetd.sh b/tests/inetd.sh
index df2e6f1..9c43e8f 100644
--- a/tests/inetd.sh
+++ b/tests/inetd.sh
@@ -206,6 +206,25 @@ converse_with_echo_server \
 $herd stop test-inetd-unix
 $herd status
 
+# Simulate EADDRINUSE.
+$herd eval root "
+  (let ((real-bind bind)
+        (failures 0))
+    (set! bind (lambda (sock address)
+                 (when (< failures 2)
+                   (set! failures (+ failures 1))
+                   (throw 'system-error \"bind\" \"Oh!\" '()
+                          (list EADDRINUSE)))
+                 (set! bind real-bind)
+                 (real-bind sock address))))"
+
+$herd start test-inetd-unix
+$herd status test-inetd-unix | grep running
+$herd stop test-inetd-unix
+
+grep "is in use" "$log"
+$herd status
+
 # At this point, shepherd should have INITIAL_FD_COUNT - 1 file descriptors
 # opened.
 test $(file_descriptor_count) -lt $initial_fd_count



reply via email to

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