Skip to content

fix: v0.5.3 server/pool stability wave (7 P0/P1 defects + regression tests)#51

Merged
magicnight merged 1 commit into
mainfrom
fix/v0.5.3-server-pool-stability
Jul 8, 2026
Merged

fix: v0.5.3 server/pool stability wave (7 P0/P1 defects + regression tests)#51
magicnight merged 1 commit into
mainfrom
fix/v0.5.3-server-pool-stability

Conversation

@magicnight

Copy link
Copy Markdown
Owner

Fixes 7 confirmed P0/P1 defects found by a 4-agent debug round against main 5e461fb (full findings tracked internally; issue #29's hang mechanism is bounded by the new watchdog).

CRITICAL

  • SRV-1 — GUI-launched server captured one frozen engine reference; pool cold-swap made it silently answer with the wrong model, or 500 forever once the original model was evicted. Now an injected engineProvider re-resolves the current engine per request.
  • SRV-2ensureModelLoaded ran outside the generation lock, so a concurrent swap could interleave with an in-flight generation (wrong-model output / load racing generation on shared MLX state). Swap + generate are now atomic under the lock (beginGeneration).

HIGH

  • SRV-3 — generation-lock acquire/release lived in different scopes (leak if the response body never ran) and parked waiters weren't cancellation-aware (dead-owner deadlock). Reworked: same-scope invariant + cancellation-aware waiter queue.
  • SRV-4 — no server-side generation timeout existed; non-streaming buffered forever (the [Bug] OpenAI-compatible API hangs on chat/completions (model loaded, no tokens returned) #29 presentation). New stall-based watchdog: generationStallTimeoutSeconds (default 120s, 0 = disabled) — inter-chunk stall detection, so long productive generations are never killed. Streaming unknown-model requests also return 404 before headers again (4 paths).
  • POOL-1 — the active model was never pinned; a failed load of another model could leave a dangling currentModel with UI showing ready. Active model is now pinned (same-model reload guard) and the dangling pointer is reconciled.
  • POOL-2 — Stop cancelled only the UI stream; the engine kept generating to completion (GPU burn). Cancellation now propagates end-to-end (onTermination → task cancel → Task.isCancelled in the engine loops).
  • POOL-3evict(toFit:) ignored isGenerating and server generations were invisible to the pool. Evict now skips in-flight entries; the server marks generations in-flight via inFlightHook.

Tests

7 new regression tests target the exact escape mechanisms (loadHook swap, concurrent dual-model, cancelled parked waiter, watchdog + lock release, pin/unpin ordering, evict guard, streaming 404): 65 XCTest + 178 swift-testing, 0 failures; app + CLI builds green. Independently code-reviewed (APPROVE).

…round

- SRV-1 (CRITICAL): server re-resolves the engine per request via an
  injected engineProvider; GUI cold-swap no longer answers with the
  wrong model or bricks with 500s on a frozen engine reference
- SRV-2 (CRITICAL): model swap + generation are atomic under the
  generation lock (beginGeneration helper); load can no longer race
  an in-flight generation on shared MLX state
- SRV-3: lock acquire/release now share one lexical scope (streaming:
  inside ResponseBody); cancellation-aware waiter queue removes the
  permanent-leak and dead-owner deadlocks
- SRV-4: stall-based generation watchdog (generationStallTimeoutSeconds,
  default 120s, 0 = disabled) — bounds the hang presentation from
  issue #29 (non-streaming buffered forever with no server timeout)
- POOL-1: active model is pinned (unpin previous, same-model guard);
  dangling currentModel reconciled when a failed load evicted it
- POOL-2: Stop now cancels engine generation (onTermination + honored
  .terminated + Task.isCancelled in both LLM/VLM loops)
- POOL-3: evict(toFit:) skips isGenerating entries; server generations
  are marked in-flight via the new inFlightHook
- A1: streaming unknown-model requests return 404 before headers again
  (OpenAI SSE, Ollama chat+generate NDJSON, Anthropic SSE)
- A4: stall-timeout clamped (0...86400s) against Double→UInt64 traps

7 regression tests cover the exact escape mechanisms: loadHook swap,
concurrent dual-model, cancelled parked waiter, watchdog + lock release,
pin/unpin ordering, evict guard, streaming 404 pre-flight.
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant