mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
tools/cron is now a thin alias over tools/tasks. The legacy ticker + crontab-matching implementation is deleted; the public surface (New, Add/MustAdd, Remove, RemoveAll, Jobs, HasJob, Total, Start, Stop, SetInterval, SetTimezone, HasStarted) is preserved so no callsite changes. app.Cron() and app.Tasks() share a single *tasks.Client, giving one obvious way to schedule work. - tools/tasks/client.go gains the primitives the cron shim needs (Remove, RemoveAll, HasJob, Total, Schedules, Run, PauseAll, ResumeAll, ActiveTickerCount) plus a richer internal scheduleEntry so remote vs local ticking are distinguishable. - cron.Cron.Stop() now pauses local tickers while preserving the registry, matching the legacy semantics expected by ResetBootstrapState and the admin API. - core/base.go builds app.cron via cron.NewFromTasks(app.tasks) so both accessors feed the same state. - apis/cron.go keeps the /crons list + run-now behaviour; lookup now uses HasJob() for an O(1) precheck. Tests rewritten around the shim (TestCronDelegatesToTasks, TestCronAppFacadeLocal, TestCronAppFacadeRemoteFallback, TestCronNewFromTasksShared). Legacy 1-minute-tick assertions (TestCronStartStop) are SKIPped with a pointer to their replacement; crontab parser tests (tools/cron/schedule_test.go) still pass — the parser stays as a validation primitive for settings_model.go. docs/cron-to-tasks.md covers the migration path for services.
124 lines
3.9 KiB
Go
124 lines
3.9 KiB
Go
// Package cron is a thin alias over Hanzo Tasks (tools/tasks) kept for
|
|
// backward compatibility. New code should call app.Tasks() directly.
|
|
//
|
|
// Scheduled jobs registered through this package are delegated to a
|
|
// *tasks.Client — durable when TASKS_URL is set, local goroutine tickers
|
|
// otherwise. The Schedule / NewSchedule helpers remain for cron-expression
|
|
// validation (used by settings validators).
|
|
package cron
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hanzoai/base/tools/tasks"
|
|
)
|
|
|
|
// Cron is a crontab-like scheduler. Since the v2 refactor it is a shim
|
|
// that delegates to a *tasks.Client.
|
|
type Cron struct {
|
|
mu sync.RWMutex
|
|
client *tasks.Client
|
|
timezone *time.Location
|
|
}
|
|
|
|
// New creates a Cron backed by a new local-only tasks.Client.
|
|
// Use NewFromTasks to share an existing client with the rest of the app.
|
|
func New() *Cron {
|
|
return &Cron{
|
|
client: tasks.New("", "", nil),
|
|
timezone: time.UTC,
|
|
}
|
|
}
|
|
|
|
// NewFromTasks wraps an existing tasks.Client.
|
|
func NewFromTasks(c *tasks.Client) *Cron {
|
|
return &Cron{
|
|
client: c,
|
|
timezone: time.UTC,
|
|
}
|
|
}
|
|
|
|
// SetInterval is retained for API compatibility. No-op: the tick cadence is
|
|
// dictated by each schedule's expression, not a global interval.
|
|
func (c *Cron) SetInterval(time.Duration) {}
|
|
|
|
// SetTimezone updates the timezone used for cron-expression evaluation.
|
|
// Kept for API compatibility; the underlying tasks.Client does not currently
|
|
// take a per-client timezone — server-side schedules run in UTC.
|
|
func (c *Cron) SetTimezone(l *time.Location) {
|
|
if l == nil {
|
|
return
|
|
}
|
|
c.mu.Lock()
|
|
c.timezone = l
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// MustAdd is Add that panics on error.
|
|
func (c *Cron) MustAdd(jobId string, cronExpr string, fn func()) {
|
|
if err := c.Add(jobId, cronExpr, fn); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// Add registers a recurring job. cronExpr is any expression accepted by
|
|
// tasks.Client.Add (standard 5-field cron, macros like @daily, or a Go
|
|
// duration string like "30s"). Re-adding with the same id replaces the
|
|
// previous registration.
|
|
func (c *Cron) Add(jobId string, cronExpr string, fn func()) error {
|
|
if fn == nil {
|
|
return errors.New("failed to add new cron job: fn must be non-nil function")
|
|
}
|
|
// Validate cron-expression syntax up front so callers get the same
|
|
// error signal they got pre-refactor. Duration strings bypass this.
|
|
if _, durErr := time.ParseDuration(cronExpr); durErr != nil {
|
|
if _, err := NewSchedule(cronExpr); err != nil {
|
|
return fmt.Errorf("failed to add new cron job: %w", err)
|
|
}
|
|
}
|
|
return c.client.Add(jobId, cronExpr, fn)
|
|
}
|
|
|
|
// Remove deletes a registered job. No-op if the id is unknown.
|
|
func (c *Cron) Remove(jobId string) { c.client.Remove(jobId) }
|
|
|
|
// RemoveAll deletes every registered job.
|
|
func (c *Cron) RemoveAll() { c.client.RemoveAll() }
|
|
|
|
// Total returns the number of registered jobs.
|
|
func (c *Cron) Total() int { return c.client.Total() }
|
|
|
|
// HasJob reports whether a job with the given id is registered.
|
|
func (c *Cron) HasJob(jobId string) bool { return c.client.HasJob(jobId) }
|
|
|
|
// Jobs returns a snapshot of all registered jobs ordered by id.
|
|
func (c *Cron) Jobs() []*Job {
|
|
schedules := c.client.Schedules()
|
|
out := make([]*Job, 0, len(schedules))
|
|
for _, s := range schedules {
|
|
out = append(out, &Job{
|
|
id: s.Name,
|
|
expression: s.Expression,
|
|
client: c.client,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Start resumes all paused tickers. Schedules are auto-started at Add()
|
|
// time, so this is only meaningful after a prior Stop().
|
|
func (c *Cron) Start() { c.client.ResumeAll() }
|
|
|
|
// Stop pauses every local ticker without removing jobs. Registered
|
|
// schedules remain discoverable via Jobs() / HasJob() and can be resumed
|
|
// with Start(). To tear down the scheduler fully, call RemoveAll() or
|
|
// tasks.Client.Stop() on the underlying client.
|
|
func (c *Cron) Stop() { c.client.PauseAll() }
|
|
|
|
// HasStarted reports whether at least one job is present and actively
|
|
// ticking.
|
|
func (c *Cron) HasStarted() bool { return c.client.ActiveTickerCount() > 0 }
|