Set SO_REUSEADDR on the web admin's TCPServer — the actual root cause
Live-confirmed the retry fix from the previous commit wasn't enough: still "Address already in use" after 20s of retries (10 attempts, 2s backoff), only to succeed on its own sometime after that. That delay pattern is TIME_WAIT, not a process-death race — and this code was never going to avoid it, because socketserver.TCPServer defaults allow_reuse_address to False. (http.server.HTTPServer sets this for you; the plain base class used here does not.) Without SO_REUSEADDR, the kernel can refuse to rebind a port with a lingering TIME_WAIT socket from the previous instance for up to 60s, regardless of whether that old process is even still alive — which is also why the entrypoint.sh fix waiting for the process to exit didn't help either. Set socketserver.TCPServer.allow_reuse_address = True before binding. This is the standard fix for exactly this symptom. Keeping the retry loop from the previous commit too, for the (now much smaller) window where network_mode: host still has no Docker-managed port mapping to instantly free. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015X1jRGHwrvovz2qkhKfDZi
This commit is contained in:
+16
-6
@@ -6135,12 +6135,22 @@ class WebAdminHandler(http.server.BaseHTTPRequestHandler):
|
||||
self.wfile.write(json.dumps(data).encode())
|
||||
|
||||
def main():
|
||||
# A container recreate under network_mode: host depends on the *previous*
|
||||
# container's web admin process actually dying before the port is free —
|
||||
# there's no Docker-managed port mapping to instantly release it like
|
||||
# there would be in bridge mode. A fast recreate-right-after-recreate can
|
||||
# briefly race that teardown. Retry instead of crashing on the first
|
||||
# failure, which otherwise means the web admin silently never comes up.
|
||||
# socketserver.TCPServer (unlike http.server.HTTPServer, which sets this
|
||||
# itself) defaults allow_reuse_address to False — without SO_REUSEADDR,
|
||||
# the kernel can refuse to rebind this port for up to 60s while any
|
||||
# TIME_WAIT socket from the previous instance lingers, regardless of
|
||||
# whether that old process is even still alive. Live-confirmed: still
|
||||
# failing after 20s of retries, only to succeed on its own well after
|
||||
# that. This was the actual bug — SO_REUSEADDR is the standard fix for
|
||||
# exactly this "restarted a TCP server, port still busy" symptom.
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
|
||||
# Still retry on top of that: under network_mode: host there's no
|
||||
# Docker-managed port mapping to instantly release like there'd be in
|
||||
# bridge mode, so a fast recreate-right-after-recreate can still
|
||||
# briefly race the previous process's own shutdown. Retry instead of
|
||||
# crashing on the first failure, which otherwise means the web admin
|
||||
# silently never comes up.
|
||||
httpd = None
|
||||
last_err = None
|
||||
for attempt in range(10):
|
||||
|
||||
Reference in New Issue
Block a user