using Microsoft.EntityFrameworkCore;
using TeamUp.Modules.Assembler.Domain;
using TeamUp.Modules.Assembler.Persistence;
namespace TeamUp.Modules.Assembler.Queue;
/// The Postgres-backed agent-run queue. Enqueue inserts; claim uses FOR UPDATE SKIP LOCKED
/// so multiple workers can drain concurrently without contention.
internal sealed class JobQueue(AssemblerDbContext db, TimeProvider clock)
{
public async Task 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 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;
}
}