Release v0.51.234 — Release HB (stage-q4) (#3488)
Some checks failed
Release & Docker / release (push) Has been cancelled
Some checks failed
Release & Docker / release (push) Has been cancelled
## Release v0.51.234 — Release HB (stage-q4) Two medium-risk backend/infra fixes. All gates green. ### Fixes | PR | Author | Fix | |----|--------|-----| | #3289 | @rodboev | Refuse server startup when a live instance already serves the port (Windows/macOS silent port-sharing hazard). Live-listener probe (`GET /health`, 2s timeout) + Windows `SO_EXCLUSIVEADDRUSE` — **preserves fast restart** (POSIX keeps `allow_reuse_address=True`; a dying socket in the kernel backlog times out → startup proceeds). | | #3486 | @dso2ng | Allow remote/SSH terminal profiles to use target-side workspace paths under `terminal.cwd` without a server-local `stat()`. Local profiles unchanged — bypass only fires for remote backends and only for paths contained within `terminal.cwd`. | ### History note on #3289 This PR was **held earlier this sweep** — its original form globally disabled `SO_REUSEADDR`, which a Codex gate flagged as breaking fast restart (TIME_WAIT bricks rebind for ~60s). The contributor reworked it along the suggested lines (live-listener probe instead of the global disable). This release ships the reworked version. Unheld → full pickup → full gate. ### Gate results - **Full pytest suite**: 7458 passed, 8 skipped, 3 xpassed, **0 failed** - **ruff forward gate**: CLEAN - **browser-smoke gate**: CLEAN (real server boots fine with the new startup probe) - **Codex (regression)**: SAFE TO SHIP (verified fast-rebind preserved + remote bypass gated on backend+containment, local validation unchanged) - **Opus (correctness + security)**: SAFE TO SHIP (probe false-positive, `_is_within` containment, local-profile bypass all hold up; applied its one minor double-call cleanup note) Closes #3289. Co-authored-by: rodboev <rodboev@users.noreply.github.com> Co-authored-by: dso2ng <dso2ng@users.noreply.github.com>
This commit is contained in:
28
server.py
28
server.py
@@ -184,6 +184,13 @@ class QuietHTTPServer(ThreadingHTTPServer):
|
||||
self.accept_loop_requests_total = 0
|
||||
self.accept_loop_last_request_at = 0.0
|
||||
|
||||
def server_bind(self):
|
||||
if sys.platform == 'win32':
|
||||
self.allow_reuse_address = False
|
||||
SO_EXCLUSIVEADDRUSE = getattr(socket, 'SO_EXCLUSIVEADDRUSE', -5)
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, SO_EXCLUSIVEADDRUSE, 1)
|
||||
super().server_bind()
|
||||
|
||||
def _handle_request_noblock(self):
|
||||
"""Record accept-loop progress before dispatching a request handler.
|
||||
|
||||
@@ -477,6 +484,25 @@ def _log_shutdown_audit(reason: str = "serve_forever_exit") -> None:
|
||||
)
|
||||
|
||||
|
||||
def _abort_if_already_serving(host: str, port: int) -> None:
|
||||
"""Refuse to start if a live HTTP server is already responding on this port."""
|
||||
probe_host = '127.0.0.1' if host in ('0.0.0.0', '', '::') else host
|
||||
try:
|
||||
with socket.create_connection((probe_host, port), timeout=2) as s:
|
||||
s.sendall(b'GET /health HTTP/1.0\r\nHost: localhost\r\n\r\n')
|
||||
s.settimeout(2)
|
||||
data = s.recv(512)
|
||||
if data:
|
||||
print(
|
||||
f'[!!] FATAL: Another server is already responding on'
|
||||
f' {probe_host}:{port}. Stop the existing instance first.',
|
||||
flush=True,
|
||||
)
|
||||
sys.exit(1)
|
||||
except (ConnectionRefusedError, ConnectionResetError, OSError, socket.timeout):
|
||||
pass
|
||||
|
||||
|
||||
def main() -> None:
|
||||
from api.config import print_startup_config, verify_hermes_imports, _HERMES_FOUND
|
||||
|
||||
@@ -572,6 +598,7 @@ def main() -> None:
|
||||
except Exception as e:
|
||||
print(f'[!!] WARNING: Plugin loading failed: {e}', flush=True)
|
||||
|
||||
_abort_if_already_serving(HOST, PORT)
|
||||
httpd = QuietHTTPServer((HOST, PORT), Handler)
|
||||
|
||||
# ── TLS/HTTPS setup (optional) ─────────────────────────────────────────
|
||||
@@ -597,6 +624,7 @@ def main() -> None:
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
finally:
|
||||
httpd.server_close()
|
||||
_log_shutdown_audit()
|
||||
# Stop the gateway watcher on shutdown
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user