baqueue

package module
v0.0.0-...-7592c80 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 26 Imported by: 0

README

BaQueue Go

A Go implementation of the BaQueue library in the sibling baqueue-fastapi directory. It provides durable queues, retries, delayed jobs, batches, scheduling, worker supervision, maintenance, and the same live monitoring dashboard.

Requires Go 1.25 or newer. SQLite is the default and uses a pure-Go driver; no C compiler or external service is required. Install the module from GitHub:

go get github.com/basalam/baqueue-golang@main

Import it as baqueue "github.com/basalam/baqueue-golang". No tagged module release has been published yet. For local development in another application:

require github.com/basalam/baqueue-golang v0.0.0

replace github.com/basalam/baqueue-golang => ../baqeueu-go

Quick start

Run these from this directory in separate terminals:

go run ./examples/worker work -q emails --workers 3
go run ./examples/dispatch
go run ./cmd/baqueue dashboard

Workers, producers, and dashboard share .baqueue-go.db in their working directory. The dashboard is at http://127.0.0.1:9100. A self-contained in-memory demo is also available:

go run ./examples/dashboard

Use the library

ctx := context.Background()
q, err := baqueue.Open(ctx, baqueue.DefaultConfig())
if err != nil { return err }
defer q.Close(context.Background())

err = q.RegisterFunc("send_email", func(ctx context.Context, data map[string]any) (any, error) {
    return mailer.Send(ctx, data["to"].(string))
}, baqueue.OnQueue("emails"), baqueue.WithMaxAttempts(3))
if err != nil { return err }

id, err := q.Push(ctx, "send_email", map[string]any{"to": "a@example.com"})
id, err = q.Later(ctx, "send_email", time.Minute, map[string]any{"to": "b@example.com"})
ids, err := q.Bulk(ctx, []baqueue.JobRequest{
    {Name: "send_email", Data: map[string]any{"to": "c@example.com"}},
    {Name: "send_email", Data: map[string]any{"to": "d@example.com"}},
})

Check each returned error in application code. Dispatch options are OnQueue, WithMaxAttempts, WithBackoff, WithTimeout, and WithTags. Registered defaults are applied first, then dispatch options. Producers can dispatch an unregistered name; each consuming process must register the corresponding handler. Separate producers should set the same options explicitly, because policy is stored in the job at dispatch time.

Numeric values inside Data decode as json.Number, preserving large IDs without float64 rounding. Decode into an application struct or call Int64/Float64 as appropriate.

Implement Handler for a struct-based job. Optional SuccessHandler and FailureHandler interfaces provide OnSuccess and terminal OnFailure hooks. Python's decorators and dynamic imports map to RegisterFunc and Register in Go; Python code itself cannot execute inside a Go worker.

Feature parity

Python feature Go API
Queue facade; push/later/bulk Queue, Push, Later, Bulk
Class/function jobs; success/failure hooks Handler, HandlerFunc, registration and hook interfaces
Memory, SQLite, Redis, PostgreSQL Driver and four built-in constructors
Fixed, linear, exponential, explicit retry delays Backoff, ComputeDelay, ShouldRetry
Per-attempt history, policy, tags, timestamps JobPayload, identical JSON names and Unix seconds
Batches and then/catch/finally/allow-failures Queue.Batch(...).Then(...).Catch(...).Finally(...)
Cron and interval schedules NewScheduler, ScheduleEntry, Add, Run
Worker pools and auto/simple/no balancing Supervisor, Scale, Balancer
Worker recycling MaxJobsPerWorker, MaxTime
One-hour stuck processing recovery RecoverStuckJobs, StuckProcessingSeconds
Graceful shutdown context cancellation; Worker.Stop drains its current job
Pruning, retention overrides, disk-full cleanup Pruner, driver maintenance methods, Config
Redis orphan/index reconciliation ReconcilePendingJobs, ReconcileIndexes
Events, metrics, supervisor heartbeats EventBus, Metrics, driver reporting methods
Dashboard and WebSockets dashboard.New, embedded original UI and API
Jobs/queues/workers, filters, details, retry/delete/execute Same dashboard controls and REST paths
Live centisecond processing duration Browser-local duration timer, independent of feed refresh
Work/schedule/dashboard/maintenance CLI cmd/baqueue, embeddable cli.Run

Go timeouts are cooperative: handlers must observe ctx.Done() and pass context to I/O. The worker does not abandon a still-running goroutine and immediately run the same job again. Use process isolation for untrusted or non-cooperative jobs. All goroutines share a heap, so memory_limit triggers garbage collection under pressure rather than pretending to enforce a per-worker memory limit. It is not a process memory cap.

Storage and delivery

config := baqueue.DefaultConfig()
config.Driver = baqueue.DriverConfig{Name: "redis", URL: "redis://localhost:6379/0"}
config.Prefix = "myapp:baqueue"
// Other choices:
// DriverConfig{Name: "sqlite", URL: "./queue.db"}
// DriverConfig{Name: "postgres", URL: "postgres://user:pass@localhost/db?sslmode=require"}
// DriverConfig{Name: "memory"}

Memory is only shared inside the current process. SQLite uses WAL, a busy timeout, and transactional claims across processes. PostgreSQL uses row locks with FOR UPDATE SKIP LOCKED. SQL tables have a baqueue_go_ prefix and store the full payload, including history; they are deliberately separate from the Python SQL schema. Opening Go against an existing Python database does not migrate its jobs.

Redis uses the Python key and payload layout. Use matching prefixes only when intentionally sharing storage, and separate queues by runtime. Go handlers can be registered under existing job_class strings. Do not let a Python worker consume a Go-only handler, or the reverse. Redis Cluster is not supported by this multi-key transaction layout. Use maxmemory-policy noeviction and appropriate Redis persistence; recovery cannot restore evicted keys or data lost by an unpersisted server restart.

A Redis claim watches the queue and hash, reads the head without removing it, then commits the payload/status/index updates and ready-entry removal together. Conflicting workers retry the transaction. A disconnect before EXEC leaves the entry ready; an uncertain successful EXEC leaves a processing record recoverable by the supervisor. Future-dated ready entries are restored to delayed storage. Stale processing attempts cannot complete or release a newer claim.

Delivery is at least once, not exactly once. Make externally visible job effects idempotent. Recovery retains the consumed attempt, max_attempts, backoff, and history. The default retry policy allows three total attempts; exponential delays start at 5 seconds, double per attempt, add up to 10% jitter, and cap at one hour. Explicit delays are seconds, for example Backoff{Delays: []float64{10,60,300}}. Manual retry preserves stored attempt counts and policy, as do release and stuck recovery. It intentionally permits another execution of a terminal failed job.

Workers, schedules, batches, and events

sc := baqueue.DefaultSupervisorConfig()
sc.Queues = []string{"emails", "payments"}
sc.MinWorkers, sc.MaxWorkers = 3, 10
sc.StuckProcessingSeconds = 3600
s := baqueue.NewSupervisor(q, sc)
s.Pruner = &baqueue.Pruner{Queue:q, Config:q.Config}
err = s.Run(ctx)

Raise the stuck timeout above the longest legitimate handler runtime. Give supervisors distinct names when monitoring multiple processes. The standard CLI starts automatic pruning once, alongside its supervisor group. Default retention: completed 5 seconds, failed/cancelled 1 day, metrics 7 days; prune interval 5 seconds. Nonpositive retention disables pruning for that category. Legacy positive prune_*_hours fields override seconds.

scheduler, err := baqueue.NewScheduler(q, []baqueue.ScheduleEntry{
    {JobClass:"cleanup", Cron:"0 * * * *", Queue:"maintenance"},
    {JobClass:"heartbeat", Every:30, Queue:"maintenance"},
})
if err != nil { return err }
err = scheduler.Run(ctx)

batch, err := q.Batch([]baqueue.JobRequest{
    {Name:"send_email", Data:map[string]any{"to":"a@example.com"}},
    {Name:"send_email", Data:map[string]any{"to":"b@example.com"}},
}).Name("newsletter").Then("all_done").Catch("any_failed").Finally("finished").Dispatch(ctx)

Schedules use UTC, five-field cron expressions, or interval seconds. As in the Python scheduler, the first tick dispatches immediately. Run one scheduler per schedule set; schedules are not a distributed leader-election mechanism. Batch callback names persist in storage and therefore work when producer and workers are separate processes. Counters are atomic; callbacks use deterministic IDs. Terminal job state and the member's first batch outcome commit together. Manual retry does not reopen that member's original batch outcome or count it twice. Callback dispatch has persistent completion flags and deterministic job IDs; the supervisor retries unfinished dispatch through ReconcileBatches. Callbacks still require idempotent external effects under at-least-once delivery.

Events: job.pushed, job.started, job.completed, job.failed, job.retrying, batch.dispatched, batch.completed, batch.failed, worker.started, worker.stopped, supervisor.started, supervisor.stopped, queue.pruned, and schedule.dispatched. q.Events.On(name, fn) returns an unsubscribe function. Listeners run locally, synchronously, in registration order. Listener errors and panics are logged without changing the job outcome.

Embed the dashboard

handler := dashboard.New(q.Driver,
    dashboard.WithContext(applicationContext),
    dashboard.WithPrefix("/baqueue"),
    dashboard.WithAuth(yourAuthenticationMiddleware),
)
mux.Handle("/baqueue", handler)
mux.Handle("/baqueue/", handler)

Use standard net/http, Gin WrapH, or Echo WrapHandler; no particular Go web framework is required. All API and WebSocket paths honor the prefix. Authentication wraps every route, including assets and WebSockets. The dashboard is administrative: use application authentication and a trusted network. The CLI binds loopback by default. WebSockets accept same-origin requests unless explicitly configured. The original UI loads Alpine.js from jsDelivr and fonts from Google Fonts, so a browser needs access to those hosts.

CLI and configuration

go run ./cmd/baqueue --config config.json status
go run ./examples/worker work --driver sqlite -q emails --workers 3 --max-workers 10
go run ./examples/worker schedule
go run ./cmd/baqueue dashboard --host 127.0.0.1 --port 9100
go run ./cmd/baqueue prune --status completed --hours 24 --yes
go run ./cmd/baqueue retry-failed --queue emails --tag newsletter --yes
go run ./cmd/baqueue reconcile-indexes --driver redis --driver-url redis://localhost:6379/0
go run ./cmd/baqueue reconcile-pending --driver redis --driver-url redis://localhost:6379/0

The standalone CLI cannot dynamically import Go handlers. Build your own executable with cli.Run(ctx, os.Args[1:], os.Stdout, register) as in examples/worker. Configuration field names match Python; load partial JSON with LoadConfig to retain defaults. See config.example.json. Driver URLs and credentials should come from application configuration, not source control.

driver.options supports Redis username, password, db, max_connections (pool_size), socket_timeout, socket_connect_timeout, read_timeout, write_timeout, and pool_timeout. PostgreSQL supports max_size (max_open_conns), min_size (max_idle_conns), and command_timeout (seconds, mapped to server statement timeout). Idle connections are retained lazily, not pre-opened. SQLite accepts URI pragma options in its URL. Unsupported options fail explicitly. SQL isolation can use separate database files or PostgreSQL search_path schemas; prefix controls Redis namespaces.

Verification

go test ./...
go vet ./...
go build ./...
docker compose -f compose.test.yml up -d --wait

PowerShell integration environment:

$env:BAQUEUE_REDIS_URL = 'redis://127.0.0.1:16389/0'
$env:BAQUEUE_POSTGRES_URL = 'postgres://postgres:baqueue_test@127.0.0.1:15489/baqueue_test?sslmode=disable'
go test ./... -count=1

Integration tests use uniquely named Redis prefixes and PostgreSQL schemas. Without the environment variables they skip external services. SQLite and memory always run. Use go test -race ./... with a supported C compiler, or a Linux Go container. Never point fault-injection tests at production.

go run ./cmd/stress --url redis://127.0.0.1:16389/0 --jobs 1000 --workers 8
# In another terminal, while the stress scenario runs:
docker compose -f compose.test.yml restart redis

See docs/verification.md for the recorded validation and CHANGELOG.md for the initial port scope. Licensed under MIT.

Documentation

Overview

Package baqueue provides durable jobs, workers, scheduling and monitoring.

Index

Constants

View Source
const (
	Pending    = "pending"
	Processing = "processing"
	Completed  = "completed"
	Failed     = "failed"
	Cancelled  = "cancelled"
)
View Source
const Version = "0.1.0"

Variables

View Source
var (
	ErrNotFound  = errors.New("baqueue: job not found")
	ErrConflict  = errors.New("baqueue: job state changed")
	ErrDuplicate = errors.New("baqueue: duplicate job ID")
)

Functions

func ApplyBatchOutcome

func ApplyBatchOutcome(data map[string]any, p *JobPayload) bool

ApplyBatchOutcome is called by drivers inside the terminal job transaction. Manual retries do not reopen the original batch's first terminal outcome.

func BatchNeedsReconciliation

func BatchNeedsReconciliation(data map[string]any) bool

func ComputeDelay

func ComputeDelay(b Backoff, attempt int) float64

ComputeDelay returns seconds; exponential jitter is [0, 10%) with a 1h cap.

func MergeAttempt

func MergeAttempt(stored, incoming *JobPayload) error

MergeAttempt preserves stored policy; only an owned attempt may update history.

func NewID

func NewID() string

func Now

func Now() float64

func ShouldRetry

func ShouldRetry(attempt, maxAttempts int) bool

Types

type Attempt

type Attempt struct {
	Attempt     int      `json:"attempt"`
	StartedAt   float64  `json:"started_at"`
	FinishedAt  float64  `json:"finished_at"`
	Duration    float64  `json:"duration"`
	Status      string   `json:"status"`
	Error       *string  `json:"error"`
	WillRetry   bool     `json:"will_retry"`
	NextRetryAt *float64 `json:"next_retry_at"`
}

type AutoBalancer

type AutoBalancer struct{ MinWorkers, MaxWorkers int }

func (AutoBalancer) Recommend

func (b AutoBalancer) Recommend(ctx context.Context, d Driver, queues []string, current int) (int, error)

type Backoff

type Backoff struct {
	Strategy string
	Delays   []float64
}

Backoff uses the same JSON representation as Python: a strategy or seconds array.

func (Backoff) MarshalJSON

func (b Backoff) MarshalJSON() ([]byte, error)

func (*Backoff) UnmarshalJSON

func (b *Backoff) UnmarshalJSON(data []byte) error

func (Backoff) Validate

func (b Backoff) Validate() error

type Balancer

type Balancer interface {
	Recommend(context.Context, Driver, []string, int) (int, error)
}

func CreateBalancer

func CreateBalancer(strategy string, minWorkers, maxWorkers int) Balancer

type Batch

type Batch struct {
	// contains filtered or unexported fields
}

func (*Batch) AllowFailures

func (b *Batch) AllowFailures() *Batch

func (*Batch) Catch

func (b *Batch) Catch(name string) *Batch

func (*Batch) Dispatch

func (b *Batch) Dispatch(ctx context.Context) (*BatchResult, error)

func (*Batch) Finally

func (b *Batch) Finally(name string) *Batch

func (*Batch) Name

func (b *Batch) Name(name string) *Batch

func (*Batch) Tag

func (b *Batch) Tag(tags ...string) *Batch

func (*Batch) Then

func (b *Batch) Then(name string) *Batch

type BatchResult

type BatchResult struct {
	BatchID string   `json:"batch_id"`
	Name    string   `json:"name"`
	JobIDs  []string `json:"job_ids"`
	Total   int      `json:"total"`
}

type Config

type Config struct {
	Prefix                string             `json:"prefix"`
	Driver                DriverConfig       `json:"driver"`
	Supervisors           []SupervisorConfig `json:"supervisors"`
	Schedules             []ScheduleEntry    `json:"schedules"`
	DashboardHost         string             `json:"dashboard_host"`
	DashboardPort         int                `json:"dashboard_port"`
	MetricsTrimMinutes    float64            `json:"metrics_trim_minutes"`
	AutoPrune             bool               `json:"auto_prune"`
	PruneIntervalSeconds  float64            `json:"prune_interval_seconds"`
	PruneCompletedSeconds float64            `json:"prune_completed_seconds"`
	PruneOtherSeconds     float64            `json:"prune_other_seconds"`
	PruneMetricsSeconds   float64            `json:"prune_metrics_seconds"`
	PruneBatchSize        int                `json:"prune_batch_size"`
	PruneCompletedHours   float64            `json:"prune_completed_hours"`
	PruneFailedHours      float64            `json:"prune_failed_hours"`
	PruneCancelledHours   float64            `json:"prune_cancelled_hours"`
	PruneMetricsHours     float64            `json:"prune_metrics_hours"`
	ReconcileOnConnect    bool               `json:"reconcile_on_connect"`
	AutoCleanupOnDiskFull bool               `json:"auto_cleanup_on_disk_full"`
}

func DefaultConfig

func DefaultConfig() Config

func LoadConfig

func LoadConfig(r io.Reader) (Config, error)

func (Config) Validate

func (c Config) Validate() error

type Driver

type Driver interface {
	Connect(context.Context) error
	Disconnect(context.Context) error
	Push(context.Context, *JobPayload) (string, error)
	PushMany(context.Context, []*JobPayload) ([]string, error)
	Pop(context.Context, string) (*JobPayload, error)
	PopDelayed(context.Context) ([]*JobPayload, error)
	Complete(context.Context, *JobPayload) error
	Fail(context.Context, *JobPayload, string) error
	Release(context.Context, *JobPayload, float64) error
	RequeueStuckJobs(context.Context, float64, string) (int, error)
	Delete(context.Context, string) error
	Promote(context.Context, string) (bool, error)
	GetJob(context.Context, string) (*JobPayload, error)
	GetJobs(context.Context, JobFilter) ([]*JobPayload, error)
	CountJobs(context.Context, JobFilter) (int, error)
	Size(context.Context, string) (int, error)
	Queues(context.Context) ([]string, error)
	RecordMetric(context.Context, string, string, float64) error
	GetMetrics(context.Context, string) (map[string]any, error)
	RecentThroughput(context.Context, int, string) (map[string]int, error)
	ReportSupervisor(context.Context, map[string]any) error
	GetSupervisorStats(context.Context, float64) ([]map[string]any, error)
	StoreBatch(context.Context, string, map[string]any) error
	GetBatch(context.Context, string) (map[string]any, error)
	GetPendingBatches(context.Context, int) ([]map[string]any, error)
	UpdateBatch(context.Context, string, map[string]any) error
	IncrementBatchCounter(context.Context, string, string, int) (map[string]any, error)
	Prune(context.Context, PruneOptions) (int, error)
	BulkDeleteJobs(context.Context, []string, int) (int, error)
	PruneTerminalJobs(context.Context, PruneOptions) (int, error)
	ReconcileIndexes(context.Context, int) (int, error)
	ReconcilePendingJobs(context.Context, int) (int, error)
	Flush(context.Context, string) error
	PruneMetrics(context.Context, float64) (int, error)
}

Driver implementations must serialize claims and lifecycle transitions. Complete/Fail/Release reject stale processing attempts with ErrConflict. Release preserves attempts, max_attempts, backoff and history from storage, appending the caller's history for the current processing attempt only. Successful lifecycle writes refresh the supplied payload. Complete and Fail commit the first batch-member outcome in the same transaction as job state.

type DriverConfig

type DriverConfig struct {
	Name    string         `json:"name"`
	URL     string         `json:"url"`
	Options map[string]any `json:"options"`
}

type Event

type Event struct {
	Name string
	Data map[string]any
}

type EventBus

type EventBus struct {
	// contains filtered or unexported fields
}

EventBus is instance scoped; listeners run in registration order outside the lock.

func NewEventBus

func NewEventBus() *EventBus

func (*EventBus) Emit

func (e *EventBus) Emit(ctx context.Context, name string, data map[string]any)

func (*EventBus) On

func (e *EventBus) On(name string, fn EventHandler) func()

type EventHandler

type EventHandler func(context.Context, Event) error

type FailureHandler

type FailureHandler interface {
	OnFailure(context.Context, error, *JobPayload) error
}

type Handler

type Handler interface {
	Handle(context.Context, map[string]any) (any, error)
}

type HandlerFunc

type HandlerFunc func(context.Context, map[string]any) (any, error)

func (HandlerFunc) Handle

func (f HandlerFunc) Handle(ctx context.Context, data map[string]any) (any, error)

type JobFilter

type JobFilter struct {
	Queue       string
	Status      string
	Tag         string
	BatchID     string
	CreatedFrom *float64
	CreatedTo   *float64
	Offset      int
	Limit       int // 0 means 50; negative means unbounded for internal maintenance.
}

type JobOption

type JobOption func(*JobPayload)

func OnQueue

func OnQueue(name string) JobOption

func WithBackoff

func WithBackoff(b Backoff) JobOption

func WithMaxAttempts

func WithMaxAttempts(n int) JobOption

func WithTags

func WithTags(tags ...string) JobOption

func WithTimeout

func WithTimeout(t time.Duration) JobOption

type JobPayload

type JobPayload struct {
	ID          string         `json:"id"`
	JobClass    string         `json:"job_class"`
	Data        map[string]any `json:"data"`
	Queue       string         `json:"queue"`
	Attempts    int            `json:"attempts"`
	MaxAttempts int            `json:"max_attempts"`
	Backoff     Backoff        `json:"backoff"`
	Timeout     float64        `json:"timeout"`
	Tags        []string       `json:"tags"`
	BatchID     *string        `json:"batch_id"`
	DelayUntil  *float64       `json:"delay_until"`
	CreatedAt   float64        `json:"created_at"`
	UpdatedAt   float64        `json:"updated_at"`
	StartedAt   *float64       `json:"started_at"`
	CompletedAt *float64       `json:"completed_at"`
	FailedAt    *float64       `json:"failed_at"`
	Status      string         `json:"status"`
	Error       *string        `json:"error"`
	History     []Attempt      `json:"history"`
}

JobPayload preserves Python's field names and Unix timestamps in seconds.

func NewPayload

func NewPayload(name string, data map[string]any) *JobPayload

func (*JobPayload) Clone

func (p *JobPayload) Clone() *JobPayload

func (*JobPayload) UnmarshalJSON

func (p *JobPayload) UnmarshalJSON(raw []byte) error

Decode job data losslessly: JSON integers may exceed float64's exact range.

func (*JobPayload) Validate

func (p *JobPayload) Validate() error

type JobRequest

type JobRequest struct {
	Name    string
	Data    map[string]any
	Options []JobOption
}

type MemoryDriver

type MemoryDriver struct {
	// contains filtered or unexported fields
}

MemoryDriver is safe for concurrent goroutines, but is not shared by processes.

func NewMemoryDriver

func NewMemoryDriver() *MemoryDriver

func (*MemoryDriver) BulkDeleteJobs

func (d *MemoryDriver) BulkDeleteJobs(ctx context.Context, ids []string, limit int) (int, error)

func (*MemoryDriver) Complete

func (d *MemoryDriver) Complete(ctx context.Context, p *JobPayload) error

func (*MemoryDriver) Connect

func (d *MemoryDriver) Connect(ctx context.Context) error

func (*MemoryDriver) CountJobs

func (d *MemoryDriver) CountJobs(ctx context.Context, f JobFilter) (int, error)

func (*MemoryDriver) Delete

func (d *MemoryDriver) Delete(ctx context.Context, id string) error

func (*MemoryDriver) Disconnect

func (d *MemoryDriver) Disconnect(context.Context) error

func (*MemoryDriver) Fail

func (d *MemoryDriver) Fail(ctx context.Context, p *JobPayload, msg string) error

func (*MemoryDriver) Flush

func (d *MemoryDriver) Flush(ctx context.Context, q string) error

func (*MemoryDriver) GetBatch

func (d *MemoryDriver) GetBatch(ctx context.Context, id string) (map[string]any, error)

func (*MemoryDriver) GetJob

func (d *MemoryDriver) GetJob(ctx context.Context, id string) (*JobPayload, error)

func (*MemoryDriver) GetJobs

func (d *MemoryDriver) GetJobs(ctx context.Context, f JobFilter) ([]*JobPayload, error)

func (*MemoryDriver) GetMetrics

func (d *MemoryDriver) GetMetrics(ctx context.Context, q string) (map[string]any, error)

func (*MemoryDriver) GetPendingBatches

func (d *MemoryDriver) GetPendingBatches(ctx context.Context, limit int) ([]map[string]any, error)

func (*MemoryDriver) GetSupervisorStats

func (d *MemoryDriver) GetSupervisorStats(ctx context.Context, age float64) ([]map[string]any, error)

func (*MemoryDriver) IncrementBatchCounter

func (d *MemoryDriver) IncrementBatchCounter(ctx context.Context, id, field string, delta int) (map[string]any, error)

func (*MemoryDriver) Pop

func (d *MemoryDriver) Pop(ctx context.Context, q string) (*JobPayload, error)

func (*MemoryDriver) PopDelayed

func (d *MemoryDriver) PopDelayed(ctx context.Context) ([]*JobPayload, error)

func (*MemoryDriver) Promote

func (d *MemoryDriver) Promote(ctx context.Context, id string) (bool, error)

func (*MemoryDriver) Prune

func (d *MemoryDriver) Prune(ctx context.Context, o PruneOptions) (int, error)

func (*MemoryDriver) PruneMetrics

func (d *MemoryDriver) PruneMetrics(ctx context.Context, age float64) (int, error)

func (*MemoryDriver) PruneTerminalJobs

func (d *MemoryDriver) PruneTerminalJobs(ctx context.Context, o PruneOptions) (int, error)

func (*MemoryDriver) Push

func (d *MemoryDriver) Push(ctx context.Context, p *JobPayload) (string, error)

func (*MemoryDriver) PushMany

func (d *MemoryDriver) PushMany(ctx context.Context, ps []*JobPayload) ([]string, error)

func (*MemoryDriver) Queues

func (d *MemoryDriver) Queues(ctx context.Context) ([]string, error)

func (*MemoryDriver) RecentThroughput

func (d *MemoryDriver) RecentThroughput(ctx context.Context, seconds int, q string) (map[string]int, error)

func (*MemoryDriver) ReconcileIndexes

func (d *MemoryDriver) ReconcileIndexes(ctx context.Context, _ int) (int, error)

func (*MemoryDriver) ReconcilePendingJobs

func (d *MemoryDriver) ReconcilePendingJobs(ctx context.Context, _ int) (int, error)

func (*MemoryDriver) RecordMetric

func (d *MemoryDriver) RecordMetric(ctx context.Context, q, m string, v float64) error

func (*MemoryDriver) Release

func (d *MemoryDriver) Release(ctx context.Context, p *JobPayload, delay float64) error

func (*MemoryDriver) ReportSupervisor

func (d *MemoryDriver) ReportSupervisor(ctx context.Context, m map[string]any) error

func (*MemoryDriver) RequeueStuckJobs

func (d *MemoryDriver) RequeueStuckJobs(ctx context.Context, age float64, q string) (int, error)

func (*MemoryDriver) Size

func (d *MemoryDriver) Size(ctx context.Context, q string) (int, error)

func (*MemoryDriver) StoreBatch

func (d *MemoryDriver) StoreBatch(ctx context.Context, id string, m map[string]any) error

func (*MemoryDriver) UpdateBatch

func (d *MemoryDriver) UpdateBatch(ctx context.Context, id string, m map[string]any) error

type NullBalancer

type NullBalancer struct{}

func (NullBalancer) Recommend

func (NullBalancer) Recommend(_ context.Context, _ Driver, _ []string, current int) (int, error)

type PruneOptions

type PruneOptions struct {
	Queue            string
	Status           string
	Tag              string
	OlderThanSeconds *float64
	Limit            int // 0 means no cap.
}

func (PruneOptions) Validate

func (o PruneOptions) Validate() error

type Pruner

type Pruner struct {
	Queue  *Queue
	Config Config
}

func (*Pruner) Run

func (p *Pruner) Run(ctx context.Context) error

func (*Pruner) RunOnce

func (p *Pruner) RunOnce(ctx context.Context) (int, error)

type Queue

type Queue struct {
	Driver Driver
	Events *EventBus
	Config Config
	// contains filtered or unexported fields
}

func NewQueue

func NewQueue(d Driver) *Queue

func Open

func Open(ctx context.Context, c Config) (*Queue, error)

func (*Queue) Batch

func (q *Queue) Batch(jobs []JobRequest) *Batch

func (*Queue) BuildPayload

func (q *Queue) BuildPayload(name string, data map[string]any, options ...JobOption) (*JobPayload, error)

func (*Queue) Bulk

func (q *Queue) Bulk(ctx context.Context, jobs []JobRequest) ([]string, error)

func (*Queue) Close

func (q *Queue) Close(ctx context.Context) error

func (*Queue) Flush

func (q *Queue) Flush(ctx context.Context, queue string) error

func (*Queue) GetJob

func (q *Queue) GetJob(ctx context.Context, id string) (*JobPayload, error)

func (*Queue) GetJobs

func (q *Queue) GetJobs(ctx context.Context, filter JobFilter) ([]*JobPayload, error)

func (*Queue) Later

func (q *Queue) Later(ctx context.Context, name string, delay time.Duration, data map[string]any, options ...JobOption) (string, error)

func (*Queue) Metrics

func (q *Queue) Metrics(ctx context.Context, queue string) (map[string]any, error)

func (*Queue) Prune

func (q *Queue) Prune(ctx context.Context, o PruneOptions) (int, error)

func (*Queue) Push

func (q *Queue) Push(ctx context.Context, name string, data map[string]any, options ...JobOption) (string, error)

func (*Queue) Queues

func (q *Queue) Queues(ctx context.Context) ([]string, error)

func (*Queue) ReconcileBatch

func (q *Queue) ReconcileBatch(ctx context.Context, id string) error

ReconcileBatch retries pending callback dispatch after worker/storage failures.

func (*Queue) ReconcileBatches

func (q *Queue) ReconcileBatches(ctx context.Context, limit int) (int, error)

func (*Queue) Register

func (q *Queue) Register(name string, h Handler, options ...JobOption) error

func (*Queue) RegisterFunc

func (q *Queue) RegisterFunc(name string, h HandlerFunc, options ...JobOption) error

func (*Queue) RetryFailed

func (q *Queue) RetryFailed(ctx context.Context, f JobFilter) (int, error)

func (*Queue) Size

func (q *Queue) Size(ctx context.Context, queue string) (int, error)

type RedisDriver

type RedisDriver struct {
	AutoCleanupOnDiskFull bool
	ReconcileOnConnect    bool
	// contains filtered or unexported fields
}

RedisDriver uses the Python driver's hashes, lists and sorted-set indexes. Configure the exported options before Connect or concurrent use. Redis must retain queue keys (use noeviction); this driver targets standalone Redis. Concurrent external key-type/schema changes are unsupported. Type preflight detects existing corruption, but is not a lock against out-of-band tampering.

func NewRedisDriver

func NewRedisDriver(url, prefix string) (*RedisDriver, error)

func (*RedisDriver) BulkDeleteJobs

func (d *RedisDriver) BulkDeleteJobs(ctx context.Context, ids []string, limit int) (int, error)

func (*RedisDriver) Complete

func (d *RedisDriver) Complete(ctx context.Context, job *JobPayload) error

func (*RedisDriver) Connect

func (d *RedisDriver) Connect(ctx context.Context) error

func (*RedisDriver) CountJobs

func (d *RedisDriver) CountJobs(ctx context.Context, filter JobFilter) (int, error)

func (*RedisDriver) Delete

func (d *RedisDriver) Delete(ctx context.Context, id string) error

func (*RedisDriver) Disconnect

func (d *RedisDriver) Disconnect(context.Context) error

func (*RedisDriver) Fail

func (d *RedisDriver) Fail(ctx context.Context, job *JobPayload, message string) error

func (*RedisDriver) Flush

func (d *RedisDriver) Flush(ctx context.Context, queue string) error

func (*RedisDriver) GetBatch

func (d *RedisDriver) GetBatch(ctx context.Context, id string) (map[string]any, error)

func (*RedisDriver) GetJob

func (d *RedisDriver) GetJob(ctx context.Context, id string) (*JobPayload, error)

func (*RedisDriver) GetJobs

func (d *RedisDriver) GetJobs(ctx context.Context, filter JobFilter) ([]*JobPayload, error)

func (*RedisDriver) GetMetrics

func (d *RedisDriver) GetMetrics(ctx context.Context, queue string) (map[string]any, error)

func (*RedisDriver) GetPendingBatches

func (d *RedisDriver) GetPendingBatches(ctx context.Context, limit int) ([]map[string]any, error)

func (*RedisDriver) GetSupervisorStats

func (d *RedisDriver) GetSupervisorStats(ctx context.Context, staleAfter float64) ([]map[string]any, error)

func (*RedisDriver) IncrementBatchCounter

func (d *RedisDriver) IncrementBatchCounter(ctx context.Context, id, field string, delta int) (map[string]any, error)

func (*RedisDriver) IsStorageFullError

func (d *RedisDriver) IsStorageFullError(err error) bool

func (*RedisDriver) Pop

func (d *RedisDriver) Pop(ctx context.Context, queue string) (*JobPayload, error)

func (*RedisDriver) PopDelayed

func (d *RedisDriver) PopDelayed(ctx context.Context) ([]*JobPayload, error)

func (*RedisDriver) Promote

func (d *RedisDriver) Promote(ctx context.Context, id string) (bool, error)

func (*RedisDriver) Prune

func (d *RedisDriver) Prune(ctx context.Context, options PruneOptions) (int, error)

func (*RedisDriver) PruneMetrics

func (d *RedisDriver) PruneMetrics(ctx context.Context, age float64) (int, error)

func (*RedisDriver) PruneTerminalJobs

func (d *RedisDriver) PruneTerminalJobs(ctx context.Context, options PruneOptions) (int, error)

func (*RedisDriver) Push

func (d *RedisDriver) Push(ctx context.Context, payload *JobPayload) (string, error)

func (*RedisDriver) PushMany

func (d *RedisDriver) PushMany(ctx context.Context, payloads []*JobPayload) ([]string, error)

func (*RedisDriver) Queues

func (d *RedisDriver) Queues(ctx context.Context) ([]string, error)

func (*RedisDriver) RecentThroughput

func (d *RedisDriver) RecentThroughput(ctx context.Context, seconds int, queue string) (map[string]int, error)

func (*RedisDriver) ReconcileIndexes

func (d *RedisDriver) ReconcileIndexes(ctx context.Context, batch int) (int, error)

ReconcileIndexes removes stale/wrong memberships and backfills missing ones. Its return value counts removed memberships, as in Python (not additions).

func (*RedisDriver) ReconcilePendingJobs

func (d *RedisDriver) ReconcilePendingJobs(ctx context.Context, batch int) (int, error)

ReconcilePendingJobs also discovers hashes missing from every index. Malformed hashes are retained for inspection, but cannot remain runnable.

func (*RedisDriver) RecordMetric

func (d *RedisDriver) RecordMetric(ctx context.Context, queue, metric string, value float64) error

func (*RedisDriver) Release

func (d *RedisDriver) Release(ctx context.Context, job *JobPayload, delay float64) error

func (*RedisDriver) ReportSupervisor

func (d *RedisDriver) ReportSupervisor(ctx context.Context, stats map[string]any) error

func (*RedisDriver) RequeueStuckJobs

func (d *RedisDriver) RequeueStuckJobs(ctx context.Context, age float64, queue string) (int, error)

func (*RedisDriver) Size

func (d *RedisDriver) Size(ctx context.Context, queue string) (int, error)

func (*RedisDriver) StoreBatch

func (d *RedisDriver) StoreBatch(ctx context.Context, id string, data map[string]any) error

func (*RedisDriver) UpdateBatch

func (d *RedisDriver) UpdateBatch(ctx context.Context, id string, patch map[string]any) error

type SQLDriver

type SQLDriver struct {
	AutoCleanupOnDiskFull bool
	ReconcileOnConnect    bool
	// Set before Connect. Positive values override PostgreSQL's 16/4 defaults;
	// SQLite always uses one open and one idle connection.
	MaxOpenConns int
	MaxIdleConns int
	// contains filtered or unexported fields
}

SQLDriver implements durable SQLite and PostgreSQL storage. The baqueue_go_* tables are intentionally NOT schema-compatible with the Python drivers. Payloads use the shared JSON format; indexed columns are updated in the same transaction as the complete payload, including its attempt history.

func NewPostgresDriver

func NewPostgresDriver(url string) *SQLDriver

NewPostgresDriver uses a Go-owned schema, separate from Python's tables. Configure the exported options before Connect; do not change them concurrently.

func NewSQLiteDriver

func NewSQLiteDriver(path string) *SQLDriver

NewSQLiteDriver uses a Go-owned schema, separate from Python's tables. Configure the exported options before Connect; do not change them concurrently.

func (*SQLDriver) BulkDeleteJobs

func (d *SQLDriver) BulkDeleteJobs(ctx context.Context, ids []string, limit int) (int, error)

func (*SQLDriver) Complete

func (d *SQLDriver) Complete(ctx context.Context, p *JobPayload) error

func (*SQLDriver) Connect

func (d *SQLDriver) Connect(ctx context.Context) error

func (*SQLDriver) CountJobs

func (d *SQLDriver) CountJobs(ctx context.Context, filter JobFilter) (int, error)

func (*SQLDriver) Delete

func (d *SQLDriver) Delete(ctx context.Context, id string) error

func (*SQLDriver) Disconnect

func (d *SQLDriver) Disconnect(ctx context.Context) error

func (*SQLDriver) Fail

func (d *SQLDriver) Fail(ctx context.Context, p *JobPayload, message string) error

func (*SQLDriver) Flush

func (d *SQLDriver) Flush(ctx context.Context, queue string) error

func (*SQLDriver) GetBatch

func (d *SQLDriver) GetBatch(ctx context.Context, id string) (map[string]any, error)

func (*SQLDriver) GetJob

func (d *SQLDriver) GetJob(ctx context.Context, id string) (*JobPayload, error)

func (*SQLDriver) GetJobs

func (d *SQLDriver) GetJobs(ctx context.Context, filter JobFilter) ([]*JobPayload, error)

func (*SQLDriver) GetMetrics

func (d *SQLDriver) GetMetrics(ctx context.Context, queue string) (map[string]any, error)

func (*SQLDriver) GetPendingBatches

func (d *SQLDriver) GetPendingBatches(ctx context.Context, limit int) ([]map[string]any, error)

func (*SQLDriver) GetSupervisorStats

func (d *SQLDriver) GetSupervisorStats(ctx context.Context, staleAfter float64) ([]map[string]any, error)

func (*SQLDriver) IncrementBatchCounter

func (d *SQLDriver) IncrementBatchCounter(ctx context.Context, id, field string, delta int) (map[string]any, error)

func (*SQLDriver) Pop

func (d *SQLDriver) Pop(ctx context.Context, queue string) (*JobPayload, error)

func (*SQLDriver) PopDelayed

func (d *SQLDriver) PopDelayed(ctx context.Context) ([]*JobPayload, error)

func (*SQLDriver) Promote

func (d *SQLDriver) Promote(ctx context.Context, id string) (bool, error)

func (*SQLDriver) Prune

func (d *SQLDriver) Prune(ctx context.Context, options PruneOptions) (int, error)

func (*SQLDriver) PruneMetrics

func (d *SQLDriver) PruneMetrics(ctx context.Context, seconds float64) (int, error)

func (*SQLDriver) PruneTerminalJobs

func (d *SQLDriver) PruneTerminalJobs(ctx context.Context, options PruneOptions) (int, error)

func (*SQLDriver) Push

func (d *SQLDriver) Push(ctx context.Context, payload *JobPayload) (string, error)

func (*SQLDriver) PushMany

func (d *SQLDriver) PushMany(ctx context.Context, payloads []*JobPayload) ([]string, error)

func (*SQLDriver) Queues

func (d *SQLDriver) Queues(ctx context.Context) ([]string, error)

func (*SQLDriver) RecentThroughput

func (d *SQLDriver) RecentThroughput(ctx context.Context, seconds int, queue string) (map[string]int, error)

func (*SQLDriver) ReconcileIndexes

func (d *SQLDriver) ReconcileIndexes(ctx context.Context, _ int) (int, error)

func (*SQLDriver) ReconcilePendingJobs

func (d *SQLDriver) ReconcilePendingJobs(ctx context.Context, _ int) (int, error)

func (*SQLDriver) RecordMetric

func (d *SQLDriver) RecordMetric(ctx context.Context, queue, metric string, value float64) error

func (*SQLDriver) Release

func (d *SQLDriver) Release(ctx context.Context, p *JobPayload, delay float64) error

func (*SQLDriver) ReportSupervisor

func (d *SQLDriver) ReportSupervisor(ctx context.Context, stats map[string]any) error

func (*SQLDriver) RequeueStuckJobs

func (d *SQLDriver) RequeueStuckJobs(ctx context.Context, seconds float64, queue string) (int, error)

func (*SQLDriver) Size

func (d *SQLDriver) Size(ctx context.Context, queue string) (int, error)

func (*SQLDriver) StoreBatch

func (d *SQLDriver) StoreBatch(ctx context.Context, id string, data map[string]any) error

func (*SQLDriver) UpdateBatch

func (d *SQLDriver) UpdateBatch(ctx context.Context, id string, data map[string]any) error

type ScheduleEntry

type ScheduleEntry struct {
	JobClass string         `json:"job_class"`
	Cron     string         `json:"cron"`
	Every    float64        `json:"every"`
	Queue    string         `json:"queue"`
	Payload  map[string]any `json:"payload"`
}

type Scheduler

type Scheduler struct {
	Queue *Queue
	// contains filtered or unexported fields
}

func NewScheduler

func NewScheduler(q *Queue, entries []ScheduleEntry) (*Scheduler, error)

func (*Scheduler) Add

func (s *Scheduler) Add(e ScheduleEntry) error

func (*Scheduler) Run

func (s *Scheduler) Run(ctx context.Context) error

func (*Scheduler) Tick

func (s *Scheduler) Tick(ctx context.Context, now time.Time) error

Tick reserves due entries under the lock, then dispatches without holding it. Event listeners may register schedules without deadlocking the scheduler.

type SimpleBalancer

type SimpleBalancer struct{ MinWorkers, MaxWorkers int }

func (SimpleBalancer) Recommend

func (b SimpleBalancer) Recommend(ctx context.Context, d Driver, queues []string, current int) (int, error)

type SuccessHandler

type SuccessHandler interface {
	OnSuccess(context.Context, any, *JobPayload) error
}

type Supervisor

type Supervisor struct {
	Queue    *Queue
	Config   SupervisorConfig
	Balancer Balancer
	Pruner   *Pruner
	// contains filtered or unexported fields
}

func NewSupervisor

func NewSupervisor(q *Queue, c SupervisorConfig) *Supervisor

func (*Supervisor) Run

func (s *Supervisor) Run(ctx context.Context) error

func (*Supervisor) Scale

func (s *Supervisor) Scale(count int)

Scale retires surplus workers after their current job finishes.

func (*Supervisor) Stats

func (s *Supervisor) Stats() map[string]any

type SupervisorConfig

type SupervisorConfig struct {
	Name                      string   `json:"name"`
	Queues                    []string `json:"queues"`
	Balance                   string   `json:"balance"`
	MinWorkers                int      `json:"min_workers"`
	MaxWorkers                int      `json:"max_workers"`
	MaxJobsPerWorker          int      `json:"max_jobs_per_worker"`
	MaxTime                   float64  `json:"max_time"`
	Sleep                     float64  `json:"sleep"`
	Timeout                   float64  `json:"timeout"`
	MemoryLimit               int      `json:"memory_limit"`
	RecoverStuckJobs          bool     `json:"recover_stuck_jobs"`
	StuckProcessingSeconds    float64  `json:"stuck_processing_seconds"`
	StuckCheckIntervalSeconds float64  `json:"stuck_check_interval_seconds"`
}

func DefaultSupervisorConfig

func DefaultSupervisorConfig() SupervisorConfig

func (*SupervisorConfig) UnmarshalJSON

func (c *SupervisorConfig) UnmarshalJSON(b []byte) error

func (SupervisorConfig) Validate

func (c SupervisorConfig) Validate() error

type Worker

type Worker struct {
	// contains filtered or unexported fields
}

func NewWorker

func NewWorker(q *Queue, o WorkerOptions) *Worker

func (*Worker) Run

func (w *Worker) Run(ctx context.Context) error

Run stops fetching on cancellation or Stop. Stop allows the active job to finish.

func (*Worker) RunOnce

func (w *Worker) RunOnce(ctx context.Context) (bool, error)

RunOnce claims and executes at most one job. Do not call concurrently on one worker.

func (*Worker) Stats

func (w *Worker) Stats() map[string]any

func (*Worker) Stop

func (w *Worker) Stop()

type WorkerOptions

type WorkerOptions struct {
	Name    string
	Queues  []string
	Sleep   time.Duration
	Timeout time.Duration
	MaxJobs int
	MaxTime time.Duration
}

Directories

Path Synopsis
Package cli embeds BaQueue commands in an application that registers its jobs.
Package cli embeds BaQueue commands in an application that registers its jobs.
cmd
baqueue command
stress command
Stress exercises Redis recovery.
Stress exercises Redis recovery.
Package dashboard exposes the BaQueue dashboard as a standard HTTP handler.
Package dashboard exposes the BaQueue dashboard as a standard HTTP handler.
examples
dashboard command
dispatch command
worker command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL