Retry web admin port bind instead of crashing on the first race

Live-confirmed: OSError: [Errno 98] Address already in use on the
web admin's TCPServer bind, right after a container recreate under
network_mode: host. Unlike bridge-mode port publishing, there's no
Docker-managed mapping to instantly free on teardown — the previous
container's own web admin process has to actually die first, and a
fast recreate-right-after-recreate can race that. The process crashed
immediately instead of retrying, so the web admin silently never came
up despite entrypoint.sh correctly launching it.

Retry the bind up to 10 times with a 2s backoff before giving up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015X1jRGHwrvovz2qkhKfDZi
This commit is contained in:
Claude
2026-07-20 19:05:53 +00:00
parent d9af3ecc3f
commit c42dc7e013
+20 -1
View File
@@ -4116,6 +4116,7 @@ import json
import subprocess
import os
import re
import time
import base64
import hashlib
import html
@@ -6134,7 +6135,25 @@ class WebAdminHandler(http.server.BaseHTTPRequestHandler):
self.wfile.write(json.dumps(data).encode())
def main():
with socketserver.TCPServer(("", PORT), WebAdminHandler) as httpd:
# 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.
httpd = None
last_err = None
for attempt in range(10):
try:
httpd = socketserver.TCPServer(("", PORT), WebAdminHandler)
break
except OSError as e:
last_err = e
print(f"Port {PORT} not free yet ({e}), retrying...")
time.sleep(2)
if httpd is None:
raise last_err
with httpd:
print(f"Easy Asterisk Web Admin running on port {PORT}")
httpd.serve_forever()