mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 14:06:36 +08:00
218 lines
4.7 KiB
Go
218 lines
4.7 KiB
Go
// Package driver_asynq_cron provides the Asynq cron schedule driver plugin for Cordis.
|
|
package driver_asynq_cron
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hibiken/asynq"
|
|
|
|
"github.com/Rain-kl/Wavelet/core"
|
|
)
|
|
|
|
// Option configures the Asynq cron scheduler driver plugin.
|
|
type Option func(*Plugin)
|
|
|
|
// WithRedisOpt sets the Redis connection options for Asynq scheduler.
|
|
func WithRedisOpt(opt asynq.RedisConnOpt) Option {
|
|
return func(p *Plugin) {
|
|
p.redisOpt = opt
|
|
}
|
|
}
|
|
|
|
// WithLocation sets the timezone location for the cron scheduler.
|
|
func WithLocation(loc *time.Location) Option {
|
|
return func(p *Plugin) {
|
|
p.location = loc
|
|
}
|
|
}
|
|
|
|
// WithSchedulerOpts sets custom Asynq SchedulerOpts.
|
|
func WithSchedulerOpts(opts *asynq.SchedulerOpts) Option {
|
|
return func(p *Plugin) {
|
|
p.schedulerOpts = opts
|
|
}
|
|
}
|
|
|
|
// WithScheduler injects a pre-configured Asynq Scheduler instance.
|
|
func WithScheduler(sched *asynq.Scheduler) Option {
|
|
return func(p *Plugin) {
|
|
p.scheduler = sched
|
|
}
|
|
}
|
|
|
|
// Plugin implements core.Plugin and core.Driver for Asynq Cron Scheduler.
|
|
type Plugin struct {
|
|
mu sync.RWMutex
|
|
redisOpt asynq.RedisConnOpt
|
|
location *time.Location
|
|
schedulerOpts *asynq.SchedulerOpts
|
|
scheduler *asynq.Scheduler
|
|
running bool
|
|
coreCtx *core.Context
|
|
}
|
|
|
|
// New creates a new Asynq Cron Scheduler driver plugin.
|
|
func New(opts ...Option) *Plugin {
|
|
p := &Plugin{
|
|
redisOpt: asynq.RedisClientOpt{Addr: "127.0.0.1:6379"},
|
|
location: time.Local,
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
if opt != nil {
|
|
opt(p)
|
|
}
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
// Name returns the unique plugin identifier.
|
|
func (p *Plugin) Name() string {
|
|
return "driver_asynq_cron"
|
|
}
|
|
|
|
// Apply mounts the Asynq Cron Scheduler driver into the micro-kernel Context.
|
|
func (p *Plugin) Apply(ctx *core.Context) error {
|
|
p.mu.Lock()
|
|
p.coreCtx = ctx
|
|
p.mu.Unlock()
|
|
|
|
ctx.OnDispose(func() error {
|
|
return p.Stop(context.Background())
|
|
})
|
|
|
|
return ctx.RegisterDriver(p)
|
|
}
|
|
|
|
// Type returns DriverTypeScheduler.
|
|
func (p *Plugin) Type() core.DriverType {
|
|
return core.DriverTypeScheduler
|
|
}
|
|
|
|
// Start registers scheduled cron entries from Context and boots the Asynq scheduler.
|
|
func (p *Plugin) Start(_ context.Context) error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.running {
|
|
return nil
|
|
}
|
|
|
|
if p.scheduler == nil {
|
|
opts := p.schedulerOpts
|
|
if opts == nil {
|
|
opts = &asynq.SchedulerOpts{
|
|
Location: p.location,
|
|
}
|
|
} else if opts.Location == nil && p.location != nil {
|
|
opts.Location = p.location
|
|
}
|
|
|
|
p.scheduler = asynq.NewScheduler(p.redisOpt, opts)
|
|
}
|
|
|
|
if p.coreCtx != nil && p.coreCtx.Schedules() != nil {
|
|
for _, sd := range p.coreCtx.Schedules().Schedules() {
|
|
payloadBytes, err := encodePayload(sd.Payload)
|
|
if err != nil {
|
|
return fmt.Errorf("driver_asynq_cron: encode payload for task %q failed: %w", sd.TaskType, err)
|
|
}
|
|
|
|
task := asynq.NewTask(sd.TaskType, payloadBytes)
|
|
asynqOpts := buildAsynqOptions(sd.Options)
|
|
|
|
if _, err := p.scheduler.Register(sd.Spec, task, asynqOpts...); err != nil {
|
|
return fmt.Errorf("driver_asynq_cron: register schedule %q for task %q failed: %w", sd.Spec, sd.TaskType, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := p.scheduler.Start(); err != nil {
|
|
return fmt.Errorf("driver_asynq_cron: start scheduler failed: %w", err)
|
|
}
|
|
|
|
p.running = true
|
|
return nil
|
|
}
|
|
|
|
// Stop gracefully shuts down the Asynq cron scheduler.
|
|
func (p *Plugin) Stop(_ context.Context) error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if !p.running {
|
|
return nil
|
|
}
|
|
|
|
p.running = false
|
|
|
|
if p.scheduler != nil {
|
|
p.scheduler.Shutdown()
|
|
p.scheduler = nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsRunning returns whether the scheduler is running.
|
|
func (p *Plugin) IsRunning() bool {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
return p.running
|
|
}
|
|
|
|
// Scheduler returns the underlying Asynq scheduler instance.
|
|
func (p *Plugin) Scheduler() *asynq.Scheduler {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
return p.scheduler
|
|
}
|
|
|
|
func encodePayload(payload any) ([]byte, error) {
|
|
if payload == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
switch val := payload.(type) {
|
|
case []byte:
|
|
return val, nil
|
|
case string:
|
|
return []byte(val), nil
|
|
default:
|
|
return json.Marshal(val)
|
|
}
|
|
}
|
|
|
|
func buildAsynqOptions(opts map[string]any) []asynq.Option {
|
|
if len(opts) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var res []asynq.Option
|
|
|
|
if q, ok := opts["queue"].(string); ok && q != "" {
|
|
res = append(res, asynq.Queue(q))
|
|
}
|
|
|
|
if retry, ok := opts["retry"].(int); ok {
|
|
res = append(res, asynq.MaxRetry(retry))
|
|
} else if maxRetry, ok := opts["max_retry"].(int); ok {
|
|
res = append(res, asynq.MaxRetry(maxRetry))
|
|
}
|
|
|
|
if timeout, ok := opts["timeout"].(time.Duration); ok {
|
|
res = append(res, asynq.Timeout(timeout))
|
|
}
|
|
|
|
if retention, ok := opts["retention"].(time.Duration); ok {
|
|
res = append(res, asynq.Retention(retention))
|
|
}
|
|
|
|
return res
|
|
}
|