[DEV-79] Enable bounded parallel execution across repositories in workspace mode - #47
[DEV-79] Enable bounded parallel execution across repositories in workspace mode#47danii1 wants to merge 8 commits into
Conversation
…ositories in workspace mode
|
|
1 similar comment
|
|
# Conflicts: # docs/code/workspaces.md # packages/code/src/lib/webhook-queue.ts # packages/code/src/lib/workspace/config.ts # packages/code/src/lib/workspace/state.ts # packages/code/src/lib/workspace/workspace-worker.ts # packages/code/tests/workspace-worker.test.ts
|
🔀 This branch had merge conflicts with |
# Conflicts: # packages/code/CHANGELOG.md # packages/code/src/lib/task-polling-acquirer.ts
|
🔀 This branch had merge conflicts with |
# Conflicts: # packages/code/CHANGELOG.md
|
🔀 This branch had merge conflicts with |
# Conflicts: # packages/code/CHANGELOG.md
|
🔀 This branch had merge conflicts with |
# Conflicts: # docs/code/workspaces.md # packages/code/CHANGELOG.md # packages/code/src/lib/workspace/config.ts # packages/code/src/lib/workspace/init.ts # packages/code/src/lib/workspace/workspace-worker.ts # packages/code/src/worker.ts # packages/code/tests/workspace-config.test.ts
|
🔀 This branch had merge conflicts with |
# Conflicts: # packages/code/CHANGELOG.md # packages/code/src/lib/task-polling-acquirer.ts # packages/code/src/lib/workspace/workspace-worker.ts
# Conflicts: # packages/code/CHANGELOG.md # packages/code/src/lib/workspace/workspace-worker.ts # packages/code/src/worker.ts
|
|
Task: DEV-79
Summary: Enable bounded parallel execution across repositories in workspace mode
Implementation Details
No usable partial work exists (branches/stashes are for other tasks). Starting fresh from current HEAD. Let me explore the workspace architecture.
Exploration complete. Now checking the dashboard-ui test setup before implementing.
Step 1: Config —
[workspace].parallel_across_reposandmax_concurrencyNow the validation logic in
parseWorkspaceConfig:Step 2: The bounded workspace scheduler
The parked-item model has a slot-accounting flaw; rewriting with a cleaner
readyAt-based defer model:Step 3: SQLite concurrency hardening (WAL + busy_timeout)
Step 4: Workspace state — routing skips WAL + fleet activity store
Now append the
FleetActivityStoreto state.ts:Now update the state.ts module docstring and add the batch-execute hook in the polling acquirer:
Step 5: Polling acquirer — batch execution hook + dedupe removal
Now add
removeProcessedtoWebhookQueue:Step 6: Core integration — executor, acquirer batching, event handlers
Now the acquirer factory with the batch strategy:
Now the executor itself:
Now wire
runWorkspaceWorker:Add the
onChangehook to the scheduler:Now update
fleet-events.tsto route review/mention runs through the scheduler:Step 7: Worker shutdown —
onShutdownhook + second-signal force exitStep 8: Dashboard API — fleet activity + fleet-aware worker lock
Now add readonly support to
FleetActivityStore:Now update
handleWorkerStatus:Let me verify compilation before continuing with UI/docs:
Step 9: Dashboard UI — fleet status strip
Step 10:
workspace inittemplate commentsAll 791 existing tests pass. Now the test suite — first, scheduler unit tests:
Three issues found: (1) my pump dispatches by lane-insertion order, which breaks global FIFO in serial mode; (2)+(3) test bugs (unhandled rejections). Rewriting the scheduler core with a global FIFO dispatch order:
Now fix the two buggy tests (unhandled rejections):
Test run hangs. Let me isolate which test with a manual environment:
The contended-item fix is clear (deferred head must block its lane). But the drain test result is surprising — let me reproduce minimally:
The scheduler itself works correctly. Let me inspect what the test file actually looks like now:
Test file looks right and the repro contradicts it. Let me instrument the actual test to find the divergence:
The queued item actually RAN in test context. Let me instrument timing to understand:
Let me verify this bizarre behavior in isolation:
Found it: in bun 1.3.2, attaching
expect(pendingPromise).rejects.*blocks the test until settlement — a runner quirk. Rewriting those assertions with manual outcome capture instead:Now fix the FIFO bug in
pump()— a deferred head must block its lane's followers:Scheduler tests pass. Now config validation tests:
Two issues: my repo-count clamp fights explicit configs (remove it — the lane model bounds naturally), and a test misplaced the TOML key:
Now the fleet integration tests — appending to
workspace-worker.test.ts:Test helper bug — runs were recorded after the gate instead of at start:
Two test-design deadlocks (never-released gates / draining before releasing the in-flight gate):
Now add fleet-activity store tests and the SQLite concurrency test:
Stale detection logic is inverted — EPERM means alive; any other error means gone:
Now extend the dashboard API tests:
latest()orders rows alphabetically by repo — fix the test expectation:Now the dashboard-ui test for the fleet chips:
All green. Now documentation:
Now the changelog:
Now the full test suite across the repo, plus lint and format:
Warnings are pre-existing style categories (repo runs with 0 errors). Let me verify I didn't inflate the baseline:
8 new warnings vs baseline — all stylistic promise-plugin warnings matching existing patterns (0 errors). Now a final review of the complete diff:
The diff looks correct. Let me verify the remaining diffs:
Everything checks out. Final full verification — build, typecheck all packages, lint, format check, full test suite:
DEV-79 is implemented on
feature/dev-79-attempt-3(no prior partial work existed — branches/stashes were for other tasks). All checks pass: 831 tests (40 new), build, typecheck, lint (0 errors), format.What was built
Config (
workspace/config.ts) —[workspace].parallel_across_repos(bool, defaultfalse) and[workspace].max_concurrency(positive integer; rejects booleans/fractions/zero/negatives/strings; safe default 4 viaDEFAULT_MAX_CONCURRENCY).effectiveMaxConcurrency()yields 1 in serial mode and naturally bounds caps larger than the fleet.Scheduler (
workspace/scheduler.ts, new) — one shared bounded scheduler with a global concurrency limit and keyed per-repo FIFO lanes. Dispatch follows global submission order, so serial mode reproduces the exact historical ordering while parallel mode overlaps independent repos up to the cap. Lock contention throwsRepoBusyError: the item returns to its lane head and retries after an injectable delay without consuming a global slot or resolving the submission — dedupe stays intact.drain()cancels never-started work (runningonCancelrollbacks) and awaits in-flight runs so per-repo locks always release.Integration (
workspace-worker.ts,fleet-events.ts,task-polling-acquirer.ts) — routing happens before scheduling; polling ticks dispatch whole ready batches via a newexecuteBatchhook, and PR review/mention/relay runs join the same lanes. The executor keeps legacy behavior when no scheduler is passed.Shutdown (
worker.ts) — new awaitedonShutdownhook plus second-signal force exit. Fleet mode drains (queued tasks get their dedupe marks rolled back viaWebhookQueue.removeProcessed), closes SQLite handles, then releases locks.SQLite — WAL + busy_timeout + NORMAL sync applied to every store connection sharing
queue.db.Observability —
FleetActivityStorepersists per-repoidle/queued/runningsnapshots with PID-based stale detection after crashes;GET /api/workerserves them (null on older DBs), the dashboard header renders live per-repo chips, and the fleet daemon's lock is now actually found (plainDir layout fix).Docs —
workspaces.md(new "Parallel execution" + shutdown/status sections), dashboard API table,workspace inittemplate comments, CLI startup banner, changelog entries under[Unreleased].This PR was automatically created by @devintern/code