package jobs import ( "context" "encoding/json" "fmt" "sync" ) // Handler defines the function signature for executing a job. type Handler func(ctx context.Context, payload json.RawMessage) error // Runner manages the registration and execution of worker job handlers. type Runner struct { mu sync.RWMutex handlers map[Kind]Handler } // NewRunner creates a new Runner instance. func NewRunner() *Runner { return &Runner{ handlers: make(map[Kind]Handler), } } // Register registers a handler for a specific job kind. func (r *Runner) Register(kind Kind, handler Handler) { r.mu.Lock() defer r.mu.Unlock() r.handlers[kind] = handler } // Len returns the number of registered handlers. func (r *Runner) Len() int { r.mu.RLock() defer r.mu.RUnlock() return len(r.handlers) } // Execute dispatches the job to its registered handler and executes it. // It checks context cancellation and handles panics gracefully. func (r *Runner) Execute(ctx context.Context, job Job) error { r.mu.RLock() handler, exists := r.handlers[job.Kind] r.mu.RUnlock() if !exists { return fmt.Errorf("unknown job kind: %s", job.Kind) } // Check context before starting if err := ctx.Err(); err != nil { return err } return r.executeWithPanicRecovery(ctx, handler, job.Payload) } func (r *Runner) executeWithPanicRecovery(ctx context.Context, handler Handler, payload json.RawMessage) (err error) { defer func() { if rec := recover(); rec != nil { err = fmt.Errorf("job panicked: %v", rec) } }() return handler(ctx, payload) }