feat(TaskScheduler): introduce semaphore-based task scheduler (Epic 3, Story 3.1) - #1031
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds idempotent task execution, a semaphore-backed ChangesTask scheduling
Estimated code review effort: 3 (Moderate) | ~30 minutes Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant ClineProvider
participant TaskScheduler
participant Task
ClineProvider->>TaskScheduler: schedule(task, task.run)
TaskScheduler->>TaskScheduler: acquire semaphore permit
TaskScheduler->>Task: run()
Task-->>TaskScheduler: resolve or reject
TaskScheduler->>TaskScheduler: release concurrency permit
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@src/core/webview/ClineProvider.ts`:
- Around line 3191-3199: Update createTaskWithHistoryItem so its implicit task
startup uses taskScheduler.schedule(task, () => task.run()) instead of invoking
task.run directly, preserving the startTask: false opt-out and matching
createTask’s concurrency-gated startup behavior.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9116becd-95dc-49c3-a4a2-4822b1a6ac95
📒 Files selected for processing (9)
AGENTS.mdsrc/__tests__/provider-delegation.spec.tssrc/__tests__/single-open-invariant.spec.tssrc/core/task/Task.tssrc/core/task/TaskScheduler.tssrc/core/task/__tests__/Task.dispose.test.tssrc/core/task/__tests__/TaskScheduler.spec.tssrc/core/webview/ClineProvider.tssrc/eslint-suppressions.json
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/core/task/Task.ts (1)
1886-1909: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winReturn a completion-bound promise when
run()is called after auto-start.
TaskScheduler.schedule(..., () => task.run())awaits its returned promise to gate concurrency, but for tasks withstartTask: true,run()returnsPromise.resolve()immediately because_startedis set without assigning_runPromise. Add a completion promise/path there so scheduled tasks cannot let the gate release before the task actually completes.🤖 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 `@src/core/task/Task.ts` around lines 1886 - 1909, Update Task.run() so the _started branch returns a promise bound to the already auto-started task’s actual completion instead of Promise.resolve(). Ensure the auto-start path assigns or exposes _runPromise consistently with the normal run flow, while preserving idempotent returns for subsequent calls and allowing TaskScheduler to await completion.
🤖 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.
Outside diff comments:
In `@src/core/task/Task.ts`:
- Around line 1886-1909: Update Task.run() so the _started branch returns a
promise bound to the already auto-started task’s actual completion instead of
Promise.resolve(). Ensure the auto-start path assigns or exposes _runPromise
consistently with the normal run flow, while preserving idempotent returns for
subsequent calls and allowing TaskScheduler to await completion.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3f606e03-9e62-4dcb-ad63-8eb02e1905d2
📒 Files selected for processing (6)
apps/vscode-e2e/src/fixtures/subtasks.tsapps/vscode-e2e/src/suite/subtasks.test.tssrc/__tests__/single-open-invariant.spec.tssrc/__tests__/task-run-dispatch.spec.tssrc/core/task/Task.tssrc/core/webview/ClineProvider.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/core/webview/ClineProvider.ts
…ire-and-forget scheduling
…ire-and-forget scheduling
Related GitHub Issue
Closes: #358
Description
Introduces
TaskScheduler, a semaphore-based concurrency gate that wraps the existingTaskSemaphoreutility, and wires it intoClineProvideras the single launch path for all tasks.Key changes:
TaskScheduler(src/core/task/TaskScheduler.ts): thin wrapper aroundTaskSemaphore;schedule(task, run)acquires a permit, checkstask.abort || task.abandonedbefore executing, and always releases infinally. Ships atmaxConcurrency=1(identical to current serial behaviour — raising it later enables fan-out without touching gate logic).cancelQueued()is called fromClineProvider.dispose()to drain the semaphore on teardown.Task.run()(src/core/task/Task.ts): awaitable twin ofTask.start(); returns the in-flight_runPromiseif already running, or a resolved promise if_startedis set (guards against double-invocation from constructor,start(), or a secondrun()call).ClineProvidernow callstask.run()through the scheduler instead oftask.start()directly.ClineProvider(src/core/webview/ClineProvider.ts): holds aTaskSchedulerinstance; all task launch sites (createTask,delegateParentAndOpenChild) usevoid this.taskScheduler.schedule(task, () => task.run()).dispose()callscancelQueued()before clearing the stack.Tests:
TaskScheduler.spec.ts(8 cases: immediate run, serial queuing, parallel at maxConcurrency=2, permit release on throw, cancelQueued, abort guard, abandoned guard, default concurrency);Task.dispose.test.tsgains aTask.run() idempotencydescribe (3 cases);provider-delegation.spec.tsandsingle-open-invariant.spec.tsupdated to carrytaskScheduleron provider stubs.AGENTS.md: adds an ESLint Suppressions section documenting that counts must never increase, when
as anyis acceptable, and the prune-suppressions maintenance command.eslint-suppressions.jsoncount forTask.dispose.test.tsreduced from 8 → 3.No behaviour change at
maxConcurrency=1— tasks still run serially. The scheduler is the expand step of an expand-contract migration; fan-out (Story 3.2b) raises the limit without changing this gate logic.Test Procedure
Pre-Submission Checklist
Summary by CodeRabbit
run()execution for tasks.run()behavior.AGENTS.mdand adjusted suppression counts.