Skip to content

fix(scheduler): close TOCTOU race in GPU admission with in-flight VRAM reservation#1705

Closed
hognek wants to merge 6 commits into
jaylfc:devfrom
hognek:fix/toctou-vram-reservation
Closed

fix(scheduler): close TOCTOU race in GPU admission with in-flight VRAM reservation#1705
hognek wants to merge 6 commits into
jaylfc:devfrom
hognek:fix/toctou-vram-reservation

Conversation

@hognek

@hognek hognek commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the TOCTOU (time-of-check to time-of-use) race in _check_admission identified in PR #1683 review feedback #2.

Problem

_check_admission reads live nvidia-smi free VRAM with no reservation mechanism. Two concurrent submit_gpu calls can both pass admission before either model loads — the exact concurrent-load crash (NVIDIA Xid 62) the arbiter exists to prevent.

Fix

Adds in-flight VRAM reservation tracking:

  • _reserved_vram_mb: counter of VRAM promised to tasks whose models are still loading
  • _pending_reservations: dict mapping task_id → reserved VRAM for idempotent release
  • _reservation_lock: asyncio.Lock to make check+reserve atomic

New method _reserve_and_check(task_id, required_vram_mb) atomically:

  1. Acquires _reservation_lock
  2. Calls _check_admission (which now subtracts _reserved_vram_mb from the probe)
  3. If admitted, immediately adds to _reserved_vram_mb

Reservations are released in:

  • _run_gpu_task finally block (normal completion, error, cancellation)
  • _evict_task (preemption)

Tests

  • 9 existing test_gpu_arbiter_894.py tests continue to pass
  • 7 new test_gpu_arbiter_toctou.py tests:
    • Concurrent _reserve_and_check only admits one of two calls exceeding capacity
    • submit_gpu reserves and releases correctly
    • Eviction releases reservation
    • _check_admission subtracts reserved VRAM from probe
    • Atomic reserve-and-check with lock
    • Idempotent release
    • Reservation fields in stats()

All 168 tests pass (25 arbiter + 143 cluster/worker).

Summary by CodeRabbit

  • New Features
    • Added GPU workload admission control with VRAM-aware scheduling, queueing, and eviction support.
    • Expanded scheduler discovery so GPU resources can be recognized and surfaced at startup.
    • GPU scheduler availability is now initialized automatically during app startup, with fallback behavior if setup fails.
  • Bug Fixes
    • Prevented GPU overcommit by accounting for in-flight VRAM reservations during admission.
    • Improved eviction behavior so running GPU work is actually stopped and resources are released correctly.

@hognek hognek marked this pull request as ready for review July 7, 2026 00:29
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a GpuArbiter class implementing VRAM-accounted admission control, queuing, and priority-based eviction for GPU workloads. It integrates the arbiter into scheduler exports, discovery (GPU resource registration), and app startup, adds a Task.estimated_vram_mb field, and includes new test suites covering eviction and TOCTOU reservation atomicity.

Changes

GPU Arbiter feature

Layer / File(s) Summary
Task VRAM field
tinyagentos/scheduler/types.py
Task dataclass gains estimated_vram_mb: int = 0.
GpuArbiter core implementation
tinyagentos/scheduler/gpu_arbiter.py
Implements VRAM probing (nvidia-smi), priority queue entries, admission results, reservation locking to prevent TOCTOU overcommit, submit_gpu, _check_admission, _run_gpu_task, eviction (evict_lowest_priority/_evict_task), queue draining, and stats/running_tasks/queue_snapshot introspection.
Scheduler export and discovery integration
tinyagentos/scheduler/__init__.py, tinyagentos/scheduler/discovery.py
GpuArbiter is exported lazily; discovery registers GPU resources (gpu-cuda-<idx>) using VRAM probing and capability/backend lookup helpers.
App startup wiring
tinyagentos/app.py
Imports GpuArbiter, initializes resource_scheduler to None, constructs and starts the arbiter during lifespan startup, storing it on app.state.gpu_arbiter with error fallback.
Eviction behavior tests
tests/test_gpu_arbiter_894.py
Covers direct-admission eviction, queued-task eviction, lease claim/release correctness, and lowest-priority eviction selection.
TOCTOU reservation tests
tests/test_gpu_arbiter_toctou.py
Covers concurrent reservation admission, submit_gpu reservation lifecycle, eviction reservation release, effective-VRAM admission checks, atomicity, idempotent release, and stats reporting.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant GpuArbiter
  participant ClusterManager
  participant Scheduler

  Caller->>GpuArbiter: submit_gpu(task, required_vram_mb)
  GpuArbiter->>GpuArbiter: _reserve_and_check(vram)
  alt admitted
    GpuArbiter->>ClusterManager: claim_lease(resource_id)
    GpuArbiter->>Scheduler: submit(task)
    Scheduler-->>GpuArbiter: result
    GpuArbiter->>GpuArbiter: release reservation
    GpuArbiter->>ClusterManager: release_lease(lease_id)
    GpuArbiter-->>Caller: result
  else rejected
    GpuArbiter->>GpuArbiter: enqueue _QueuedGpuTask with future
    GpuArbiter-->>Caller: await arbiter_future
    GpuArbiter->>GpuArbiter: _drain_queue admits later
  end
Loading
sequenceDiagram
  participant Operator
  participant GpuArbiter
  participant RunningTask
  participant ClusterManager

  Operator->>GpuArbiter: evict_lowest_priority(min_priority)
  GpuArbiter->>GpuArbiter: select lowest-priority running task
  GpuArbiter->>GpuArbiter: release VRAM reservation
  GpuArbiter->>ClusterManager: release_lease(lease_id)
  GpuArbiter->>RunningTask: cancel()
  RunningTask-->>GpuArbiter: CancelledError propagated
Loading

Possibly related PRs

  • jaylfc/taOS#1687: Provides the GPU VRAM telemetry and ClusterManager lease APIs (claim_lease/release_lease/renew_lease) that GpuArbiter depends on for reservation and eviction.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: fixing a GPU admission TOCTOU race via in-flight VRAM reservation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

if required_vram_mb <= 0:
return GpuAdmission(admitted=True)

async with self._reservation_lock:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: _reservation_lock is held during self._vram_probe(), which can block all concurrent admissions for up to the probe's 5-second timeout (_probe_nvidia_vram). Under load this serialises every admission decision behind the slowest probe call.

Consider probing outside the lock (or caching the probe value with a short TTL) and only taking the lock to read _reserved_vram_mb and update _pending_reservations. Alternatively, snapshot _vram_probe() before the async with and pass the result into _check_admission.

Comment thread tinyagentos/scheduler/gpu_arbiter.py Outdated
l.required_vram_mb for l in leases
if l.resource_id.startswith(worker.name + ":") and l.required_vram_mb > 0
)
if worker.free_vram_mb - worker_leases >= required_vram_mb:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: The cluster fallback path does not subtract _reserved_vram_mb from worker.free_vram_mb, so the TOCTOU fix is incomplete for cluster-mode admission. Two concurrent submit_gpu calls can both be admitted to different (or the same) cluster workers when their combined required VRAM exceeds available capacity — the same race the fix is meant to close.

Apply the same reservation accounting here: subtract _reserved_vram_mb (or a per-worker share) from the worker's effective free VRAM before admitting.


# Simulate what _reserve_and_check would have done when the task
# was admitted.
arbiter._reserved_vram_mb += 4096

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: This test manually mutates _reserved_vram_mb and _pending_reservations instead of going through the public _reserve_and_check API. That bypasses the reservation flow it's supposed to test and won't catch integration bugs (e.g. if _reserve_and_check forgets to populate _pending_reservations).

Use await arbiter._reserve_and_check("t-evict", 4096) to set up the reservation, so the test exercises the real code path that submit_gpu and _drain_queue use.

assert "t-noevict" in arbiter._running

# Clean up
arbiter._running_tasks.get("t-noevict", None)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: arbiter._running_tasks.get("t-noevict", None) is dead code — the result is discarded. The comment says "Clean up" but nothing is actually cleaned up here. Replace with arbiter._evict_task("t-noevict") (or cancel the stored asyncio task) so the test doesn't leak a running coroutine if evict_lowest_priority returns 0.

@kilo-code-bot

kilo-code-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 6 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 4
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 159 _reservation_lock held during self._vram_probe() — serialises all admissions behind the probe (up to 5s timeout).
tinyagentos/scheduler/gpu_arbiter.py 253 NEW — Cluster-mode admission now does worker.free_vram_mb - worker_leases - self._reserved_vram_mb, but WorkerInfo.free_vram_mb is `int
tinyagentos/app.py 1148 GpuArbiter constructed without vram_probe — falls back to (0, 0), so the arbiter never checks local VRAM and the TOCTOU reservation system is never exercised.
tinyagentos/app.py 1154 gpu_arbiter.start() called but no gpu_arbiter.stop() in lifespan shutdown — queue-processor task leaks on app shutdown.

SUGGESTION

File Line Issue
tests/test_gpu_arbiter_toctou.py 107 Test manually mutates _reserved_vram_mb / _pending_reservations instead of using _reserve_and_check — bypasses the real reservation flow.
tests/test_gpu_arbiter_894.py 151 Dead cleanup expression (_running_tasks.get(...) result discarded) — coroutine can leak when evict_lowest_priority returns 0.
Files Reviewed (4 files)
  • .gitignore — 0 issues
  • tests/test_gpu_arbiter_894.py — 1 issue
  • tests/test_gpu_arbiter_toctou.py — 1 issue
  • tinyagentos/scheduler/gpu_arbiter.py — 2 issues

Fix these issues in Kilo Cloud

Previous Review Summaries (3 snapshots, latest commit 815cca8)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 815cca8)

Status: 7 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 4
SUGGESTION 2
Issue Details (click to expand)

CRITICAL

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 277 Lease-claim failure raises before _run_gpu_task's try block — VRAM reservation leaks permanently.

WARNING

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 159 _reservation_lock held during self._vram_probe() — serialises all admissions behind the probe (up to 5s timeout).
tinyagentos/scheduler/gpu_arbiter.py 250 Cluster fallback in _check_admission does not subtract _reserved_vram_mb from worker VRAM — TOCTOU fix is incomplete for cluster-mode admission.
tinyagentos/app.py 1148 GpuArbiter constructed without vram_probe — falls back to (0, 0), so the arbiter never checks local VRAM and the TOCTOU reservation system is never exercised.
tinyagentos/app.py 1154 gpu_arbiter.start() called but no gpu_arbiter.stop() in lifespan shutdown — queue-processor task leaks on app shutdown.

SUGGESTION

File Line Issue
tests/test_gpu_arbiter_toctou.py 107 Test manually mutates _reserved_vram_mb / _pending_reservations instead of using _reserve_and_check — bypasses the real reservation flow.
tests/test_gpu_arbiter_894.py 151 Dead cleanup expression (_running_tasks.get(...) result discarded) — coroutine can leak when evict_lowest_priority returns 0.
Files Reviewed (8 files)
  • tests/test_gpu_arbiter_894.py — 1 issue
  • tests/test_gpu_arbiter_toctou.py — 1 issue
  • tests/test_gpu_arbiter.py — 0 issues (deleted in this PR)
  • tinyagentos/app.py — 2 issues
  • tinyagentos/scheduler/__init__.py — 0 issues
  • tinyagentos/scheduler/discovery.py — 0 issues
  • tinyagentos/scheduler/gpu_arbiter.py — 3 issues
  • tinyagentos/scheduler/types.py — 0 issues

Fix these issues in Kilo Cloud

Previous review (commit d647517)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 2
SUGGESTION 2
Issue Details (click to expand)

CRITICAL

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 277 Lease-claim failure raises before _run_gpu_task's try block — VRAM reservation leaks permanently. Regression introduced by the TOCTOU-fix patch.

WARNING

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 159 _reservation_lock held during self._vram_probe() — serialises all admissions behind the probe (up to 5s timeout).
tinyagentos/scheduler/gpu_arbiter.py 250 Cluster fallback in _check_admission does not subtract _reserved_vram_mb from worker VRAM — TOCTOU fix is incomplete for cluster-mode admission.

SUGGESTION

File Line Issue
tests/test_gpu_arbiter_toctou.py 107 Test manually mutates _reserved_vram_mb / _pending_reservations instead of using _reserve_and_check — bypasses the real reservation flow.
tests/test_gpu_arbiter_894.py 151 Dead cleanup expression (_running_tasks.get(...) result discarded) — coroutine can leak when evict_lowest_priority returns 0.
Files Reviewed (8 files)
  • tests/test_gpu_arbiter.py — 0 issues
  • tests/test_gpu_arbiter_894.py — 1 issue
  • tests/test_gpu_arbiter_toctou.py — 1 issue
  • tinyagentos/app.py — 0 issues
  • tinyagentos/scheduler/__init__.py — 0 issues
  • tinyagentos/scheduler/discovery.py — 0 issues
  • tinyagentos/scheduler/gpu_arbiter.py — 2 issues (+1 CRITICAL from incremental)
  • tinyagentos/scheduler/types.py — 0 issues

Fix these issues in Kilo Cloud

Previous review (commit 04f5a69)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 159 _reservation_lock held during self._vram_probe() — serialises all admissions behind the probe (up to 5s timeout).
tinyagentos/scheduler/gpu_arbiter.py 250 Cluster fallback in _check_admission does not subtract _reserved_vram_mb from worker VRAM — TOCTOU fix is incomplete for cluster-mode admission.

SUGGESTION

File Line Issue
tests/test_gpu_arbiter_toctou.py 107 Test manually mutates _reserved_vram_mb / _pending_reservations instead of using _reserve_and_check — bypasses the real reservation flow.
tests/test_gpu_arbiter_894.py 151 Dead cleanup expression (_running_tasks.get(...) result discarded) — coroutine can leak when evict_lowest_priority returns 0.
Files Reviewed (8 files)
  • tests/test_gpu_arbiter.py — 0 issues
  • tests/test_gpu_arbiter_894.py — 1 issue
  • tests/test_gpu_arbiter_toctou.py — 1 issue
  • tinyagentos/app.py — 0 issues
  • tinyagentos/scheduler/__init__.py — 0 issues
  • tinyagentos/scheduler/discovery.py — 0 issues
  • tinyagentos/scheduler/gpu_arbiter.py — 2 issues
  • tinyagentos/scheduler/types.py — 0 issues

Fix these issues in Kilo Cloud


Reviewed by minimax-m3 · Input: 43.1K · Output: 5.2K · Cached: 425.1K

@gitar-bot

gitar-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

Comment thread tinyagentos/scheduler/gpu_arbiter.py Outdated
resource_id=resource_id, caller=task.submitter,
ttl_seconds=300, required_vram_mb=required_vram_mb,
)
if lease is None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Orphaned VRAM reservation when claim_lease fails.

submit_gpu now reserves VRAM via _reserve_and_check (line 178) BEFORE calling _run_gpu_task. If claim_lease returns None here at line 277, the raise NoResourceAvailableError propagates out before the try block on line 287 — so the finally block (which calls _release_reservation(task.id)) never runs. The reservation leaks: _reserved_vram_mb and _pending_reservations[task.id] stay populated, permanently reducing the effective free VRAM seen by _check_admission for the lifetime of the arbiter. This is a regression introduced by the TOCTOU fix in patch 4.

Restructure so the reservation release is guaranteed on lease-claim failure — e.g., wrap the entire function body in try/finally, or release the reservation at the raise site.

Suggested change
if lease is None:
if lease_id is None and self._cluster_manager is not None and resource_id is not None:
raise NoResourceAvailableError(
f"GPU lease claim failed for {resource_id} (task {task.id})"
)

Note: the suggestion above is structural; the minimal safe fix is to move the raise inside the try: block (or add an early self._release_reservation(task.id) before the raise).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

hognek added 5 commits July 7, 2026 12:37
…ction

Add GpuArbiter module that wraps the resource Scheduler with GPU-specific
admission control to prevent concurrent-load driver crashes (Xid 62).

Key features:
- VRAM-aware admission: checks local VRAM probes and cluster worker
  leases before admitting GPU tasks
- Priority queue: pending GPU tasks wait when VRAM is insufficient,
  dequeued in priority order when resources free up
- Eviction: lower-priority running tasks can be evicted to make room
  for higher-priority work
- Lease integration: claims/releases GPU leases via ClusterManager
  for distributed coordination
- GPU resource registration: discovery.py now registers gpu-cuda-N
  resources when GPU backends are healthy
- GPU_POTENTIAL_CAPABILITIES constant for UI latent-capability display

19 unit tests pass. Builds on Slice 1 (VRAM endpoint, jaylfc#893 leases).

Task: t_d7208884. Fixes jaylfc#894.
… futures

_evict_task previously only cancelled the _arbiter_future on the Task
object. Directly-admitted tasks never have one set, and for queued-then-
admitted tasks the future is resolved by _drain_queue before _run_gpu_task
completes. In both cases the running GPU work keeps executing and VRAM is
never freed until natural completion.

Now _run_gpu_task registers its asyncio.Task in _running_tasks, and
_evict_task cancels that Task directly. CancelledError propagates to
the payload, the finally block cleans up VRAM/leases, and eviction
actually stops running GPU work.

- Added _running_tasks dict to track asyncio.Tasks
- _run_gpu_task registers current task and handles CancelledError
- _evict_task cancels the asyncio.Task (in addition to _arbiter_future)
- _drain_queue catches CancelledError from evicted tasks to keep
  queue processor alive
- Finally block checks if task was already evicted (entry popped)
  before releasing lease — prevents double-release

Fixes jaylfc#894.
_evict_task now uses pop(task_id, None) atomically instead of the
check-then-pop pattern, eliminating the TOCTOU KeyError that could
occur when _run_gpu_task's finally block pops the entry between
the existence check and the pop.

Ownership is now explicit: whoever pops self._running first releases
the lease.  _evict_task's pop is the sole lease releaser for evicted
tasks; _run_gpu_task's finally only releases on normal completion
(when it still finds the entry).  Comments document the invariant.

Refs jaylfc#894.  Task: t_0c86d08e.
…ncompatible with stack

The file was incorrectly brought in from the jaylfc#1689 version and references
VramAllocation, total_vram_mb, headroom_mb, and evict_callback that don't
exist in the stack's GpuArbiter.  test_gpu_arbiter_894.py covers the same
territory with the correct API.
…M reservation

_addmission_check read live nvidia-smi free VRAM with no reservation,
so two concurrent submit_gpu calls could both pass before either model
loaded — the exact concurrent-load crash (NVIDIA Xid 62) the arbiter
exists to prevent.

Add _reserved_vram_mb counter and _pending_reservations dict, guarded
by _reservation_lock.  _reserve_and_check() atomically checks admission
against (probe - _reserved_vram_mb) and reserves on success.  Reservations
are released in _run_gpu_task's finally block and in _evict_task.

_stats() now includes reserved_vram_mb and pending_reservations for
observability.

Fixes: PR jaylfc#1683 review feedback #2
Tests: 168 pass (25 arbiter + 143 cluster/worker)
@hognek hognek force-pushed the fix/toctou-vram-reservation branch from d647517 to 815cca8 Compare July 7, 2026 10:50
Comment thread tinyagentos/app.py
# Build the GPU arbiter — wraps the resource scheduler with VRAM-accounted
# admission control, queuing, and eviction for GPU-bound workloads.
try:
gpu_arbiter = GpuArbiter(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: GpuArbiter is constructed without a vram_probe argument, so it falls back to _default_vram_probe which returns (0, 0). With free_vram=0, _check_admission always falls through to the cluster branch (or returns admitted=True when no cluster_manager), meaning the arbiter never checks local VRAM and the entire TOCTOU reservation system (_reserved_vram_mb, _reserve_and_check, etc.) is never exercised in practice. Pass vram_probe=_probe_nvidia_vram (or a cached variant) so the arbiter actually prevents the Xid 62 crashes it was built to stop.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread tinyagentos/app.py
max_queue_size=100,
eviction_enabled=True,
)
await gpu_arbiter.start()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: gpu_arbiter.start() spawns a long-running queue-processor task (asyncio.create_task(self._process_queue())), but there is no corresponding gpu_arbiter.stop() in the FastAPI lifespan shutdown sequence (see lines ~1295-1344). On app shutdown the queue processor task is never cancelled and awaited, so the asyncio task leaks and the process may hang at interpreter shutdown while waiting for the background coroutine. Add await gpu_arbiter.stop() to the lifespan cleanup, guarded the same way as the other .stop() calls (e.g. inside a try/except or behind a getattr(app.state, "gpu_arbiter", None) check).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

…ed VRAM in cluster path

taOS jaylfc#1705 — reservation leak fix:
- Move lease claim and _running registration inside try block so the
  finally always releases the VRAM reservation, even on claim failure.
- Await claim_lease / release_lease (both are async on ClusterManager).
- Make _evict_task and evict_lowest_priority async (they await release_lease).
- Subtract _reserved_vram_mb in the cluster-mode admission path, matching
  the local-path behaviour so in-flight reservations are accounted for.
- Update tests: FakeClusterManager methods made async, all _evict_task and
  evict_lowest_priority calls now awaited.

Tests: 16/16 arbiter + 111/111 cluster pass.
# Subtract in-flight reservations as well — the cluster-mode
# path must also account for tasks whose models are still
# loading (taOS #1705).
available = worker.free_vram_mb - worker_leases - self._reserved_vram_mb

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Cluster path now risks TypeError on workers with no VRAM probe

WorkerInfo.free_vram_mb is typed int | None (see tinyagentos/cluster/worker_protocol.py:69) and is None on non-NVIDIA hardware (RK3588, Apple Silicon, CPU-only) and on workers that haven't reported a heartbeat yet. The expression worker.free_vram_mb - worker_leases - self._reserved_vram_mb will raise TypeError: unsupported operand type(s) for -: 'NoneType' and 'int' the moment such a worker is online, defeating the new reservation fix and crashing the admission check. Treat None as "unknown / un-leasable" and continue (mirroring the None-vs-0 contract documented on the dataclass) so this path stays consistent with the lease pre-claim check.

Suggested change
available = worker.free_vram_mb - worker_leases - self._reserved_vram_mb
if worker.free_vram_mb is None:
continue
available = worker.free_vram_mb - worker_leases - self._reserved_vram_mb

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

hognek added a commit to hognek/tinyagentos that referenced this pull request Jul 7, 2026
taOS jaylfc#1706 — TOCTOU fix:
- After eviction-to-make-room frees VRAM, the re-admit in _drain_queue
  used _check_admission (no reservation) instead of _reserve_and_check
  (atomic reservation).  This spawned tasks with no reservation entry,
  re-opening the TOCTOU window and causing _reserved_vram_mb drift.
- Changed to _reserve_and_check so post-eviction admission is atomic
  and properly tracked.
- Also fixes the missing await on evict_lowest_priority (made async
  by the base jaylfc#1705 fix) and sync _evict_task calls in new tests.

Tests: 22/22 arbiter pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tinyagentos/app.py (1)

1149-1149: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant conditional.

resource_scheduler if resource_scheduler is not None else None always evaluates to resource_scheduler.

♻️ Simplify
-            gpu_arbiter = GpuArbiter(
-                scheduler=resource_scheduler if resource_scheduler is not None else None,
+            gpu_arbiter = GpuArbiter(
+                scheduler=resource_scheduler,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/app.py` at line 1149, The conditional expression in the scheduler
assignment is redundant because it always returns the same value; simplify the
argument in the relevant call site by passing resource_scheduler directly
instead of using the “if not None else None” form. Update the code around the
scheduler parameter in the app setup path so the expression is cleaner while
preserving the existing behavior.
tinyagentos/scheduler/discovery.py (1)

190-197: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Comment lists GPU backends not in gpu_backend_types.

The block comment advertises llama-cpp (CUDA build) and sd-cpp/sd-gpu (image-generation) as GPU backends, but gpu_backend_types only contains {"vllm", "ollama", "exo", "mlx"}. As written, a CUDA-built llama-cpp and any image-generation backend are never treated as GPU-capable here (and llama-cpp/sd-cpp are only picked up by the CPU tier below). Either drop the misleading lines from the comment or add the intended types to the set.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/scheduler/discovery.py` around lines 190 - 197, The GPU backend
classification in the discovery logic is inconsistent with the surrounding
comment: `gpu_backend_types` in `discovery.py` only includes `vllm`, `ollama`,
`exo`, and `mlx`, while the comment also claims `llama-cpp` and
`sd-cpp`/`sd-gpu` are GPU-capable. Update the `gpu_backend_types` set in
`gpu_backends` (or trim the comment) so the documented GPU backends and the
actual filter match, ensuring the intended `llama-cpp` and image-generation
backends are treated consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tinyagentos/app.py`:
- Line 1149: The conditional expression in the scheduler assignment is redundant
because it always returns the same value; simplify the argument in the relevant
call site by passing resource_scheduler directly instead of using the “if not
None else None” form. Update the code around the scheduler parameter in the app
setup path so the expression is cleaner while preserving the existing behavior.

In `@tinyagentos/scheduler/discovery.py`:
- Around line 190-197: The GPU backend classification in the discovery logic is
inconsistent with the surrounding comment: `gpu_backend_types` in `discovery.py`
only includes `vllm`, `ollama`, `exo`, and `mlx`, while the comment also claims
`llama-cpp` and `sd-cpp`/`sd-gpu` are GPU-capable. Update the
`gpu_backend_types` set in `gpu_backends` (or trim the comment) so the
documented GPU backends and the actual filter match, ensuring the intended
`llama-cpp` and image-generation backends are treated consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ae14a23b-2a8b-4166-8a69-d943005ad17f

📥 Commits

Reviewing files that changed from the base of the PR and between 02e296a and eb377f6.

📒 Files selected for processing (8)
  • .gitignore
  • tests/test_gpu_arbiter_894.py
  • tests/test_gpu_arbiter_toctou.py
  • tinyagentos/app.py
  • tinyagentos/scheduler/__init__.py
  • tinyagentos/scheduler/discovery.py
  • tinyagentos/scheduler/gpu_arbiter.py
  • tinyagentos/scheduler/types.py

@jaylfc

jaylfc commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Closing as superseded: this branch is a strict git-ancestor of #1718 (verified by commit ancestry — #1705#1706#1707#1718), so its commits are all contained in #1718. Consolidating the arbiter review there; see the plan in #1718. Reopen if I have the ancestry wrong. Thanks @hognek.

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.

2 participants