Documentation
¶
Overview ¶
Package chronos is a Redis-backed distributed task queue and scheduler.
A Client enqueues tasks; a Server consumes them by routing each to a handler registered on a Mux. Tasks are strongly typed: define an args type implementing TaskArgs, register a handler with AddHandler (or AddHandlerR to return a result), and enqueue with Enqueue.
type EmailArgs struct{ To string }
func (EmailArgs) Kind() string { return "email:send" }
mux := chronos.NewMux()
chronos.AddHandler(mux, func(ctx context.Context, t *chronos.Task[EmailArgs]) error {
return send(t.Args.To)
})
// At least one queue is required: Start errors if Queues is empty.
srv := chronos.NewServer(rdb, chronos.ServerConfig{
Queues: map[string]int{"default": 1},
Concurrency: 10,
})
srv.Start(ctx, mux)
chronos.Enqueue(ctx, client, EmailArgs{To: "a@b.c"})
Workflows ¶
NewChain runs tasks in sequence (each link starts when its predecessor succeeds); NewGroup fans out tasks in parallel and fires an OnComplete callback when all finish. Results flow between steps: a handler registered with AddHandlerR exposes its result to the next chain link via PrevResult and to a group callback via GroupResults. Chain.ThenGroup embeds a parallel stage inside a chain, and Group.AddChain makes a group member a chain — together they express fan-out/fan-in pipelines.
Scheduling ¶
NewScheduler runs periodic jobs (interval or cron) with leader election, so many instances may run a scheduler and only one fires each trigger.
Inspection ¶
NewInspector reads queue stats, lists and re-runs tasks, pauses queues, and reports scheduler state — the read/operate surface behind the CLI and web console.
Delivery semantics ¶
Delivery is at-least-once: a handler may run more than once (redelivery after a crash, or a recoverer reclaiming a stalled task), so handlers must be idempotent. See the module README for the full list of guarantees and caveats.
Stability ¶
From v1.0.0 the core package follows semantic versioning. The contrib modules (contrib/webui, contrib/prometheus) and internal/ are experimental and not covered by that guarantee.
Example ¶
Example shows the enqueue → handler round trip: define a typed args, register a handler on a Mux, start a Server, and enqueue.
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
"github.com/kenshin579/chronos-go"
)
type EmailArgs struct {
To string `json:"to"`
}
func (EmailArgs) Kind() string { return "email:send" }
func main() {
rdb := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
ctx := context.Background()
mux := chronos.NewMux()
chronos.AddHandler(mux, func(ctx context.Context, t *chronos.Task[EmailArgs]) error {
fmt.Println("sending to", t.Args.To)
return nil
})
srv := chronos.NewServer(rdb, chronos.ServerConfig{
Queues: map[string]int{"default": 1},
Concurrency: 10,
})
_ = srv.Start(ctx, mux)
defer srv.Shutdown(context.Background())
client := chronos.NewClient(rdb)
_, _ = chronos.Enqueue(ctx, client, EmailArgs{To: "a@b.c"}, chronos.WithQueue("default"))
// Output omitted: a real Redis round trip with async processing is not
// deterministic, so go test compiles this example without running it.
}
Output:
Index ¶
- Constants
- Variables
- func AddHandler[T TaskArgs](mux *Mux, fn func(ctx context.Context, task *Task[T]) error)
- func AddHandlerR[T TaskArgs, R any](mux *Mux, fn func(ctx context.Context, task *Task[T]) (R, error))
- func DefaultRetryDelay(retried int, _ error) time.Duration
- func GroupResults[R any, T TaskArgs](t *Task[T]) ([]R, error)
- func PrevResult[R any, T TaskArgs](t *Task[T]) (R, error)
- func RegisterCron[T TaskArgs](s *Scheduler, spec string, args T, opts ...Option) error
- func RegisterInterval[T TaskArgs](s *Scheduler, interval time.Duration, args T, opts ...Option) error
- func SkipRetry(err error) error
- type Chain
- type Client
- type Group
- type GroupInfo
- type Inspector
- func (i *Inspector) DeleteTask(ctx context.Context, qname, taskID string) error
- func (i *Inspector) GetTask(ctx context.Context, qname, taskID string) (*TaskInfo, error)
- func (i *Inspector) GroupMembers(ctx context.Context, cbQueue, groupID string) ([]string, error)
- func (i *Inspector) ListTasks(ctx context.Context, qname, state string, limit int) ([]*TaskInfo, error)
- func (i *Inspector) PauseQueue(ctx context.Context, qname string) error
- func (i *Inspector) PausedQueues(ctx context.Context) ([]string, error)
- func (i *Inspector) Queues(ctx context.Context) ([]*QueueInfo, error)
- func (i *Inspector) ResumeQueue(ctx context.Context, qname string) error
- func (i *Inspector) RunTask(ctx context.Context, qname, taskID string) error
- func (i *Inspector) SchedulerStatus(ctx context.Context) (*SchedulerStatus, error)
- type Metrics
- type MisfirePolicy
- type Mux
- type Option
- func WithDeadLetterDiscard() Option
- func WithMaxRetry(n int) Option
- func WithMisfirePolicy(p MisfirePolicy) Option
- func WithProcessAt(t time.Time) Option
- func WithProcessIn(d time.Duration) Option
- func WithQueue(name string) Option
- func WithRetention(d time.Duration) Option
- func WithTaskID(id string) Option
- func WithUnique(ttl time.Duration) Option
- type QueueInfo
- type RetryDelayFunc
- type ScheduleInfo
- type Scheduler
- type SchedulerConfig
- type SchedulerStatus
- type Server
- type ServerConfig
- type Task
- type TaskArgs
- type TaskInfo
- type TaskOutcome
Examples ¶
Constants ¶
const DefaultMaxRetry = 25
DefaultMaxRetry is the retry budget used when WithMaxRetry is not given.
const DefaultQueue = "default"
DefaultQueue is the queue used when none is specified.
const MaxResultSize = 1 << 20
MaxResultSize bounds a handler result's JSON encoding. Larger results dead-letter the task without retry (the same value would be produced again) — pass a reference (object-store path, row ID) instead.
Variables ¶
var ( ErrTaskNotFound = errors.New("chronos: task not found") ErrInvalidState = errors.New("chronos: invalid task state") )
Errors returned by the Inspector, exposed so callers (e.g. the web console) can map them to the right response.
var ErrDuplicateTask = rdb.ErrDuplicateTask
ErrDuplicateTask is returned by Enqueue when WithUnique is used and an identical task already holds the unique lock.
var ErrNoResult = errors.New("chronos: no result")
ErrNoResult is returned when the previous step (or a group member) produced no result — the handler was registered with AddHandler, or this is the first chain link.
var ErrResultTooLarge = errors.New("chronos: result exceeds MaxResultSize")
ErrResultTooLarge marks a handler result that exceeds MaxResultSize.
Functions ¶
func AddHandler ¶
AddHandler registers a strongly-typed handler for tasks of type T. The Kind is read from the zero value of T, so T's Kind method must use a value receiver. Registering two handlers for the same Kind panics.
func AddHandlerR ¶ added in v0.11.0
func AddHandlerR[T TaskArgs, R any](mux *Mux, fn func(ctx context.Context, task *Task[T]) (R, error))
AddHandlerR registers a handler whose success return value becomes the task's result: it is relayed to the next chain link (read with PrevResult) and collected for the group callback (read with GroupResults). The result is marshalled as JSON. Kind rules and duplicate-registration panics match AddHandler.
Example ¶
ExampleAddHandlerR shows a handler that returns a result, which the next workflow step reads with PrevResult / GroupResults.
package main
import (
"context"
"github.com/kenshin579/chronos-go"
)
type ResizeArgs struct {
Path string `json:"path"`
}
func (ResizeArgs) Kind() string { return "img:resize" }
type ResizeResult struct {
Bytes int `json:"bytes"`
}
func main() {
mux := chronos.NewMux()
chronos.AddHandlerR(mux, func(ctx context.Context, t *chronos.Task[ResizeArgs]) (ResizeResult, error) {
return ResizeResult{Bytes: 1024}, nil
})
_ = mux
}
Output:
func DefaultRetryDelay ¶
DefaultRetryDelay is the default backoff: an exponential cap (base * 2^retried, clamped to retryMaxDelay) with full jitter — the actual delay is uniformly random in [0, cap]. Full jitter spreads retries to avoid thundering herds.
func GroupResults ¶ added in v0.11.0
GroupResults decodes every member result in Add order. It assumes a homogeneous group (every member returned R); a member without a result fails with ErrNoResult. For heterogeneous or partial results use RawGroupResults.
func PrevResult ¶ added in v0.11.0
PrevResult decodes the previous chain link's result:
out, err := chronos.PrevResult[EncodeResult](task)
func RegisterCron ¶
RegisterCron registers args to be enqueued on a standard 5-field cron spec.
func RegisterInterval ¶
func RegisterInterval[T TaskArgs](s *Scheduler, interval time.Duration, args T, opts ...Option) error
RegisterInterval registers args to be enqueued every interval (>= 1s). Any sub-second component is truncated to whole seconds (e.g. 1500ms behaves as 1s). Register all schedules before calling Start (registration is not concurrency- safe with the running tick loop).
Types ¶
type Chain ¶ added in v0.6.0
type Chain struct {
// contains filtered or unexported fields
}
Chain builds a sequence of tasks in which each link is enqueued only after the previous one succeeds. A link that exhausts its retries stops the chain; re-running the dead-lettered link (Inspector/CLI RunTask) resumes it, because every link carries its remaining tail inside its own message.
Handlers must be idempotent, as everywhere in chronos-go: a redelivered link may run its handler more than once. Its successor is enqueued at most once while that successor still exists; if a predecessor is redelivered after its successor already finished and was not retained, the successor can be recreated — the usual at-least-once caveat. Per-link WithRetention keeps the successor's record around and closes that window for its duration.
func NewChain ¶ added in v0.6.0
func NewChain() *Chain
NewChain returns an empty chain builder.
Example ¶
ExampleNewChain runs tasks in sequence; each link starts after the previous one succeeds.
package main
import (
"context"
"github.com/redis/go-redis/v9"
"github.com/kenshin579/chronos-go"
)
type ResizeArgs struct {
Path string `json:"path"`
}
func (ResizeArgs) Kind() string { return "img:resize" }
func main() {
rdb := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
client := chronos.NewClient(rdb)
_, _ = chronos.NewChain().
Then(ResizeArgs{Path: "a.png"}).
Then(ResizeArgs{Path: "b.png"}).
Enqueue(context.Background(), client)
}
Output:
func (*Chain) Enqueue ¶ added in v0.6.0
Enqueue makes the first link available for processing and returns its TaskInfo. Later links run only as their predecessors succeed.
func (*Chain) Then ¶ added in v0.6.0
Then appends a link. opts accepts the usual per-task options (WithQueue, WithMaxRetry, WithRetention, WithProcessIn, ...); WithTaskID and WithUnique are rejected at Enqueue time (the chain owns task IDs, and unique dedup inside chains is not supported).
func (*Chain) ThenGroup ¶ added in v0.11.0
ThenGroup appends a parallel stage: every member of g runs concurrently (each receiving the previous stage's result via PrevResult) and g's OnComplete callback fans the member results in (GroupResults) before the chain continues with the callback's own result. g must not be reused or mutated afterwards. A group cannot be the chain's first stage — use NewGroup directly when no preceding step exists.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client enqueues tasks.
func NewClient ¶
func NewClient(r redis.UniversalClient) *Client
NewClient returns a Client backed by the given Redis client.
type Group ¶ added in v0.7.0
type Group struct {
// contains filtered or unexported fields
}
Group builds a parallel fan-out: every member is enqueued at once, and when ALL members have succeeded, the callback task is enqueued exactly once while its record exists. A member that exhausts its retries parks the group: re-run its dead-letter (Inspector/CLI RunTask) and, once it succeeds, the group resumes — the same stop-and-resume rule chains follow. Abandoned groups (a member deleted and never re-run) expire after rdb.GroupTTL.
Handlers must be idempotent, as everywhere in chronos-go.
func NewGroup ¶ added in v0.7.0
func NewGroup() *Group
NewGroup returns an empty group builder.
Example ¶
ExampleNewGroup fans out members in parallel, then fires OnComplete once all succeed. ThenGroup embeds this as a chain stage; AddChain makes a member a chain.
package main
import (
"context"
"github.com/redis/go-redis/v9"
"github.com/kenshin579/chronos-go"
)
type ResizeArgs struct {
Path string `json:"path"`
}
func (ResizeArgs) Kind() string { return "img:resize" }
func main() {
rdb := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
client := chronos.NewClient(rdb)
_, _ = chronos.NewGroup().
Add(ResizeArgs{Path: "a.png"}).
AddChain(chronos.NewChain().Then(ResizeArgs{Path: "b.png"}).Then(ResizeArgs{Path: "b2.png"})).
OnComplete(ResizeArgs{Path: "manifest"}).
Enqueue(context.Background(), client)
}
Output:
func (*Group) Add ¶ added in v0.7.0
Add appends a member task. Members run in parallel on their own queues with their own options; WithTaskID and WithUnique are rejected at Enqueue time.
func (*Group) AddChain ¶ added in v0.12.0
AddChain appends a chain member: its links run in sequence, and the chain's FINAL link reports the member's completion to this group (its result becomes this member's GroupResults entry). The chain may not contain a ThenGroup stage (one-level nesting only) and its links may not use WithUnique/ WithTaskID or WithDeadLetterDiscard.
func (*Group) Enqueue ¶ added in v0.7.0
Enqueue validates every member up front, creates the group's pending-member record (so a partially failed enqueue can never fire the callback early), then enqueues every member. Enqueueing is sequential and non-atomic only against infrastructure failures: a network error midway leaves already enqueued members running and the group incomplete (its record expires after rdb.GroupTTL).
type GroupInfo ¶ added in v0.7.0
type GroupInfo struct {
GroupID string // the group's identity
MemberIDs []string // deterministic member task IDs ("<groupID>:m<i>")
CallbackID string // the callback's task ID ("<groupID>:cb")
}
GroupInfo describes an enqueued group.
type Inspector ¶
type Inspector struct {
// contains filtered or unexported fields
}
Inspector provides read and administrative access to queues and tasks. It is the foundation the CLI (and any future UI) is built on.
func NewInspector ¶
func NewInspector(r redis.UniversalClient) *Inspector
NewInspector returns an Inspector backed by the given Redis client.
Example ¶
ExampleNewInspector reads operational state: queue stats, task listing, pause/resume, scheduler status.
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
"github.com/kenshin579/chronos-go"
)
func main() {
rdb := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
insp := chronos.NewInspector(rdb)
queues, _ := insp.Queues(context.Background())
for _, q := range queues {
fmt.Printf("%s: pending=%d paused=%v\n", q.Queue, q.Pending, q.Paused)
}
}
Output:
func (*Inspector) DeleteTask ¶
DeleteTask removes a scheduled/retry/archived task and releases its unique lock.
For a task that does not exist or has already been processed it is an idempotent no-op and returns nil (unlike GetTask, it does not return ErrTaskNotFound).
func (*Inspector) GetTask ¶ added in v0.3.0
GetTask returns full detail for a single stored task (scheduled/retry/archived/completed).
func (*Inspector) GroupMembers ¶ added in v0.9.0
GroupMembers returns the IDs of a group's not-yet-succeeded members. cbQueue is the callback queue (a member TaskInfo carries it as GroupQueue).
func (*Inspector) ListTasks ¶
func (i *Inspector) ListTasks(ctx context.Context, qname, state string, limit int) ([]*TaskInfo, error)
ListTasks returns up to limit tasks in the given state (scheduled|retry|archived).
func (*Inspector) PauseQueue ¶ added in v0.10.0
PauseQueue stops servers from consuming the queue (within about one second). Enqueueing, forwarding and recovery continue — work accumulates as pending.
The queue name is not validated: a name that does not exist is still accepted and stays in the paused list until ResumeQueue clears it, with no effect on any active queue.
func (*Inspector) PausedQueues ¶ added in v0.10.0
PausedQueues lists currently paused queue names.
func (*Inspector) ResumeQueue ¶ added in v0.10.0
ResumeQueue lifts a pause; consumption restarts within about one second.
func (*Inspector) RunTask ¶
RunTask promotes a scheduled/retry/archived task so it runs immediately.
For a task that does not exist or has already been processed it is an idempotent no-op and returns nil (unlike GetTask, it does not return ErrTaskNotFound).
func (*Inspector) SchedulerStatus ¶ added in v0.9.0
func (i *Inspector) SchedulerStatus(ctx context.Context) (*SchedulerStatus, error)
SchedulerStatus returns the scheduler's leader and every known schedule: the registry (registered schedules, fired or not) merged by ID with the fire-history keys (which may also hold pre-registry leftovers).
type Metrics ¶
type Metrics interface {
ObserveTask(queue, kind string, outcome TaskOutcome, dur time.Duration)
}
Metrics receives one observation per processed task. Implementations MUST be safe for concurrent use (the server calls it from worker goroutines). The zero/nil Metrics disables observation. The Prometheus implementation lives in the contrib/prometheus module so the core stays dependency-free.
type MisfirePolicy ¶
type MisfirePolicy int
MisfirePolicy decides what happens when a schedule's triggers were missed (e.g. during a leader-election gap or downtime).
const ( // MisfireSkip (default) discards missed triggers and resumes on schedule. MisfireSkip MisfirePolicy = iota // MisfireFireOnce fires exactly one catch-up trigger if any were missed. MisfireFireOnce )
type Mux ¶
type Mux struct {
// contains filtered or unexported fields
}
Mux routes tasks to handlers by their Kind.
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option customizes a single Enqueue call.
func WithDeadLetterDiscard ¶
func WithDeadLetterDiscard() Option
WithDeadLetterDiscard discards the task on retry exhaustion instead of storing it in the archived ZSET. The OnDeadLetter hook still fires.
func WithMaxRetry ¶
WithMaxRetry sets the maximum number of retries before the task is dead-lettered. Defaults to DefaultMaxRetry.
func WithMisfirePolicy ¶
func WithMisfirePolicy(p MisfirePolicy) Option
WithMisfirePolicy sets how a scheduled job handles missed triggers (after a leader-election gap or downtime). Only meaningful for RegisterInterval / RegisterCron; ignored by a plain Enqueue. Defaults to MisfireSkip.
func WithProcessAt ¶
WithProcessAt schedules the task to first become available at t. A non-future time enqueues immediately.
func WithProcessIn ¶
WithProcessIn schedules the task to first become available after d. A non-positive d enqueues immediately.
func WithRetention ¶ added in v0.5.0
WithRetention keeps the task in the "completed" set for d after it succeeds, so it can be inspected (Inspector/CLI state "completed") before the janitor removes it. The default (no option) deletes the task immediately on success. Durations under one second are rounded up to one second; d <= 0 is ignored. Note: on high-throughput queues a long retention grows Redis memory — the per-queue MaxCompleted cap (ServerConfig) bounds the worst case.
func WithTaskID ¶
WithTaskID sets an explicit task ID (used for lookup and correlation). When omitted a random UUID is generated.
This is not deduplication: enqueueing twice with the same ID does not reject the second call — it may overwrite the existing task's body and enqueue it again. For content-based duplicate suppression use WithUnique.
func WithUnique ¶
WithUnique deduplicates tasks by (kind + payload): while a matching task is anywhere in the pipeline (pending, retrying, scheduled), enqueueing another returns ErrDuplicateTask. The lock is released when the task reaches a terminal state (completed / archived / discarded).
While a task is actively being processed, the server's heartbeat renews the lock's TTL, so a single attempt that runs longer than ttl still holds the lock. ttl mainly bounds the lock for the time a task spends waiting (pending / scheduled / retry backoff) where no worker is renewing it; set it comfortably above the expected total waiting time. For a delayed task the lock TTL is automatically extended to cover the delay.
type QueueInfo ¶
type QueueInfo struct {
Queue string
Pending int64
Active int64
Scheduled int64
Retry int64
Archived int64
Completed int64
Paused bool
}
QueueInfo is a queue's per-state task counts.
type RetryDelayFunc ¶
RetryDelayFunc computes how long to wait before the next retry, given the number of retries already performed and the error that caused the failure.
type ScheduleInfo ¶ added in v0.9.0
type ScheduleInfo struct {
ID string
Kind string // "" for fire-history-only entries (pre-registry leftovers)
Queue string
Spec string // "@every 30s" or a 5-field cron expression
LastFired time.Time // zero if the schedule never fired
LastSeen time.Time // zero for fire-history-only entries
Stale bool // registry entry not refreshed within staleAfter
}
ScheduleInfo is one schedule's registry entry merged with its fire history.
type Scheduler ¶
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler registers periodic jobs and, on whichever instance is elected leader, enqueues their due triggers. Every instance may call Start; only the leader enqueues, and deterministic dedup keys prevent double-enqueue during leader handover.
func NewScheduler ¶
func NewScheduler(r redis.UniversalClient, cfg SchedulerConfig) *Scheduler
NewScheduler returns a Scheduler backed by the given Redis client.
Example ¶
ExampleNewScheduler registers a periodic job. Every instance may call Start; leader election ensures only one fires each trigger.
package main
import (
"context"
"time"
"github.com/redis/go-redis/v9"
"github.com/kenshin579/chronos-go"
)
type EmailArgs struct {
To string `json:"to"`
}
func (EmailArgs) Kind() string { return "email:send" }
func main() {
rdb := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
s := chronos.NewScheduler(rdb, chronos.SchedulerConfig{})
_ = chronos.RegisterInterval(s, time.Hour, EmailArgs{To: "digest@b.c"})
_ = s.Start(context.Background())
defer s.Shutdown(context.Background())
}
Output:
func (*Scheduler) Shutdown ¶
Shutdown stops the scheduler; if this instance is the leader it resigns so a follower can take over immediately.
func (*Scheduler) Start ¶
Start launches leader election and the tick loop. Safe to call on every instance; only the leader enqueues. Register all schedules before Start.
For correct cross-instance behavior, every instance must register the same schedules with the same SchedulerConfig.Location and have reasonably synchronized clocks: the deterministic dedup key is derived from the computed trigger instant, so divergent timezones or badly skewed clocks could let two instances treat the same logical trigger as different ones.
type SchedulerConfig ¶
type SchedulerConfig struct {
// Location is the timezone in which cron schedules are evaluated. Defaults to
// time.Local. A CRON_TZ=/TZ= prefix on an individual spec takes precedence over
// this for that schedule.
Location *time.Location
// Logger receives operational logs. Defaults to slog.Default().
Logger *slog.Logger
// LeaderTTL is how long a leadership term lasts before it must be renewed.
// Defaults to 5s. Failover happens within ~LeaderTTL of a leader dying.
LeaderTTL time.Duration
}
SchedulerConfig configures a Scheduler.
type SchedulerStatus ¶ added in v0.9.0
type SchedulerStatus struct {
LeaderID string
Schedules []ScheduleInfo
}
SchedulerStatus reports the current scheduler leader and every schedule that has fired at least once (registered-but-never-fired schedules are invisible; a future schedule registry will list them).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server fetches tasks from Redis and dispatches them to handlers.
func NewServer ¶
func NewServer(r redis.UniversalClient, cfg ServerConfig) *Server
NewServer returns a Server backed by the given Redis client.
type ServerConfig ¶
type ServerConfig struct {
// Queues maps queue name to weight. While every queue has work, a queue with
// weight 6 is dequeued about 6x as often as a queue with weight 1 (smooth
// weighted round-robin — no queue starves). When the queue picked for a round
// is empty, that round falls through to the highest-weight queue that does
// have work, so an idle high-priority queue never blocks lower ones. Weights
// <= 0 are treated as 1; very large weights are capped.
//
// At least one queue is required: there is no implicit default queue, and
// Start returns an error if Queues is empty.
Queues map[string]int
// StrictPriority, if true, always drains higher-weight queues first: a
// lower-weight queue is served only while every higher-weight queue is
// empty. Ties are broken by queue name for determinism.
StrictPriority bool
// Concurrency is the max number of tasks processed simultaneously.
Concurrency int
// Logger receives operational logs. Defaults to slog.Default().
Logger *slog.Logger
// RetryDelayFunc computes the backoff before a retry. Defaults to
// DefaultRetryDelay (exponential + full jitter).
RetryDelayFunc RetryDelayFunc
// OnDeadLetter is invoked when a task exhausts its retries (or returns a
// SkipRetry error). It fires whether the task is archived or discarded.
//
// The heartbeat keeps an actively-processing task's lease fresh, so the
// recoverer does not normally reclaim it. It may still fire more than once in
// pathological cases (the server/heartbeat unavailable long enough for the
// lease to lapse), so make the hook idempotent (the archived ZSET entry is
// deduplicated by task ID).
OnDeadLetter func(ctx context.Context, info *TaskInfo, err error)
// Metrics, if set, receives one observation per processed task. Use the
// contrib/prometheus implementation, or your own. Defaults to nil (disabled).
Metrics Metrics
// ForwardInterval is how often the retry ZSET is scanned for due tasks.
// Defaults to 1s.
ForwardInterval time.Duration
// RecoverInterval is how often stuck PEL entries are reclaimed. Defaults to 15s.
RecoverInterval time.Duration
// RecoverMinIdle is how long a PEL entry must be idle before it is treated as
// abandoned. Defaults to 30s when unset (<= 0). The heartbeat refreshes the
// lease of in-flight tasks every HeartbeatInterval, so a task that runs longer
// than RecoverMinIdle is safe as long as this server (its heartbeat) is alive;
// RecoverMinIdle is the window after a worker actually dies before its tasks
// are reclaimed.
RecoverMinIdle time.Duration
// HeartbeatInterval is how often the server refreshes the lease and unique
// lock of in-flight tasks. Defaults to RecoverMinIdle/3. Must be shorter than
// RecoverMinIdle so an actively-processing task is never reclaimed.
HeartbeatInterval time.Duration
// ArchivedRetention is how long a dead-lettered (archived) task is kept
// before the janitor deletes it. Defaults to 7 days (168h).
ArchivedRetention time.Duration
// MaxArchived caps the number of archived tasks per queue; the janitor
// deletes the oldest beyond this even within the retention window. Defaults
// to 10000. Set negative to disable the size cap.
MaxArchived int
// MaxCompleted caps the number of retained completed tasks per queue; the
// janitor deletes the oldest beyond this even before their retention
// expires. Defaults to 10000. Set negative to disable the size cap.
MaxCompleted int
// JanitorInterval is how often the janitor runs. Defaults to 1 minute.
JanitorInterval time.Duration
}
ServerConfig configures a Server.
type Task ¶
type Task[T TaskArgs] struct { Args T // contains filtered or unexported fields }
Task is a strongly-typed task delivered to a handler.
func (*Task[T]) RawGroupResults ¶ added in v0.11.0
RawGroupResults returns raw member results in Add order (nil = no result). Nil when this task is not a group callback or no member produced a result.
type TaskArgs ¶
type TaskArgs interface {
Kind() string
}
TaskArgs is implemented by every task payload type. Kind returns a stable identifier used to route the task to its handler; it MUST be defined on a value receiver so it can be called on the zero value during registration.
type TaskInfo ¶
type TaskInfo struct {
ID string
Kind string
Queue string
// The following are populated by Inspector.ListTasks / GetTask for tasks
// stored in a state ZSET (scheduled / retry / archived).
State string // "scheduled" | "retry" | "archived" | ...
Payload []byte // raw task payload
Retried int // retries already attempted
MaxRetry int // retry budget
LastErr string // most recent failure message ("" if none)
NextProcessAt time.Time // ZSET score as a time: scheduled-for / retry-at / died-at / expires-at (completed)
CompletedAt time.Time // when the task finished successfully (zero unless retained)
ChainPending int // number of chain links still queued behind this task (0 = none/last)
ChainIndex int // this task's 0-based position in its chain
ChainNext []string // kinds of the links waiting behind this task, in order
GroupID string // group this task belongs to ("" = none)
GroupPending int // members of that group not yet succeeded; populated by GetTask only. 0 also when the group finished or its record expired (see rdb.GroupTTL) or the lookup failed — it is a hint, not an authority.
GroupQueue string // queue holding the group's state (= callback queue)
// HasResult reports whether the handler produced a result (AddHandlerR).
// The result itself travels the workflow (PrevResult/GroupResults); the
// Inspector only reports its presence and size.
HasResult bool
// ResultSize is the result's JSON size in bytes (0 when HasResult=false).
ResultSize int
}
TaskInfo describes an enqueued or stored task. Enqueue returns one with only ID/Kind/Queue set; the Inspector fills the rest for stored tasks.
type TaskOutcome ¶
type TaskOutcome string
TaskOutcome is the terminal result of processing one task, reported to Metrics.
const ( // OutcomeSuccess: the handler returned nil; the task was acked and removed. OutcomeSuccess TaskOutcome = "success" // OutcomeRetry: the handler failed and the task was scheduled for retry. OutcomeRetry TaskOutcome = "retry" // OutcomeDeadLetter: the task exhausted retries (or returned SkipRetry) and // was archived or discarded. OutcomeDeadLetter TaskOutcome = "dead_letter" )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
chronos
command
Command chronos is a CLI for inspecting and administering chronos-go queues.
|
Command chronos is a CLI for inspecting and administering chronos-go queues. |
|
examples
|
|
|
jobscheduler
Package jobscheduler is a verification adapter proving chronos-go can back operator-review's string+[]byte JobScheduler interface.
|
Package jobscheduler is a verification adapter proving chronos-go can back operator-review's string+[]byte JobScheduler interface. |
|
tour
command
Command tour is a narrated, runnable walkthrough of everything chronos-go can do: the core queue, reliability (retry / dead-letter), delayed + unique tasks, the Inspector, the distributed scheduler with leader failover, the retention janitor, the heartbeat, weighted priority queues, completed-task retention, task chains, task groups, workflows (fan-out/fan-in with results), group member chains (fan-out of pipelines), and queue pause/resume.
|
Command tour is a narrated, runnable walkthrough of everything chronos-go can do: the core queue, reliability (retry / dead-letter), delayed + unique tasks, the Inspector, the distributed scheduler with leader failover, the retention janitor, the heartbeat, weighted priority queues, completed-task retention, task chains, task groups, workflows (fan-out/fan-in with results), group member chains (fan-out of pipelines), and queue pause/resume. |
|
internal
|
|
|
base
Package base defines the Redis key layout, task states, and message serialization shared across chronos-go internals.
|
Package base defines the Redis key layout, task states, and message serialization shared across chronos-go internals. |
|
rdb
Package rdb implements the Redis operations backing chronos-go: enqueueing tasks, dequeueing via a consumer group, and acking completion.
|
Package rdb implements the Redis operations backing chronos-go: enqueueing tasks, dequeueing via a consumer group, and acking completion. |
|
testutil
Package testutil provides shared test helpers for chronos-go.
|
Package testutil provides shared test helpers for chronos-go. |