09eaf360a3
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>
41 lines
1.5 KiB
C#
41 lines
1.5 KiB
C#
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;
|
|
}
|
|
}
|