Skip to content

fix(gpu-arbiter): op kinds, shadow queue, cancel_op, position, + 4 pre-deploy defect fixes (#1864)#1984

Open
hognek wants to merge 8 commits into
jaylfc:devfrom
hognek:feat/gpu-queue-a2-ops
Open

fix(gpu-arbiter): op kinds, shadow queue, cancel_op, position, + 4 pre-deploy defect fixes (#1864)#1984
hognek wants to merge 8 commits into
jaylfc:devfrom
hognek:feat/gpu-queue-a2-ops

Conversation

@hognek

@hognek hognek commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

GPU arbiter operational improvements and pre-deploy defect fixes, all gated behind TAOS_GPU_QUEUE (off by default):

Earlier commits (A2 operations slice)

  • Op kinds (inference / load) — validated on submit, exposed in queue snapshot + position
  • Shadow queue dict (_queued_entries) — O(1) lookups for cancel_op, position, snapshot
  • cancel_op — cancel queued tasks with VRAM reservation release; evict running tasks; atomic across physical queue + shadow dict + tombstones
  • queue_position — 1-based position, global for loads, per-model for inference
  • queue_snapshot — non-destructive read with position + op/model/backend fields
  • Cancellation-toctou fix — re-check _cancelled_ids after await windows in drain + retry loops

New (pre-deploy defect fixes, #1864)

  1. Queue resilience_process_queue wraps _drain_queue in try/except so one bad task doesn't permanently kill the loop.
  2. None guard_check_cluster_admission skips non-NVIDIA workers (free_vram_mb is None).
  3. resource_id threading — Added to _QueuedGpuTask, threaded through submit_gpu_drain_queue_run_gpu_task, so drained tasks hold a cluster lease on the correct resource.
  4. Lease renewal_renew_lease_loop background coroutine calls renew_lease every 200 s; stops in finally.

Testing

  • 36/36 GPU arbiter tests pass (894, TOCTOU, queue_ops, queue_flag)
  • 26/26 cluster tests pass
  • 10/10 scheduler tests pass
  • Total: 72/72 related tests, no regressions

Task: t_68584724 (defect fixes), t_c34f79c7 (cancellation atomicity), prior A2 work
Fixes: #1864

@hognek
hognek marked this pull request as ready for review July 18, 2026 09:54
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@hognek, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dbf157c6-a516-4dc2-b7df-e9d6f497b226

📥 Commits

Reviewing files that changed from the base of the PR and between dc0a631 and cad6bd0.

📒 Files selected for processing (1)
  • tinyagentos/scheduler/gpu_arbiter.py
📝 Walkthrough

Walkthrough

GpuArbiter now carries operation metadata through submission and queueing, computes operation-aware queue positions, exposes non-destructive snapshots, and supports cancellation of queued or running tasks with VRAM cleanup.

Changes

GPU queue operations

Layer / File(s) Summary
Submission metadata and admission
tinyagentos/scheduler/gpu_arbiter.py, tests/test_gpu_arbiter_queue_ops.py
submit_gpu accepts validated operation, model, and backend metadata while preserving the legacy call shape; queued entries retain these fields.
Cancellation-aware queue draining
tinyagentos/scheduler/gpu_arbiter.py, tests/test_gpu_arbiter_queue_ops.py
Queued cancellations are tracked across reservation waits, removed from admission, and running-task cancellation delegates to eviction with VRAM release.
Queue ordering and snapshots
tinyagentos/scheduler/gpu_arbiter.py, tests/test_gpu_arbiter_queue_ops.py
Queue positions use global load ordering or per-model inference ordering, and snapshots expose metadata without draining the queue.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant GpuArbiter
  participant VramReservationManager
  participant TaskFuture
  Caller->>GpuArbiter: submit_gpu(task, op, model, backend_name)
  GpuArbiter->>VramReservationManager: reserve required VRAM
  VramReservationManager-->>GpuArbiter: admission result
  GpuArbiter->>TaskFuture: queue or run task
  Caller->>GpuArbiter: cancel_op(task_id)
  GpuArbiter->>TaskFuture: cancel queued future
  GpuArbiter->>VramReservationManager: release running-task reservation
Loading

Possibly related PRs

  • jaylfc/taOS#1869: Implements related unified GPU queue metadata, ordering, and cancellation behavior.
  • jaylfc/taOS#1986: Also modifies the GPU arbiter’s queue-drain and task-promotion flow.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds load op handling, shadow queue tracking, and eviction-aware cancellation aligned with routing model loads through GpuArbiter.
Out of Scope Changes check ✅ Passed The added tests and arbiter refactor stay centered on GPU queue behavior and do not introduce clear unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main GPU arbiter queue and cancellation changes, though it is a bit noisy.
✨ 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.

@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 →

@gitar-bot

gitar-bot Bot commented Jul 18, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

Comment thread tinyagentos/scheduler/gpu_arbiter.py Outdated
if entry.task.id in self._cancelled_ids:
self._cancelled_ids.discard(entry.task.id)
continue
self._queued_entries.pop(entry.task.id, 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.

WARNING: Shadow dict _queued_entries is dropped on line 579 but only re-added at line 624 (in the retry re-queue), and _drain_queue awaits asyncio.to_thread at line 580 between them. During that await window the task is still logically queued in self._queue but ABSENT from _queued_entries. A concurrent cancel_op(task_id) will find entry is None, fall through to the running check, find the task is not running, and return False even though the task is genuinely queued. queue_position/queue_snapshot also report it as not-queued mid-drain.

This makes cancel_op and the position/snapshot reads racy against the 2s drain cycle. Consider keeping _queued_entries authoritative and only mutating it atomically with self._queue (e.g. re-add the entry to _queued_entries when it goes into the local retry list, or guard the whole drain step under the same lock used elsewhere), so reads/cancellation stay consistent.


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

try:
return await done
except asyncio.CancelledError:
self._queued_entries.pop(task.id, 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: Submitter-side cancellation (the await done raising CancelledError) pops _queued_entries here but leaves the entry in the real self._queue. Nothing re-checks _cancelled_ids for an entry cancelled this way, so on the next drain that entry is admitted and _run_gpu_task starts — running a task whose submitter already gave up (its _arbiter_future is discarded via _propagate's ct.cancelled() early-return). Consider also recording the id in _cancelled_ids in this branch (or removing the entry from self._queue) so the orphaned queue entry is skipped rather than executed.


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

self, task: Task, required_vram_mb: int = 0,
evictable: bool = False, resource_id: str | None = None,
required_gpu_arch: str | None = None,
op: str = "inference",

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: op is accepted unchecked. Any value other than "inference" silently falls into the else (global) branch of queue_position (line 664), so a typo like op="infere" or op="LOAD" is treated as a load and counted globally rather than flagged. Consider validating against the allowed set {"inference", "load"} in submit_gpu (and/or normalizing case) to fail fast on misuse. Also note: inference tasks with model=None are counted as the same model (line 665), so two model=None inference tasks occupy positions 1 and 2 behind each other — verify that is the intended per-model semantics when model is unset.


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

@kilo-code-bot

kilo-code-bot Bot commented Jul 18, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Incremental review of commit cad6bd0 (HEAD) vs previous review commit 9680011. The incremental diff reworks _process_queue to replace the permanent return (after MAX_CONSECUTIVE_FAILURES) with a break to an outer restart loop that waits COOLDOWN_BACKOFF and restarts the processor. The prior WARNING about the permanent return is now RESOLVED. One new SUGGESTION was introduced by the newly added COOLDOWN_BACKOFF constant.

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

SUGGESTION

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 625 COOLDOWN_BACKOFF = 300 (5 min) imposes a full admission freeze after MAX_CONSECUTIVE_FAILURES; combined with the in-loop backoff this causes a 5-minute admission starvation window even for transient failures.
Files Reviewed (1 file)
  • tinyagentos/scheduler/gpu_arbiter.py - 1 issue (incremental); 1 prior issue resolved

Fix these issues in Kilo Cloud

Previous Review Summaries (7 snapshots, latest commit 9680011)

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

Previous review (commit 9680011)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Incremental review of commit 9680011 (HEAD) vs previous review commit 1331ea0. The incremental diff adds lease-loss cancellation to _renew_lease_loop (cancels the running task when the lease expires/fails to renew) and replaces the bare except Exception: in _process_queue with a narrowed catch plus a consecutive-failure backoff/threshold. Both prior SUGGESTION findings (lease-loss detection at line 443, bare-except at line 617) are now RESOLVED by this code. One new WARNING was introduced by the threshold logic.

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

WARNING

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 646 Permanent return after MAX_CONSECUTIVE_FAILURES kills the queue processor with no restart path — transient failures cause permanent admission starvation.
Files Reviewed (1 file)
  • tinyagentos/scheduler/gpu_arbiter.py - 1 issue (incremental); 2 prior issues resolved

Fix these issues in Kilo Cloud

Previous review (commit 1331ea0)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Incremental review of commit 1331ea0 (HEAD) vs previous review commit c41ac97. The incremental diff adds lease-renewal support to GpuArbiter (resource_id plumbing, _renew_lease_loop, finally cleanup), guards non-NVIDIA workers in admission, and wraps _drain_queue in an exception handler. Two SUGGESTION-level issues were found in the changed code.

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

SUGGESTION

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 438 Lease-expiry mid-renewal returns but the running GPU task keeps executing with no active lease — the cluster may have already reclaimed the resource.
tinyagentos/scheduler/gpu_arbiter.py 617 Bare except Exception: around _drain_queue() swallows all errors (incl. programming bugs) and retries every 2s, masking persistent defects.
Files Reviewed (2 files)
  • tinyagentos/scheduler/gpu_arbiter.py - 2 issues (incremental)
  • tests/test_gpu_arbiter_queue_ops.py - no changes in incremental diff

Fix these issues in Kilo Cloud

Previous review (commit c41ac97)

Status: No Issues Found | Recommendation: Merge

Overview

Incremental review of commit c41ac97 (HEAD) vs previous review commit 53a7f53. The incremental diff touches only tinyagentos/scheduler/gpu_arbiter.py (two hunks); the test file had no changes. Both hunks add cancel-race handling and are sound:

  • Lines 647-654 (_drain_queue re-queue loop): after put_nowait + shadow dict insert, a re-check of _cancelled_ids pops the shadow entry and discards the cancelled id. This closes the race where cancel_op fired between the pre-insert check and the insert, which would otherwise resurrect the shadow entry and leak a queue slot. The physical PriorityQueue entry cannot be removed, but is correctly skipped at dequeue time by _drain_queue (per comment).
  • Lines 705-708 (cancel_op): releases the VRAM reservation immediately rather than leaking it until _drain_queue eventually skips the entry. _release_reservation is idempotent, so this is safe when no reservation exists.

Both changes mirror the existing admitted-path handling (lines 603-606) and the retry-branch cancel re-check (lines 641-643), giving symmetric coverage of the cancellation race across all branches.

All 5 previously-active findings (lines 313, 376, 631, 712, N/A) are outside the incremental change scope and remain unchanged; none are reintroduced or affected by the new code.

Files Reviewed (2 files)
  • tinyagentos/scheduler/gpu_arbiter.py - incremental: 0 new issues
  • tests/test_gpu_arbiter_queue_ops.py - no changes in incremental diff

Previous review (commit 53a7f53)

Status: No Issues Found | Recommendation: Merge

Overview

Incremental review of commit 53a7f53 (PATCH 4/4): added a _cancelled_ids re-check in the _drain_queue re-queue loop (gpu_arbiter.py:638-643), mirroring the admitted-path re-check at lines 603-606.

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0

Notes on the incremental change

  • The new re-check correctly discards the cancelled id and continues before the _queue.full() drop path, so a task cancelled during the admission-await window is neither re-queued nor dropped with a spurious NoResourceAvailableError.
  • Reservation was already released at line 635 before retry.append, and _release_reservation is idempotent, so the cancel path introduces no double-release hazard.
  • Behavior is symmetric with the admitted-path handling (lines 603-606); cancellation during the drain await window is now handled on both the admitted and the retry branches.
Files Reviewed (1 file)
  • tinyagentos/scheduler/gpu_arbiter.py - incremental: 0 new issues

Previous review (commit dc0a631)

Status: No Issues Found | Recommendation: Merge

Overview

Incremental review of commit dc0a6311 (PATCH 3/3): added a _cancelled_ids re-check after the _reserve_and_check await window in _drain_queue (gpu_arbiter.py:596-606).

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0

Resolved in this incremental diff

  • SUGGESTION (line 621/631): cancel_op racing with in-progress _drain_queue admission — RESOLVED. The new re-check at lines 596-606 calls _release_reservation and continues, so a task cancelled during the await window is no longer admitted/spawned.

Resolved by earlier commits in this PR (carried from prior review)

  • WARNING (line 579): shadow dict dropped during drain await window — RESOLVED. PATCH 2/3 stopped popping _queued_entries at line 583; the entry now stays authoritative through the await window and is only popped at line 631 after promotion. cancel_op/queue_position/queue_snapshot remain consistent mid-drain.
  • SUGGESTION (line 376): orphaned queue entry on submitter cancel — RESOLVED. The CancelledError branch now adds the id to _cancelled_ids (line 377), so the drain loop skips it at line 583.
  • SUGGESTION (line 313): op accepted unchecked — RESOLVED. submit_gpu now raises ValueError for unknown op (lines 332-335).
Files Reviewed (2 files)
  • tinyagentos/scheduler/gpu_arbiter.py - incremental: 0 new issues (1 prior resolved)
  • tests/test_gpu_arbiter_queue_ops.py - not changed in this incremental diff

Previous review (commit 4374588)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

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

SUGGESTION

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 621 cancel_op racing with an in-progress _drain_queue admission (after the _cancelled_ids check at line 583) can still admit and spawn _run_gpu_task for a cancelled task; the future is cancelled but the workload runs.
Previously reported issues — resolved by this incremental diff
  • gpu_arbiter.py:313 (op unchecked) — FIXED: submit_gpu now raises ValueError for unknown op (lines 332-335).
  • gpu_arbiter.py:376 (orphaned queue entry on submitter cancel) — FIXED: line 377 now adds the id to _cancelled_ids, so the drain loop skips it (line 583).
  • gpu_arbiter.py:579 (shadow dict dropped during drain race) — RESOLVED: the pop moved to after promotion (line 621); the entry stays authoritative through the await window and retry re-adds it at line 631.
Files Reviewed (2 files)
  • tinyagentos/scheduler/gpu_arbiter.py - 1 new issue + 3 resolved
  • tests/test_gpu_arbiter_queue_ops.py - no issues in changed lines

Fix these issues in Kilo Cloud

Previous review (commit d441658)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 579 Shadow dict _queued_entries is dropped during drain (line 579) and only re-added at line 624 after an awaiting call; a concurrent cancel_op/queue_position/queue_snapshot mid-drain misses a genuinely-queued task, so cancel_op can wrongly return False.

SUGGESTION

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 370 Submitter-side CancelledError pops _queued_entries but leaves the entry in self._queue; the orphaned entry is later admitted and runs despite the submitter giving up.
tinyagentos/scheduler/gpu_arbiter.py 313 op is unvalidated; non-"inference" values silently fall into the global branch of queue_position, and model=None inference tasks are counted together.
Files Reviewed (2 files)
  • tinyagentos/scheduler/gpu_arbiter.py - 3 issues
  • tests/test_gpu_arbiter_queue_ops.py - 0 issues (6 new tests look reasonable; note cancelled futures are never awaited, which may emit "Task destroyed but pending" warnings)

Fix these issues in Kilo Cloud


Reviewed by hy3:free · Input: 35K · Output: 2.6K · Cached: 111.1K

…date op

- _drain_queue: keep entry in _queued_entries during admission check so
  concurrent cancel_op finds it (was popped too early before asyncio.to_thread).
  Pop from shadow dict only after admission is confirmed (line 621).
- submit_gpu CancelledError handler: add task_id to _cancelled_ids so the
  drain loop skips the orphaned queue entry instead of admitting it.
- submit_gpu: validate op against {'inference','load'}, raising ValueError
  for unknown values so typos don't silently fall through to global branch.

Kilo review items on PR jaylfc#1984 (GPU queue A2).
Targeted: 29/29 pass.
@hognek

hognek commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Kilo review fixes (commit 4374588)

Three issues from Kilo at 09:58:

  1. Shadow dict drain window — Moved _queued_entries.pop from before _reserve_and_check to after admission is confirmed. During the asyncio.to_thread gap, the entry remains findable by cancel_op via the shadow dict.

  2. Orphan task on submitter cancelCancelledError handler now adds task_id to _cancelled_ids, so the drain loop skips the entry (which remains in the real self._queue since PriorityQueue is append-only).

  3. Op validationsubmit_gpu now validates op against {"inference", "load"}, raising ValueError for unknown values so typos like "LOAD" don't silently fall through to the global branch.

Targeted tests: 29/29 pass.


t.add_done_callback(_propagate)

self._queued_entries.pop(entry.task.id, None) # promoted → _running

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: cancel_op (line 678) pops the task from _queued_entries and cancels its future, but _drain_queue already passed the _cancelled_ids check at line 583 and still holds the entry object locally. If cancel_op runs in the window between line 583 and here, the entry is gone from _queued_entries so the check at 583 won't skip it on the next drain, but on the current drain the task is still admitted and _run_gpu_task is spawned (line 600) and runs to completion — despite cancellation. The future is cancelled, yet a background task executes a workload the caller abandoned.

Consider re-checking self._cancelled_ids (or re-confirming presence in _queued_entries) after the admission await at line 586 and before spawning _run_gpu_task, or have cancel_op also signal running/in-flight drain intent under self._running_lock, so an in-progress admission honors a late cancel.


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

…ancelled_ids after await

cancel_op pops from _queued_entries and cancels the future while
_drain_queue is suspended on _reserve_and_check — the entry is still
admitted despite cancellation.  Add a second _cancelled_ids check after
the await window so a concurrent cancel_op is caught before the task is
spawned.

Kilo SUGGESTION on PR jaylfc#1984 — task t_27501d36

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_gpu_arbiter_queue_ops.py (1)

82-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover cancellation consistency across both queue representations.

Add regressions that:

  • cancel with max_queue_size=1 while processing is paused/not started, then successfully enqueue another task;
  • cancel during a failed asynchronous admission check and verify snapshots remain empty after the next drain.
🤖 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 `@tests/test_gpu_arbiter_queue_ops.py` around lines 82 - 92, Extend
cancellation regression coverage around GpuArbiter.cancel_op: add a
max_queue_size=1 case with processing paused or not started, cancel the queued
task, and verify a replacement task can be enqueued; also add a case cancelling
during a failed asynchronous admission check, then drain and assert both queue
representations and snapshots remain empty. Reuse existing arbiter submission,
cancellation, drain, and snapshot helpers.
🤖 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.

Inline comments:
In `@tinyagentos/scheduler/gpu_arbiter.py`:
- Around line 688-695: Make cancellation atomic across the physical queue,
shadow entries, and tombstones: update cancel_op to reclaim or compact the
physical queued entry immediately so capacity is reusable, and update the
admission retry/reinsertion flow around the failed-await path to skip
reinsertion when cancellation occurred. In tests/test_gpu_arbiter_queue_ops.py
lines 82-92, add coverage for immediate capacity reuse and cancellation during
failed admission.

---

Nitpick comments:
In `@tests/test_gpu_arbiter_queue_ops.py`:
- Around line 82-92: Extend cancellation regression coverage around
GpuArbiter.cancel_op: add a max_queue_size=1 case with processing paused or not
started, cancel the queued task, and verify a replacement task can be enqueued;
also add a case cancelling during a failed asynchronous admission check, then
drain and assert both queue representations and snapshots remain empty. Reuse
existing arbiter submission, cancellation, drain, and snapshot helpers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 71342dc9-67e6-40cd-9dd5-e061233a8bbc

📥 Commits

Reviewing files that changed from the base of the PR and between 0e6e6fa and dc0a631.

📒 Files selected for processing (2)
  • tests/test_gpu_arbiter_queue_ops.py
  • tinyagentos/scheduler/gpu_arbiter.py

Comment thread tinyagentos/scheduler/gpu_arbiter.py
Same race as the admission-path fix: cancel_op may fire during the
admission-await window (failed-admission path).  The retry list re-queues
cancelled entries because it never checks _cancelled_ids.  Add the guard
so cancelled tasks aren't resurrected.

CodeRabbit finding on PR jaylfc#1984
@hognek

hognek commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Applied the Kilo SUGGESTION (task t_27501d36): two fixes for the cancel_op/_drain_queue race.

Fix 1 — Admission path (line 603): Re-check _cancelled_ids after the _reserve_and_check await returns. If cancel_op fired during the await window, the cancelled entry is discarded with reservation released rather than admitted.

Fix 2 — Retry/re-queue path (line 641): Same _cancelled_ids guard in the failed-admission retry loop. Cancelled entries that landed in the retry list during the admission-await window are skipped instead of being resurrected back into _queued_entries.

Both fixes follow the existing _cancelled_ids + discard pattern from the initial check at line 583.

Tests: 22/22 targeted (test_gpu_arbiter_queue_ops + test_gpu_arbiter_894) pass.

hognek added 2 commits July 18, 2026 12:52
…dow queue, tombstones

- cancel_op: release VRAM reservation immediately so capacity is freed
  rather than leaking until _drain_queue eventually skips the entry.
- retry/re-queue loop: re-check _cancelled_ids after put_nowait+dict
  insert to prevent TOCTOU resurrection of cancelled shadow entries.

CodeRabbit finding on PR jaylfc#1984 (GPU queue A2).
…uard, resource_id threading, lease renewal

- _process_queue: wrap _drain_queue in try/except so one bad task
  cannot permanently kill the queue processor loop (defect 1).
- _check_cluster_admission: skip workers whose free_vram_mb is None
  (non-NVIDIA workers) to prevent TypeErrors (defect 2).
- _drain_queue: thread resource_id through _QueuedGpuTask so drained
  tasks hold a cluster lease on the correct resource (defect 3).
- _run_gpu_task: add _renew_lease_loop background coroutine that
  periodically calls renew_lease every 200 s so the lease never
  expires mid-run (defect 4).

Fixes: jaylfc#1864
@hognek hognek changed the title feat(gpu-queue): op kinds, shadow queue dict, position, cancel_op on GpuArbiter (#1864) fix(gpu-arbiter): op kinds, shadow queue, cancel_op, position, + 4 pre-deploy defect fixes (#1864) Jul 18, 2026
renewed = await self._cluster_manager.renew_lease(
lease_id, ttl_seconds=300,
)
if renewed 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.

SUGGESTION: When renew_lease returns None the loop returns and the renew task ends, but _run_gpu_task keeps executing the GPU workload with no active lease. The cluster manager may have already revoked/reclaimed the VRAM or device for that lease, so the still-running task could silently lose its resource mid-inference and fail or corrupt state. Consider signaling the running task (e.g. set a flag the run loop checks, or cancel the task) when the lease cannot be renewed, rather than only logging and returning.


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

Comment thread tinyagentos/scheduler/gpu_arbiter.py Outdated
await self._drain_queue()
try:
await self._drain_queue()
except Exception:

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: The bare except Exception: around _drain_queue() swallows every error — including programming bugs like KeyError/AttributeError/TypeError — and the 2s loop then retries forever. A persistent bug would spam logs every 2 seconds and stay masked indefinitely. Consider narrowing the catch (or at least logging exc_info/repr(e) and adding a backoff / consecutive-failure threshold) so real defects surface instead of being hidden behind logger.exception.


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

…rror handling

Two Kilo SUGGESTIONS from PR jaylfc#1984 review:

1. gpu_arbiter.py:438 — When renew_lease returns None, the renew loop
   silently returns but _run_gpu_task keeps executing GPU work with no
   active lease. Fix: _renew_lease_loop now accepts an optional
   task_to_cancel parameter and cancels the parent asyncio Task on
   renewal failure or expiry.

2. gpu_arbiter.py:617 — Bare except Exception around _drain_queue()
   swallows programming bugs (KeyError, TypeError, etc.) and retries
   forever in a tight 2s loop. Fix: narrow to transient errors
   (NoResourceAvailableError, TimeoutError, OSError), add exponential
   backoff (2s→60s), and stop queue processor after 10 consecutive
   failures.
Comment thread tinyagentos/scheduler/gpu_arbiter.py Outdated
"times — stopping queue processor",
consecutive_failures,
)
return

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: Permanent return on MAX_CONSECUTIVE_FAILURES permanently kills the queue processor.

_process_queue is started exactly once in start() (line 145) and there is no supervisor/restart logic for _queue_processor_task. Previously the loop swallowed errors and retried forever; now, after 10 consecutive (transient) NoResourceAvailableError/TimeoutError/OSError — e.g. a temporarily unavailable cluster manager, a brief network partition, or a maintenance window — the processor returns and the GPU admission queue is silently dead for the rest of the process lifetime. No queued tasks are ever admitted again, with no log line saying the processor exited for a recoverable reason beyond one logger.critical.

Consider either (a) not permanently exiting — reset consecutive_failures and back off but keep the loop alive, or (b) if a hard stop is intended, surface a recoverable restart path (supervisor task or a health check that re-calls start()) so transient failures don't cause permanent admission starvation.

…RES instead of dying permanently

Previously _process_queue returned after 10 consecutive transient failures
(NoResourceAvailableError, TimeoutError, OSError), permanently killing the
queue processor with no restart logic.  A temporary cluster outage or GPU
restart would kill the queue forever until manual intervention.

Wrap the inner drain loop in an outer restart loop that waits a 300s
cooldown after hitting the failure limit, then re-enters the inner loop
with a fresh failure counter.  CancelledError propagation is preserved.

Kilo WARNING jaylfc#1984
@hognek

hognek commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Addressed Kilo WARNING: queue processor permanently dies after MAX_CONSECUTIVE_FAILURES.

What changed: Wrapped the inner drain loop in an outer restart loop. After 10 consecutive transient failures (NoResourceAvailableError, TimeoutError, OSError), the processor now waits a 300s cooldown then restarts the inner loop with a fresh failure counter — instead of returning permanently. CancelledError propagation is preserved.

Before: return on line 646 — queue processor dead forever until manual restart.
After: break from inner loop → 300s cooldown → outer loop restarts.

All 29 GPU arbiter tests + 192 scheduler/cluster tests pass.

async def _process_queue(self) -> None:
try:
MAX_CONSECUTIVE_FAILURES = 10
COOLDOWN_BACKOFF = 300 # seconds to wait before restarting the processor

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: COOLDOWN_BACKOFF = 300 (5 min) imposes a full admission freeze after the inner loop exhausts MAX_CONSECUTIVE_FAILURES. Combined with the in-loop exponential backoff (capped at 60s), this means after ~10 failures the queue stops admitting for 5 minutes, even if the underlying failure was transient (e.g. a brief cluster blip). Consider making this backoff shorter, jittered, or exponential/limited so transient failures don't cause a 5-minute admission starvation window.


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

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