mirror of
https://github.com/acaloiaro/neoq
synced 2026-07-21 18:29:08 +00:00
There could be a condition where jobs were added to a queue that is not longer being watched, yet the jobs in those queues would continue appearing in the pending job list because the query to get pending jobs did not filter by queues being watched.
160 lines
6.7 KiB
Go
160 lines
6.7 KiB
Go
package neoq
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/acaloiaro/neoq/handler"
|
|
"github.com/acaloiaro/neoq/jobs"
|
|
"github.com/acaloiaro/neoq/logging"
|
|
)
|
|
|
|
const (
|
|
DefaultIdleTxTimeout = 30000
|
|
// The duration of time between checking if any queues have pending jobs
|
|
// It's necessary to check for pending jobs periodically because if the LISTENer connection fails at any point, there is a period
|
|
// of time when new jobs get announced by the new job trigger announcement, but no listeners are LISTENing for work.
|
|
DefaultPendingJobFetchInterval = 60 * time.Second
|
|
// the window of time between time.Now() and when a job's RunAfter comes due that neoq will schedule a goroutine to
|
|
// schdule the job for execution.
|
|
// E.g. right now is 16:00:00 and a job's RunAfter is 16:00:30 of the same date. This job will get a dedicated goroutine
|
|
// to wait until the job's RunAfter, scheduling the job to be run exactly at RunAfter
|
|
DefaultFutureJobWindow = 30 * time.Second
|
|
DefaultJobCheckInterval = 1 * time.Second
|
|
)
|
|
|
|
var ErrBackendNotSpecified = errors.New("a backend must be specified")
|
|
|
|
// Config configures neoq and its backends
|
|
//
|
|
// This configuration struct includes options for all backends. As such, some of its options are not applicable to all
|
|
// backends. [BackendConcurrency], for example, is only used by the redis backend. Other backends manage concurrency on a
|
|
// per-handler basis.
|
|
type Config struct {
|
|
BackendInitializer BackendInitializer
|
|
BackendAuthPassword string // password with which to authenticate to the backend
|
|
BackendConcurrency int // total number of backend processes available to process jobs
|
|
ConnectionString string // a string containing connection details for the backend
|
|
JobCheckInterval time.Duration // the interval of time between checking for new future jobs
|
|
PendingJobCheckInterval time.Duration // duration of time between checking for unprocessed jobs due to downtime/db disconnects
|
|
FutureJobWindow time.Duration // time duration between current time and job.RunAfter that future jobs get scheduled
|
|
IdleTransactionTimeout int // number of milliseconds PgBackend transaction may idle before the connection is killed
|
|
ShutdownTimeout time.Duration // duration to wait for jobs to finish during shutdown
|
|
SynchronousCommit bool // Postgres: Enable synchronous commits (increases durability, decreases performance)
|
|
LogLevel logging.LogLevel // the log level of the default logger
|
|
PGConnectionTimeout time.Duration // the amount of time to wait for a connection to become available before timing out
|
|
RecoveryCallback handler.RecoveryCallback // the recovery handler applied to all Handlers excuted by the associated Neoq instance
|
|
}
|
|
|
|
// ConfigOption is a function that sets optional backend configuration
|
|
type ConfigOption func(c *Config)
|
|
|
|
// NewConfig initiailizes a new Config with defaults
|
|
func NewConfig() *Config {
|
|
return &Config{
|
|
FutureJobWindow: DefaultFutureJobWindow,
|
|
JobCheckInterval: DefaultJobCheckInterval,
|
|
RecoveryCallback: handler.DefaultRecoveryCallback,
|
|
}
|
|
}
|
|
|
|
// BackendInitializer is a function that initializes a backend
|
|
type BackendInitializer func(ctx context.Context, opts ...ConfigOption) (backend Neoq, err error)
|
|
|
|
// Neoq interface is Neoq's primary API
|
|
//
|
|
// Neoq is implemented by:
|
|
// - [pkg/github.com/acaloiaro/neoq/backends/memory.MemBackend]
|
|
// - [pkg/github.com/acaloiaro/neoq/backends/postgres.PgBackend]
|
|
// - [pkg/github.com/acaloiaro/neoq/backends/redis.RedisBackend]
|
|
type Neoq interface {
|
|
// Enqueue queues jobs to be executed asynchronously
|
|
Enqueue(ctx context.Context, job *jobs.Job) (jobID string, err error)
|
|
|
|
// Start starts processing jobs on the queue specified in the Handler
|
|
Start(ctx context.Context, h handler.Handler) (err error)
|
|
|
|
// StartCron starts processing jobs with the specified cron schedule and handler
|
|
//
|
|
// See: https://pkg.go.dev/github.com/robfig/cron?#hdr-CRON_Expression_Format for details on the cron spec format
|
|
StartCron(ctx context.Context, cron string, h handler.Handler) (err error)
|
|
|
|
// SetLogger sets the backend logger
|
|
SetLogger(logger logging.Logger)
|
|
|
|
// Shutdown halts job processing and releases resources
|
|
Shutdown(ctx context.Context)
|
|
}
|
|
|
|
// New creates a new backend instance for job processing.
|
|
//
|
|
// By default, neoq initializes [memory.Backend] if New() is called without a backend configuration option.
|
|
//
|
|
// Use [neoq.WithBackend] to initialize different backends.
|
|
//
|
|
// For available configuration options see [neoq.ConfigOption].
|
|
func New(ctx context.Context, opts ...ConfigOption) (b Neoq, err error) {
|
|
c := Config{}
|
|
for _, opt := range opts {
|
|
opt(&c)
|
|
}
|
|
|
|
if c.BackendInitializer == nil {
|
|
err = ErrBackendNotSpecified
|
|
return
|
|
}
|
|
|
|
b, err = c.BackendInitializer(ctx, opts...)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// WithBackend configures neoq to initialize a specific backend for job processing.
|
|
//
|
|
// Neoq provides two [config.BackendInitializer] that may be used with WithBackend
|
|
// - [pkg/github.com/acaloiaro/neoq/backends/memory.Backend]
|
|
// - [pkg/github.com/acaloiaro/neoq/backends/postgres.Backend]
|
|
func WithBackend(initializer BackendInitializer) ConfigOption {
|
|
return func(c *Config) {
|
|
c.BackendInitializer = initializer
|
|
}
|
|
}
|
|
|
|
// WithRecoveryCallback configures neoq with a function to be called when fatal errors occur in job Handlers.
|
|
//
|
|
// Recovery callbacks are useful for reporting errors to error loggers and collecting error metrics
|
|
func WithRecoveryCallback(cb handler.RecoveryCallback) ConfigOption {
|
|
return func(c *Config) {
|
|
c.RecoveryCallback = cb
|
|
}
|
|
}
|
|
|
|
// WithJobCheckInterval configures the duration of time between checking for future jobs
|
|
func WithJobCheckInterval(interval time.Duration) ConfigOption {
|
|
return func(c *Config) {
|
|
c.JobCheckInterval = interval
|
|
}
|
|
}
|
|
|
|
// WithPendingJobCheckInterval configures the duration of time between checking for unprocessed jobs on watched queues.
|
|
//
|
|
// Pending jobs are jobs that were queued while neoq wasn't listening or while a disconnect occurred. Most jobs are
|
|
// processed via Postgres' LISTEN/NOTIFY facilities. This check interval is a last resort and does not need to be short.
|
|
// Be careful not to hammer your database by setting this too low.
|
|
func WithPendingJobCheckInterval(interval time.Duration) ConfigOption {
|
|
return func(c *Config) {
|
|
c.PendingJobCheckInterval = interval
|
|
}
|
|
}
|
|
|
|
// WithLogLevel configures the log level for neoq's default logger. By default, log level is "INFO".
|
|
// if SetLogger is used, WithLogLevel has no effect on the set logger
|
|
func WithLogLevel(level logging.LogLevel) ConfigOption {
|
|
return func(c *Config) {
|
|
c.LogLevel = level
|
|
}
|
|
}
|