fix(jobs): timer leak in Module.Stop and warn on unknown runner names

Module.Stop used time.After which leaks the timer if the done channel
wins first; switch to time.NewTimer + defer Stop() to align with the
codebase convention.

scheduleEntry silently returned {0, false} for unknown runner names —
typos in production wiring were invisible. Return (entry, known) and
log Warn at NewModule registration time so misconfiguration surfaces.
This commit is contained in:
2026-05-06 08:37:09 +08:00
parent 895a816221
commit 895799a6c9
+14 -9
View File
@@ -47,7 +47,10 @@ func NewModule(cfg Config, deps ModuleDeps) *Module {
sch := NewScheduler(log) sch := NewScheduler(log)
for name, fn := range deps.RunnerFns { for name, fn := range deps.RunnerFns {
entry := scheduleEntry(cfg, name) entry, known := scheduleEntry(cfg, name)
if !known {
log.Warn("jobs.module.unknown_runner", "name", name)
}
sch.Add(name, entry.interval, entry.enabled, fn) sch.Add(name, entry.interval, entry.enabled, fn)
} }
@@ -95,10 +98,12 @@ func (m *Module) Stop(ctx context.Context) {
if grace <= 0 { if grace <= 0 {
grace = 30 * time.Second grace = 30 * time.Second
} }
timer := time.NewTimer(grace)
defer timer.Stop()
select { select {
case <-done: case <-done:
m.log.Info("jobs.module.stopped") m.log.Info("jobs.module.stopped")
case <-time.After(grace): case <-timer.C:
m.log.Warn("jobs.module.shutdown_timeout", "grace_ms", grace.Milliseconds()) m.log.Warn("jobs.module.shutdown_timeout", "grace_ms", grace.Milliseconds())
case <-ctx.Done(): case <-ctx.Done():
m.log.Warn("jobs.module.shutdown_ctx_canceled") m.log.Warn("jobs.module.shutdown_ctx_canceled")
@@ -112,18 +117,18 @@ type schedEntry struct {
enabled bool enabled bool
} }
func scheduleEntry(cfg Config, name string) schedEntry { func scheduleEntry(cfg Config, name string) (schedEntry, bool) {
switch name { switch name {
case "workspace_fetch": case "workspace_fetch":
return schedEntry{cfg.WorkspaceFetch.Interval, cfg.WorkspaceFetch.Enabled} return schedEntry{cfg.WorkspaceFetch.Interval, cfg.WorkspaceFetch.Enabled}, true
case "worktree_prune": case "worktree_prune":
return schedEntry{cfg.WorktreePrune.Interval, cfg.WorktreePrune.Enabled} return schedEntry{cfg.WorktreePrune.Interval, cfg.WorktreePrune.Enabled}, true
case "attachment_cleanup": case "attachment_cleanup":
return schedEntry{cfg.AttachmentCleanup.Interval, cfg.AttachmentCleanup.Enabled} return schedEntry{cfg.AttachmentCleanup.Interval, cfg.AttachmentCleanup.Enabled}, true
case "job_reaper": case "job_reaper":
return schedEntry{cfg.JobReaper.Interval, cfg.JobReaper.Enabled} return schedEntry{cfg.JobReaper.Interval, cfg.JobReaper.Enabled}, true
case "job_purge": case "job_purge":
return schedEntry{cfg.JobPurge.Interval, cfg.JobPurge.Enabled} return schedEntry{cfg.JobPurge.Interval, cfg.JobPurge.Enabled}, true
} }
return schedEntry{0, false} return schedEntry{0, false}, false
} }