问题 / #2760

#2760 Second server start should fail fast: PID lock before model loading

open · @ver9118-droid · 0 评论 · 去 GitHub 看

Setup & installServer & APIModels & quantsWindows

说明

## 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.

本站相关内容

相关页面的快捷入口。