[CRITICAL] start_model memory check + status update nested inside 'stop other active models' loop (processes.py:90-140) #39
Labels
No labels
blocked:lint-failed
blocked:needs-decision
blocked:no-changes
blocked:no-procedure
blocked:preflight
blocked:verify-failed
bug
feature-request
in-progress
lint-cleanup
ops-task
quickfix
security/high
security/medium
test-coverage
triage-approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Mark/vllm_mm#39
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Memory check and
status="loading"update are incorrectly nested inside the "stop other active models" loop, producing redundant work, stale GPU state, and a missing status update when no other models are active.Location
backend/app/routers/processes.pylines 88-140 (instart_model).Bug
Lines 103-140 (
# Run pre-flight memory check...throughawait repo.update_status(model_id, status="loading")) are indented under thefor other in active_models:loop started at line 90, not after it.Observed effects at HEAD e91deb0:
N-fold redundant memory check. With N other active models,
MemoryChecker.check_before_startruns N times — each against progressively-changing GPU state asstop_modelcalls free VRAM. The check on iteration 2+ sees memory reclaimed by iteration 1's stop, so it may pass when it shouldn't (or vice versa).Wrong GPU state on first check. The very first memory check runs before any
stop_modelcall has freed memory from competing models. If two models are simultaneously active, the check is performed against occupied VRAM and may fail or succeed non-deterministically.status="loading"set N times instead of once. Cosmetic but noisy; also means the DB row gets touched per-iteration.Memory check skipped entirely when
active_modelsis empty. The loop body still runs once for each item, but if the list is empty the body never executes — so the check and the status update both vanish. A fresh start of the only model skips the pre-flight check.Dead variable.
backend_impl = get_backend_from_string(backend)at line 144 is created and never used.Reproduction
Expected: exactly one memory check, performed after all other models are stopped.
Suggested Fix
Dedent lines 103-140 to be siblings of the
forloop (i.e., at the same indentation as line 142'stry:). Sequence should be:status="loading".Drop the unused
backend_implat line 144.Severity
Critical — affects every multi-model start, can produce false-positive OOM errors or false-negative OOM kills under load.
Detected
Code review of HEAD
e91deb0(2026-09-14).Fix applied
Drops the dead
backend_impl = get_backend_from_string(backend)line instart_modelplus its now-orphanedget_backend_from_stringimport.Context
The indentation bug originally reported in this issue (memory check + status=loading nested under the
for other in active_modelsloop) was already fixed by the #35 PR (commit9aa91ec). However, the deadbackend_implassignment survived the dedent, and ruff F401 was flagging the now-orphaned import. This commit cleans up the remaining artifacts.Diff
Verification
python3 -m py_compile backend/app/routers/processes.py— OKpython3 -m ruff check backend/app/routers/processes.py backend/tests/test_processes.py— All checks passedpython3 -m pytest backend/tests/test_processes.py— 11 passedpython3 -m pytest backend/— 234 passed, 2 skippedBranch:
fix/39-start-model-memory-check-dedent(commit1ef3f9f) — sitting on top oforigin/main(ea16ebf). Currently blocked from pushing due to a server-side sideband-disconnect issue unrelated to the diff. Local commit verified; ready for verifier to push and merge.PR #4 opened with the proper title + rule-to-guard mapping:
PR: Mark/kanban#4
Note: PR #2 was the merge PR (auto-generated title "Merge 137-kanban-guards-and-watcher-branches -> main"), already merged to main on 2026-09-12 16:51:50 UTC as commit
4b983dc. PR #4 captures the rule-to-guard mapping table that PR #2's auto-description lacked.Rule-to-guard summary (full table in PR #4 description)
doneis terminal; no transition out ofdoneserver/transition_guards.ts::computeFlagUpdatesForTransitionsetsterminal: 1on done entry; line 132 rejects done→X withkind: done_is_terminal. Wired intoserver/storage.ts::transitionTask(line 290, atomic flag update). 409 surface atserver/routes.ts.four_guards_integration.test.ts:142— 2 testsblockedonly returns to active viakanban_unblockserver/watcher.ts::branch_blocked(line 150, unsubscribe; re-add only onkanban_unblockevent indeliverKanbanEventline 287) +server/storage.ts::transitionTask(line 236, rejects blocked→active with typed error referencingkanbanUnblock) +server/storage.ts::kanbanUnblock(line 55, sole handler)four_guards_integration.test.ts:251— 3 testsrunningis owned exclusively by the watcherserver/transition_guards.ts::assertCallerAllowedForRunning(rejects non-watcher withkind: foreign_running_write, missing/null caller withkind: missing_caller_for_running) +server/watcher.ts::branch_running(line 170, tick-entry assertioncard.owner !== WATCHER_PRINCIPALthrowsWatcherError{kind: foreign_running_write}) +server/principal.ts::deriveCallerFromRequest(HTTP trust boundary, body-supplied caller ignored, server-derived fromX-Internal-Actorheader)four_guards_integration.test.ts:382— 5 testsserver/watcher.ts::branch_done(line 131,drop_doneaction;_watchSet.addonly at line 181 [legit running dispatch] and line 287 [kanban_unblockarm]) +KANBAN_EVENT_CATALOGexposes no event that re-adds a done cardfour_guards_integration.test.ts:552— 3 testsCI status
CI on the merge commit (
4b983dc) is GREEN — run #6623, status=success, 93 seconds duration, triggered by push to main on 2026-09-12 17:55:51Z → 2026-09-12 17:57:24Z. URL: https://forgejo.tail018ac4.ts.net/Mark/kanban/actions/runs/2.The PR #4 head (
a0bc081) is an ancestor of the merge commit (4b983dc) and contains the same code that CI verified — 157/157 vitest pass locally on the branch HEAD.Acceptance
kanban_unblockis the only path out ofblocked✓doneverified by test (5 sweeps, no re-subscribe) ✓Refs: kanban t_d3f147f9 (root), t_e49638bb (implementer), t_002084ad/t_c657a9cc/t_383734ad/t_fc944a5a (children), t_7fd87e14 (verifier).
Verified merged — fix landed on
mainat2fe61099via PR #44.2fe61099d536a7c12f0be74cdb775ff913b92323(main advanced fromea16ebf→2fe61099)closed_at: 2026-09-18T23:14:45Zfix/39-start-model-memory-check-dedentmerged intomainDiff was 9 deletions across 2 files (
backend/app/routers/processes.py,backend/tests/test_processes.py), dropping the deadbackend_impl = get_backend_from_string(backend)binding, the orphanedget_backend_from_stringimport, and the matching@patchdecorator +Mocksetup from the #35 regression test. Ruff clean, 234/234 backend tests passed on the worktree.