[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