package runner import ( "context" "errors" "time" "github.com/easyai/easyai-ai-gateway/apps/api/internal/executionpool" "github.com/jackc/pgx/v5" "github.com/riverqueue/river" "github.com/riverqueue/river/rivertype" ) // riverExecutionBroker is the River adapter for the platform-neutral broker // port. Queue names and River uniqueness semantics do not escape this adapter. type riverExecutionBroker struct { service *Service } func (broker riverExecutionBroker) Publish( ctx context.Context, poolID string, taskID string, notBefore time.Time, ) (int64, error) { tx, err := broker.service.store.Pool().Begin(ctx) if err != nil { return 0, err } defer func() { rollbackCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _ = tx.Rollback(rollbackCtx) }() jobID, err := broker.publishTx(ctx, tx, poolID, taskID, notBefore, 2) if err != nil { return 0, err } if err := tx.Commit(ctx); err != nil { return 0, err } return jobID, nil } func (broker riverExecutionBroker) publishTx( ctx context.Context, tx pgx.Tx, poolID string, taskID string, notBefore time.Time, priority int, ) (int64, error) { riverClient := broker.service.asyncControlClient() if riverClient == nil { return 0, errors.New("River execution broker is not started") } opts := riverExecutionInsertOptions(poolID, notBefore, priority) result, err := riverClient.InsertTx(ctx, tx, asyncTaskArgs{TaskID: taskID}, opts) if err != nil { return 0, err } if result.Job == nil { return 0, nil } if _, err := tx.Exec(ctx, ` UPDATE gateway_tasks SET river_job_id = $2, updated_at = now() WHERE id = $1::uuid`, taskID, result.Job.ID); err != nil { return 0, err } return result.Job.ID, nil } func riverExecutionInsertOptions(poolID string, notBefore time.Time, priority int) *river.InsertOpts { if priority < 1 { priority = 2 } opts := &river.InsertOpts{ MaxAttempts: 1000, Priority: priority, Queue: executionpool.QueueName(poolID), Tags: []string{"gateway-task"}, UniqueOpts: river.UniqueOpts{ ByArgs: true, ByQueue: true, ByState: []rivertype.JobState{ rivertype.JobStateAvailable, rivertype.JobStatePending, rivertype.JobStateRetryable, rivertype.JobStateRunning, rivertype.JobStateScheduled, }, }, } if !notBefore.IsZero() { opts.ScheduledAt = notBefore } return opts } var _ executionpool.ExecutionBroker = riverExecutionBroker{}