- Manager gains the apparatus JobManager surface: StartJob, UpdateJobState, UpdateMetadata, FailJob, CancelJob (is_canceled + STOPPED + River JobCancel), StopJob (STOPPED only), CheckIfCanceled and GetMetadata, all raw column writes so updated_at is untouched - serve starts the in-process worker unless queue.work_in_serve is false and stops it on shutdown; an app without jobs gets an idle worker - queue:work runs a foreground worker with repeatable --queue filters; queue:clear deletes available, scheduled and retryable jobs of one queue - the generated main appends conga.RuntimeCommands; summer delegates queue:work and queue:clear; make:job scaffolds a conga.Job
119 lines
3.4 KiB
Go
119 lines
3.4 KiB
Go
package conga
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/bonfire"
|
|
"git.golem15.com/golem15/summercms/modules/lagoon"
|
|
"git.golem15.com/golem15/summercms/modules/party"
|
|
"github.com/riverqueue/river"
|
|
"github.com/riverqueue/river/rivertype"
|
|
)
|
|
|
|
// clearBatch is the number of jobs one JobDeleteMany pass removes.
|
|
const clearBatch = 10000
|
|
|
|
// RuntimeCommands returns the queue:work and queue:clear commands for the
|
|
// application binary.
|
|
func RuntimeCommands(app *backpack.App, plugins []party.Plugin) []bonfire.Command {
|
|
return []bonfire.Command{
|
|
{
|
|
Name: "queue:work",
|
|
Description: "Run background job workers in the foreground",
|
|
Flags: []bonfire.Flag{{
|
|
Name: "queue",
|
|
Description: "Queue to work (repeatable; default all known queues)",
|
|
Repeatable: true,
|
|
}},
|
|
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
|
return withDB(ctx, app, func() error {
|
|
return work(ctx, app, plugins, in.Flags("queue"), out)
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Name: "queue:clear",
|
|
Description: "Clear all queued jobs, by deleting all pending jobs.",
|
|
Args: []bonfire.Arg{{
|
|
Name: "queue",
|
|
Description: "The name of the queue to clear (default \"default\")",
|
|
}},
|
|
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
|
queue, _ := in.Argument("queue")
|
|
queue = strings.TrimSpace(queue)
|
|
if queue == "" {
|
|
queue = defaultQueue
|
|
}
|
|
return withDB(ctx, app, func() error {
|
|
return clearQueue(ctx, app, queue, out)
|
|
})
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func work(ctx context.Context, app *backpack.App, plugins []party.Plugin, queues []string, out bonfire.Output) error {
|
|
ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
w, err := StartWorker(ctx, app, plugins, WorkerOptions{Queues: queues})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Info("worker started on queues: " + strings.Join(w.Queues(), ", "))
|
|
<-ctx.Done()
|
|
stopCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
return w.Stop(stopCtx)
|
|
}
|
|
|
|
// clearQueue deletes the available, scheduled and retryable jobs of queue in
|
|
// batches until a pass deletes none. River never deletes running jobs.
|
|
func clearQueue(ctx context.Context, app *backpack.App, queue string, out bonfire.Output) error {
|
|
m, err := From(app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
client, err := m.insertClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Info(fmt.Sprintf("Clearing queue %q", queue))
|
|
total := 0
|
|
for {
|
|
res, err := client.JobDeleteMany(ctx, river.NewJobDeleteManyParams().
|
|
Queues(queue).
|
|
States(rivertype.JobStateAvailable, rivertype.JobStateScheduled, rivertype.JobStateRetryable).
|
|
First(clearBatch))
|
|
if err != nil {
|
|
return fmt.Errorf("conga: clear queue %q: %w", queue, err)
|
|
}
|
|
if len(res.Jobs) == 0 {
|
|
break
|
|
}
|
|
total += len(res.Jobs)
|
|
}
|
|
out.Info(fmt.Sprintf("Cleared %d jobs", total))
|
|
return nil
|
|
}
|
|
|
|
// withDB opens and publishes the shared pool for a CLI command and closes it
|
|
// when fn returns, the way lagoon's own commands do.
|
|
func withDB(ctx context.Context, app *backpack.App, fn func() error) error {
|
|
sqlDB, gdb, err := lagoon.OpenFromApp(ctx, app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer sqlDB.Close()
|
|
if err := lagoon.Publish(app, sqlDB, gdb); err != nil {
|
|
return err
|
|
}
|
|
return fn()
|
|
}
|