v0.24.0
The queue, further: a kind's jobs run so many at once at most, with queue.AtOnce, or start so many a time, with queue.Rate, counted on all the instances of the app together, as a CPU or a mail provider takes only so much however many instances there are; and queue.OnFail, which marks what a job was about once it has failed for good, as a Laravel job's failed method does. A hundred photos to resize no longer take every worker, and a newsletter's ten thousand mails no longer go faster than the provider allows. The auth starter's jobs table keeps both limits, in SQLite, Postgres and MySQL.
queue.Handle(q, "resize-photo", a.resizePhoto, queue.AtOnce(2))
// 14 a second, counted in the auth starter's throttles table, where every instance counts.
perSecond := &auth.Throttle{Name: "newsletter", Max: 14, Window: time.Second, Store: counts}
queue.Handle(q, "newsletter", a.sendNewsletter, queue.Rate(perSecond))
queue.Handle(q, "process-photo", a.processPhoto,
queue.OnFail(func(ctx context.Context, job ProcessPhoto, err error) error {
return a.posts.photoFailed(ctx, job.Post, err.Error()) // the post says its photo didn't come
}),
)What's new:
queue.AtOnce(n): no more thannof a kind's jobs run at once, on all the instances together. A job waits whilenof its kind are held by claims, and runs once one is done, has failed or gone back, or has lost its worker. The claim counts, as it passes over aOneAtATimejob, so nothing stays locked while the jobs run. Each job carries its kind's limit,Job.AtOnce, which aqueue.AtOnceStorekeeps.queue.Rate(l): a kind's jobs start no faster thanllets them.lis aqueue.Limiter, theTrythatauth.Throttlehas, so the queue imports noauth, and a rate counts where the throttles do. A joblrefuses goes back as it was, its attempts unchanged and its place kept, and no job of its kind is claimed untill's wait is over, on any instance, through aqueue.HoldBackStore'sHoldBack: the rest of a newsletter waits in the Store, rather than each be claimed and refused in turn. A limiter that fails, as when its store is down, holds the job back for 10 seconds.queue.OnFail(fn):fngets the value a job was pushed with and the error its last attempt returned, once, after the Store has kept the job as failed for good, on the instance that ran it, with a context done after the kind'sTimeout. Its own error, or its panic, goes to the log.Handlepanics whenfntakes another type than the kind's value.- The next job sooner: a job of an
AtOnceorOneAtATimekind wakes the queue as it ends, so the next of its kind runs at once, where aOneAtATimekind's waited for the nextPoll. AndDrainreturns once its context is done. queuetest.TestStorechecks anAtOnceStore's promises, claims at once among them, and aHoldBackStore's, andqueuetest.Memorykeeps both.- The auth starter's jobs table keeps both limits, in each database: an
at_oncecolumn, which a claim compares with a count of its kind's held jobs, by a new index onheld_until, and on Postgres and MySQL counts again under its kind'sjob_locksrow; and aheld_kindstable, of the kinds held back until when, whichprune-jobsclears. On MySQL, the claim counts the kinds held back, as aNOT EXISTSthere became a join that locked every job it read, so claims at once found none.
Upgrading: nothing an app calls changed: go get github.com/cuonggt/tug@v0.24.0. A kind with AtOnce or Rate needs a Store that's an AtOnceStore or a HoldBackStore, and Handle panics without one, as for Unique's options; an app's own Store keeps working for every other kind. To have the limits in an app made from the auth starter, take jobs_db.go, and KeepsAtOnce from jobs.go, from an app tug new -auth makes on the same database, and its migration step, the at_once column, the jobs_held index and the held_kinds table, three steps on MySQL, at the end of the app's own migrations. docs/jobs.md has the limits, a job that fails for good OnFail, and the Stores their new extras.
The guide is in docs/. tug needs Go 1.26.