front: don't flag a busy-but-alive engine as "not responding" during generation

The supervisor health-polls /internal/engine-state every couple seconds. While the
engine is GIL-busy generating, that poll can't be answered in time and the engine
was flipped to healthy=False — flapping out of the UI/routing mid-generation even
though it's perfectly alive. Now a poll timeout only downgrades health when the
PROCESS IS GONE (true death, already caught by the restart check); a timeout with
the process alive keeps the last-known-healthy state. Also bump proxy_status_timeout
2s→4s so transient GIL contention doesn't trip it. (Pairs with the engine-state
VRAM cache that removed the per-poll CUDA call.)
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
parent f1f11723
...@@ -50,7 +50,9 @@ class ServerConfig: ...@@ -50,7 +50,9 @@ class ServerConfig:
internal_port_base: int = 8780 # first engine binds here; +1 per extra engine internal_port_base: int = 8780 # first engine binds here; +1 per extra engine
engines: int = 0 # 0 = auto (one per detected GPU, min 1) engines: int = 0 # 0 = auto (one per detected GPU, min 1)
engine_gpus: Optional[list] = None # explicit GPU indices, e.g. [0, 1]; None = auto engine_gpus: Optional[list] = None # explicit GPU indices, e.g. [0, 1]; None = auto
proxy_status_timeout: float = 2.0 # short timeout for UI/status proxying (seconds) proxy_status_timeout: float = 4.0 # short timeout for UI/status proxying (seconds);
# generous enough that a GIL-busy engine's
# health poll doesn't time out mid-generation
proxy_max_inflight: int = 64 # max concurrent proxied requests through the front proxy_max_inflight: int = 64 # max concurrent proxied requests through the front
engine_restart_drain_grace: float = 30.0 # on engine restart, wait this many seconds engine_restart_drain_grace: float = 30.0 # on engine restart, wait this many seconds
# for in-flight requests to finish before # for in-flight requests to finish before
......
...@@ -526,9 +526,17 @@ class EngineSupervisor: ...@@ -526,9 +526,17 @@ class EngineSupervisor:
else: else:
self.registry.update_state(engine.id, healthy=False) self.registry.update_state(engine.id, healthy=False)
except Exception: except Exception:
# Connection refused / timeout: still-loading or dead. Mark # Connection refused / timeout. If the process is ALIVE it's
# unhealthy; the process-exit check above handles true death. # almost certainly just GIL-busy generating (the engine-state
self.registry.update_state(engine.id, healthy=False) # handler couldn't answer in time), NOT dead — keep its
# last-known state so a busy engine doesn't flap to "not
# responding" and drop out of the UI/routing mid-generation.
# True death is caught by the process-exit check above
# (_maybe_restart); only downgrade when the process is gone.
if not engine.is_alive():
self.registry.update_state(engine.id, healthy=False)
else:
healthy = self._health.get(engine.id, True)
# --debug-engine: report health transitions (ready / lost). # --debug-engine: report health transitions (ready / lost).
if self.debug and self._health.get(engine.id) != healthy: if self.debug and self._health.get(engine.id) != healthy:
self._health[engine.id] = healthy self._health[engine.id] = healthy
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment