Keep the same port across app restarts
The port probe did a plain bind() without SO_REUSEADDR, so TIME_WAIT connections left by the previous instance (e.g. the in-app update restart) made the port look occupied and the app hopped to the next one. Probe with SO_REUSEADDR like Werkzeug binds, and give the requested port a 10-second grace period before auto-incrementing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
679f91da2c
commit
d6bf80a68a
@ -11,6 +11,8 @@ Version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html
|
||||
|
||||
### Fixed
|
||||
|
||||
- **App restart no longer hops to a new port** — the in-app update restart (and any quick stop/start) left connections from the previous instance in TIME_WAIT, and the startup port probe did a plain `bind()` that treats TIME_WAIT as occupied — so the restarted app silently came up on 5101 and the browser's reload poll never found it. The probe now sets `SO_REUSEADDR` (matching how Werkzeug actually binds, so an actively listening port is still detected as occupied), and the requested port gets a 10-second grace period before the auto-increment fallback kicks in, covering the brief window where the old process hasn't fully released the socket.
|
||||
|
||||
- **Share links now respect a reverse proxy** — `_getShareBaseUrl()` rewrote every copied share link to `http://<LAN-IP>:5100` (via `/api/local_ip`), which would bypass TLS when the scanner sits behind a reverse proxy (Zoraxy, Caddy, nginx, …): a DPO opening the link would silently fall back to plain HTTP. The LAN-IP rewrite now only applies in the case it was built for — browsing the app at `localhost` over HTTP, where `window.location.origin` would produce links unusable from other machines. Any HTTPS or non-localhost origin is used as-is.
|
||||
|
||||
---
|
||||
|
||||
@ -2261,14 +2261,33 @@ Example --settings file with SMTP:
|
||||
|
||||
# Find a free port — auto-increment from the requested port if in use.
|
||||
import socket as _socket
|
||||
|
||||
def _can_bind(p: int, host: str) -> bool:
|
||||
with _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM) as s:
|
||||
# Probe with SO_REUSEADDR, matching how Werkzeug binds.
|
||||
# Without it, connections left in TIME_WAIT by a previous
|
||||
# instance (e.g. the in-app update restart) make the port
|
||||
# look occupied and the app silently moves to the next one.
|
||||
s.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
s.bind((host, p))
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def _find_free_port(start: int, host: str) -> int:
|
||||
for p in range(start, start + 100):
|
||||
with _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM) as s:
|
||||
try:
|
||||
s.bind((host, p))
|
||||
return p
|
||||
except OSError:
|
||||
continue
|
||||
# Give the requested port a grace period — after a self-restart
|
||||
# the previous process may not have released it yet.
|
||||
deadline = time.time() + 10
|
||||
while True:
|
||||
if _can_bind(start, host):
|
||||
return start
|
||||
if time.time() >= deadline:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
for p in range(start + 1, start + 100):
|
||||
if _can_bind(p, host):
|
||||
return p
|
||||
raise RuntimeError(f"No free port found in range {start}–{start + 99}")
|
||||
|
||||
actual_port = _find_free_port(args.port, args.host)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user