refactor(worker/queue): split queue waker and picker, auto-wake on enqueue
Slice 3 of the worker state and queue consolidation refactor. - Add IQueueWaker / QueueWaker (singleton holding the wake semaphore). - Add IQueuePicker / QueuePicker; raw SQL UPDATE...RETURNING moves out of TaskRepository.GetNextQueuedAgentTaskAsync (deleted) and now also filters on blocked_by_task_id IS NULL and writes started_at on claim. - TaskStateService takes IQueueWaker directly; the Func<QueueService> indirection is gone. State transitions to Queued auto-wake the dispatcher. - QueueService waits via the shared waker and dispatches via the picker. - Drop explicit _queue.WakeQueue() calls in WorkerHub.QueuePlanningSubtasksAsync and ExternalMcpService.AddTask. The hub WakeQueue endpoint stays for diagnostics, delegating to _waker.Wake(). - Migrate tests; pre-existing flaky AppSettings/ExternalMcp tests untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Worker.Config;
|
||||
using ClaudeDo.Worker.Hub;
|
||||
using ClaudeDo.Worker.Queue;
|
||||
using ClaudeDo.Worker.Runner;
|
||||
using ClaudeDo.Worker.Services;
|
||||
using ClaudeDo.Worker.Tests.Infrastructure;
|
||||
@@ -45,6 +46,8 @@ public sealed class QueueServiceSlotGuardTests : IDisposable
|
||||
try { Directory.Delete(_tempDir, true); } catch { }
|
||||
}
|
||||
|
||||
private QueueWaker _waker = null!;
|
||||
|
||||
private (QueueService service, FakeClaudeProcess fakeProcess) CreateService(
|
||||
Func<string, string, string, Func<string, Task>, CancellationToken, Task<RunResult>>? handler = null)
|
||||
{
|
||||
@@ -55,7 +58,9 @@ public sealed class QueueServiceSlotGuardTests : IDisposable
|
||||
var argsBuilder = new ClaudeArgsBuilder();
|
||||
var runner = new TaskRunner(fake, dbFactory, broadcaster, wtManager, argsBuilder, _cfg,
|
||||
NullLogger<TaskRunner>.Instance, TaskStateServiceBuilder.Build(dbFactory).State);
|
||||
var service = new QueueService(dbFactory, runner, _cfg, NullLogger<QueueService>.Instance);
|
||||
_waker = new QueueWaker();
|
||||
var picker = new QueuePicker(dbFactory);
|
||||
var service = new QueueService(dbFactory, runner, _cfg, NullLogger<QueueService>.Instance, _waker, picker);
|
||||
return (service, fake);
|
||||
}
|
||||
|
||||
@@ -102,7 +107,7 @@ public sealed class QueueServiceSlotGuardTests : IDisposable
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
await service.StartAsync(cts.Token);
|
||||
service.WakeQueue();
|
||||
_waker.Wake();
|
||||
|
||||
// Wait until the queue slot has actually picked up the task.
|
||||
await queuePickedUp.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
@@ -132,7 +137,7 @@ public sealed class QueueServiceSlotGuardTests : IDisposable
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
await service.StartAsync(cts.Token);
|
||||
service.WakeQueue();
|
||||
_waker.Wake();
|
||||
|
||||
await queuePickedUp.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user