Skip to content

feat(gpu-arbiter): event-driven admission wakeup replacing 2s poll tick (#1864 A3)#1986

Open
hognek wants to merge 1 commit into
jaylfc:devfrom
hognek:feat/gpu-queue-a3-wakeup
Open

feat(gpu-arbiter): event-driven admission wakeup replacing 2s poll tick (#1864 A3)#1986
hognek wants to merge 1 commit into
jaylfc:devfrom
hognek:feat/gpu-queue-a3-wakeup

Conversation

@hognek

@hognek hognek commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements Slice A3 of #1864: event-driven admission wakeup (locked decision 7).

Replaces the hardcoded asyncio.sleep(2) poll tick in _process_queue with an asyncio.Event-based wake path. A reservation release or task completion now immediately wakes the drain loop, so contended ops admit in << 2 s instead of ≤ 2 s. The 2 s constant becomes the configurable fallback timeout (drain_tick_seconds, default 2.0).

Changes

  • __init__: new drain_tick_seconds: float = 2.0 param, self._wake: asyncio.Event
  • _signal_capacity(): new internal method — self._wake.set()
  • _process_queue: asyncio.wait_for(self._wake.wait(), timeout=drain_tick_seconds) + clear
  • _release_reservation: calls _signal_capacity() on actual reservation release
  • _run_gpu_task finally: calls _signal_capacity() after _running pop
  • tests/test_gpu_arbiter_wakeup.py: 2 new tests

Test results

Targeted: 25/25 pass (2 new + 23 existing regression)
Canonical gate: running (no failures visible)

Summary by CodeRabbit

  • New Features

    • GPU tasks now resume more quickly when VRAM becomes available or another task finishes.
    • Queue processing remains responsive to capacity changes while continuing to check periodically.
  • Bug Fixes

    • Reduced unnecessary delays when queued GPU tasks are waiting for available capacity.
    • Preserved queue draining when capacity changes occur without an explicit wake-up signal.

…ck (jaylfc#1864 A3)

Replace the hardcoded asyncio.sleep(2) in _process_queue with an
asyncio.Event-based wake path.  The drain loop now blocks on
asyncio.wait_for(self._wake.wait(), timeout=drain_tick_seconds) so a
reservation release or task completion immediately wakes the drain
loop.  The 2 s constant becomes the configurable fallback timeout
(drain_tick_seconds, default 2.0).

- __init__: new drain_tick_seconds param, self._wake Event
- _signal_capacity(): new method — calls self._wake.set()
- _process_queue: event-driven wait + clear, fallback timeout
- _release_reservation: calls _signal_capacity on actual release
- _run_gpu_task finally: calls _signal_capacity on completion
- tests/test_gpu_arbiter_wakeup.py: 2 new tests
  * test_release_triggers_immediate_drain (60 s tick, < 0.5 s admit)
  * test_poll_tick_still_drains_without_signal (0.1 s tick)
@hognek
hognek marked this pull request as ready for review July 18, 2026 10:23
@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 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

GpuArbiter now supports event-driven queue draining after VRAM or task state changes, with a configurable polling fallback. New async tests verify prompt admission after release and continued draining when capacity changes without an explicit signal.

Changes

GPU arbiter capacity wakeups

Layer / File(s) Summary
Capacity wakeup signaling
tinyagentos/scheduler/gpu_arbiter.py
Adds drain_tick_seconds and an asyncio wake event, then signals capacity changes after reservation release and task cleanup.
Event-driven queue draining and validation
tinyagentos/scheduler/gpu_arbiter.py, tests/test_gpu_arbiter_wakeup.py
Replaces fixed sleeping with event-or-timeout waiting and tests both immediate wakeups and periodic polling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GPU_Task
  participant GpuArbiter
  participant ReservationManager
  GPU_Task->>GpuArbiter: release reservation or finish
  GpuArbiter->>GpuArbiter: signal _wake
  GpuArbiter->>ReservationManager: process queued admission
  ReservationManager-->>GpuArbiter: report capacity
  GpuArbiter->>GPU_Task: admit queued task
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main GPU arbiter change: replacing the polling tick with event-driven admission wakeup.
✨ 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.

@gitar-bot

gitar-bot Bot commented Jul 18, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

self._paused_at: float | None = None

# ── taOS #1864 A3: event-driven wakeup ───────────────────────────
self._drain_tick_seconds = drain_tick_seconds

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: drain_tick_seconds is not validated and is passed straight into asyncio.wait_for(..., timeout=self._drain_tick_seconds). asyncio.wait_for raises ValueError if timeout <= 0, so a caller passing 0 or a negative value will crash the entire _process_queue drain loop on startup. Consider clamping/validating the value (e.g. max(drain_tick_seconds, 0.01) or raising a clear error in __init__).


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

async with self._running_lock:
entry = self._running.pop(task.id, None)
self._running_tasks.pop(task.id, None)
self._signal_capacity() # taOS #1864 A3: wake drain on completion

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: _run_gpu_task's finally calls _release_reservation (line 444), which already invokes _signal_capacity() at line 250, and then calls _signal_capacity() again here. The wake fires twice for every normal task completion. Event.set() is idempotent so this is harmless today, but the redundancy is misleading and could break a future wait_for-with-count design. Consider dropping one of the two calls (e.g. let _release_reservation own the signal and remove this line).


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: 2 Issues Found | Recommendation: Address before merge

Overview

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

SUGGESTION

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 137 drain_tick_seconds passed unvalidated into asyncio.wait_for; 0/negative raises ValueError and crashes drain loop on startup
tinyagentos/scheduler/gpu_arbiter.py 448 Redundant double _signal_capacity() (also fired by _release_reservation at line 250) on every task completion
Files Reviewed (2 files)
  • tinyagentos/scheduler/gpu_arbiter.py - 2 issues
  • tests/test_gpu_arbiter_wakeup.py - 0 issues

Fix these issues in Kilo Cloud


Reviewed by hy3:free · Input: 39.4K · Output: 2.9K · Cached: 107.5K

@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/scheduler/gpu_arbiter.py (2)

543-551: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Signal capacity on queue insertion and unpausing to maximize responsiveness.

To fully achieve the PR's goal of admitting contended operations in less than 2 seconds, consider waking the drain loop when new tasks are queued and when the arbiter is resumed. Currently, if a high-priority task is queued (e.g., due to insufficient capacity requiring eviction) or the arbiter is resumed, processing is still delayed by up to the drain_tick_seconds fallback.

Calling self._signal_capacity() in submit_gpu (after queue insertion) and resume ensures prompt preemption and processing.

🤖 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/gpu_arbiter.py` around lines 543 - 551, Update
submit_gpu to call _signal_capacity immediately after inserting a task into the
queue, and update resume to call _signal_capacity when the arbiter is unpaused.
Preserve the existing _signal_capacity behavior while ensuring both newly queued
work and resumed processing wake the drain loop without waiting for
drain_tick_seconds.

552-561: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Catch and log unexpected exceptions to prevent the queue processor from silently dying.

If await self._drain_queue() raises an unexpected exception, the while True loop will break, and the background _queue_processor_task will silently terminate. The arbiter will then permanently stop processing queued tasks.

Consider wrapping the queue processing step in a generic exception handler to log the error and allow the loop to continue.

🛡️ Proposed fix to ensure resilience
                 self._wake.clear()
                 if not self._paused:
-                    await self._drain_queue()
+                    try:
+                        await self._drain_queue()
+                    except Exception as e:
+                        logger.exception("gpu-arbiter: unexpected error in queue processor: %s", e)
         except asyncio.CancelledError:
             raise
🤖 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/gpu_arbiter.py` around lines 552 - 561, Update
_process_queue so unexpected exceptions from _drain_queue are caught by a
generic exception handler, logged with the existing arbiter logging mechanism,
and followed by continuing the while loop. Preserve the current cancellation and
normal queue-processing behavior, ensuring _queue_processor_task does not
terminate silently after a drain failure.
🤖 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/scheduler/gpu_arbiter.py`:
- Around line 543-551: Update submit_gpu to call _signal_capacity immediately
after inserting a task into the queue, and update resume to call
_signal_capacity when the arbiter is unpaused. Preserve the existing
_signal_capacity behavior while ensuring both newly queued work and resumed
processing wake the drain loop without waiting for drain_tick_seconds.
- Around line 552-561: Update _process_queue so unexpected exceptions from
_drain_queue are caught by a generic exception handler, logged with the existing
arbiter logging mechanism, and followed by continuing the while loop. Preserve
the current cancellation and normal queue-processing behavior, ensuring
_queue_processor_task does not terminate silently after a drain failure.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 77a5f5bb-66b6-4731-b4f7-414c5727291d

📥 Commits

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

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

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