M4: agent-run job queue + worker drain (Increment 1)
SharedKernel: IWorkerModule seam (RegisterWorker runs in the worker host only).
Bootstrap: AddTeamUpWorkerServices; the worker host now wires it.
Assembler module (schema "assembler", InitialAssembler migration):
- Job (Pending→Processing→Done/Failed) + AgentRun (Queued→Running→Completed/Failed) entities.
- JobQueue: enqueue + ClaimNextAsync using `FOR UPDATE SKIP LOCKED` in a transaction.
- AgentRunExecutor (Increment-1 placeholder — real assemble/model/parse lands in Increment 2).
- JobProcessor BackgroundService drains the queue on the worker host (web off the model path).
- POST /api/assembler/runs enqueues a run; GET /api/assembler/runs/{id} reads it.
Verified: build green; ArchitectureTests 8/8 (Assembler references only SharedKernel);
IntegrationTests 28/28 incl. enqueue→claim(SKIP LOCKED)→process→Completed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeamUp.Modules.Assembler.Domain;
|
||||
using TeamUp.Modules.Assembler.Persistence;
|
||||
|
||||
namespace TeamUp.Modules.Assembler.Queue;
|
||||
|
||||
/// <summary>The Postgres-backed agent-run queue. Enqueue inserts; claim uses FOR UPDATE SKIP LOCKED
|
||||
/// so multiple workers can drain concurrently without contention.</summary>
|
||||
internal sealed class JobQueue(AssemblerDbContext db, TimeProvider clock)
|
||||
{
|
||||
public async Task<Job> EnqueueAsync(string type, string payload, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var job = new Job(type, payload, clock.GetUtcNow());
|
||||
db.Jobs.Add(job);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return job;
|
||||
}
|
||||
|
||||
public async Task<Job?> ClaimNextAsync(string worker, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);
|
||||
|
||||
var job = await db.Jobs
|
||||
.FromSqlRaw(
|
||||
"SELECT * FROM assembler.jobs WHERE \"Status\" = 'Pending' " +
|
||||
"ORDER BY \"CreatedAtUtc\" LIMIT 1 FOR UPDATE SKIP LOCKED")
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (job is null)
|
||||
{
|
||||
await transaction.RollbackAsync(cancellationToken);
|
||||
return null;
|
||||
}
|
||||
|
||||
job.MarkProcessing(worker, clock.GetUtcNow());
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
await transaction.CommitAsync(cancellationToken);
|
||||
return job;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user