Issues / #2760
#2760 Second server start should fail fast: PID lock before model loading
open · @ver9118-droid · 0 comments · View on GitHub
Setup & installServer & APIModels & quantsWindows
Description
## Problem
Starting a second Strata server on the same port does not fail fast. `main()` checks the port (`Server((host, port), ...)` probe) and binds only **after** the model is loaded, so two simultaneous starts (e.g. Windows autostart + `START-HERE.bat` + an agent script at boot) both pass the probe, each maps ~39 GB of experts, and they fight over RAM for minutes on a 32 GB PC before the loser finally fails at bind.
On Windows `allow_reuse_address` is already `False`, so the bind itself is safe — the hole is the check-then-bind race combined with minutes of loading in between.
## Proposed fix
Take a PID lock file (next to the model config, `.strata-<port>.lock`) right after arg/config resolution, before tokenizer/engine loading. Second start exits in <1s with the owner's PID. Stale locks (hard kill, no atexit) are detected via PID-liveness check and taken over.
Tested locally on Windows 11 + RX 9070 XT, engine 0.1.44.1:
- duplicate start: `server.py: error: another Strata server is already starting/serving port 8081 (PID 7204)` in **0.2 s**, no model touched
- hard-killed owner leaves a stale lock; next start detects the dead PID, takes over, and serves normally
## Patch (against 0.1.44.1 `serve/server.py`)
Add after `listen_problem` (~line 107):
```python
def _pid_alive(pid: int) -> bool:
try:
import ctypes
h = ctypes.windll.kernel32.OpenProcess(0x1000, False, pid)
if not h:
return False
ctypes.windll.kernel32.CloseHandle(h)
return True
except Exception: # noqa: BLE001 - unknown platform: assume taken (safe side)
return True
def take_server_lock(ap, a) -> None:
"""One server per port: a PID lock taken before the minutes of model loading,
so a second start (autostart + START-HERE + agent at boot) exits in <1s instead
of mapping ~39 GB for minutes and then failing at bind."""
import atexit
try:
base = Path(a.config).parent if a.config else Path(tempfile.gettempdir())
except (OSError, TypeError):
base = Path(tempfile.gettempdir())
lock = base / f".strata-{a.port}.lock"
try:
fd = os.open(lock, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
except FileExistsError:
pid = 0
try:
pid = int(lock.read_text(encoding="utf-8").strip().split()[0])
except (OSError, ValueError, IndexError):
pid = 0
if pid > 0 and _pid_alive(pid):
ap.error(f"another Strata server is already starting/serving port {a.port} (PID {pid}): "
f"use it instead of starting a second one")
try:
lock.unlink()
except OSError:
pass
try:
fd = os.open(lock, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
except FileExistsError:
ap.error(f"port {a.port} is already in use - is Strata (or another server) already running?")
except OSError as e:
print(f"[strata] WARNING: cannot take the start lock ({e}): continuing without duplicate protection",
flush=True)
return
except OSError as e:
print(f"[strata] WARNING: cannot take the start lock ({e}): continuing without duplicate protection",
flush=True)
return
with os.fdopen(fd, "w") as f:
f.write(str(os.getpid()))
def _release(path=lock):
try:
path.unlink(missing_ok=True)
except OSError:
pass
atexit.register(_release)
```
And call it in `main()` right after the `a.host = ...` line:
```python
take_server_lock(ap, a) # one server per port: fail fast before loading
```
Environment: Windows 11, RX 9070 XT 16 GB, 32 GB RAM, IQ2_XS + Coder IQ1_M configs, port 8080.
Related on this site
Quick links to related pages.