This commit is contained in:
ponzischeme89
2026-08-17 07:34:23 +12:00
parent 93fb0fd728
commit 36cb1324fd
56 changed files with 1177 additions and 168 deletions
+25
View File
@@ -93,6 +93,7 @@ type Scheduler struct {
store *store.Store
log *slog.Logger
events *adminevents.Bus
paused func() bool
mu sync.RWMutex
tasks map[string]*registered
@@ -101,6 +102,21 @@ type Scheduler struct {
started bool
}
// SetPaused installs the server-wide activity gate. The function is intentionally read at
// execution time so an admin change takes effect without rebuilding the task registry.
func (s *Scheduler) SetPaused(paused func() bool) {
s.mu.Lock()
defer s.mu.Unlock()
s.paused = paused
}
func (s *Scheduler) isPaused() bool {
s.mu.RLock()
paused := s.paused
s.mu.RUnlock()
return paused != nil && paused()
}
func New(st *store.Store, log *slog.Logger, events *adminevents.Bus) *Scheduler {
return &Scheduler{
store: st, log: log.With("component", "scheduler"), events: events,
@@ -219,6 +235,9 @@ func (s *Scheduler) loop(ctx context.Context) {
}
func (s *Scheduler) runDue(ctx context.Context) {
if s.isPaused() {
return
}
now := time.Now()
s.mu.RLock()
entries := make([]*registered, 0, len(s.tasks))
@@ -248,6 +267,9 @@ func (s *Scheduler) RunNow(ctx context.Context, id string) error {
if !ok {
return fmt.Errorf("scheduler: no task %q", id)
}
if s.isPaused() {
return fmt.Errorf("scheduler: server activity is paused for quiet time")
}
entry.mu.Lock()
if entry.running {
entry.mu.Unlock()
@@ -261,6 +283,9 @@ func (s *Scheduler) RunNow(ctx context.Context, id string) error {
}
func (s *Scheduler) execute(ctx context.Context, entry *registered, trigger string) {
if s.isPaused() {
return
}
entry.mu.Lock()
if entry.running {
entry.mu.Unlock()
@@ -40,6 +40,22 @@ func TestATaskNeedsAnIDAndAFunction(t *testing.T) {
sched.Register(Task{ID: "broken"})
}
func TestQuietTimePausesManualTasks(t *testing.T) {
sched := quietScheduler()
ran := false
sched.Register(Task{ID: "quiet", Name: "Quiet", Run: func(context.Context) (string, error) {
ran = true
return "", nil
}})
sched.SetPaused(func() bool { return true })
if err := sched.RunNow(context.Background(), "quiet"); err == nil {
t.Fatal("manual task started during quiet time")
}
if ran {
t.Fatal("quiet-time task function ran")
}
}
func TestAPanickingTaskBecomesAFailedRun(t *testing.T) {
// A background job is the one place a panic takes the whole process down for a reason
// nobody is watching for. One housekeeping job with a nil map must not be able to stop