fix(cron): improve panic recovery and config validation

- Use safego.Recover for per-job panic recovery in PG and SQLite cron
  schedulers (captures stack traces vs inline fmt.Sprint)
- Log warning when cron job_timeout config value is invalid instead of
  silently falling back to default
This commit is contained in:
viettranx committed 2026-04-10 19:52:43 +07:00
1 parent 50871eaaae
commit e839f42412
3 files changed
+10 -15

No files matched your search

+4 -1
View File
@@ -3,6 +3,7 @@ package config
import (
"encoding/json"
"fmt"
"log/slog"
"sync"
"time"
@@ -352,9 +353,11 @@ const DefaultJobTimeout = 10 * time.Minute
// JobTimeoutDuration returns the configured job timeout or the default (10m).
func (cc CronConfig) JobTimeoutDuration() time.Duration {
if cc.JobTimeout != "" {
if d, err := time.ParseDuration(cc.JobTimeout); err == nil && d > 0 {
d, err := time.ParseDuration(cc.JobTimeout)
if err == nil && d > 0 {
return d
}
slog.Warn("cron: invalid job_timeout, using default", "value", cc.JobTimeout, "default", DefaultJobTimeout)
}
return DefaultJobTimeout
}
+3 -7
View File
@@ -10,6 +10,7 @@ import (
"github.com/google/uuid"
"github.com/nextlevelbuilder/goclaw/internal/cron"
"github.com/nextlevelbuilder/goclaw/internal/safego"
"github.com/nextlevelbuilder/goclaw/internal/store"
)
@@ -195,13 +196,8 @@ func (s *PGCronStore) checkAndRunDueJobs() {
// due jobs. Now each job runs independently; cache is invalidated per-job.
for _, job := range claimedJobs {
go func(job store.CronJob) {
defer func() {
if r := recover(); r != nil {
slog.Error("cron: job execution panicked", "job_id", job.ID, "job_name", job.Name, "panic", fmt.Sprint(r))
}
// Invalidate cache so the next tick picks up the updated next_run_at.
s.InvalidateCache()
}()
defer safego.Recover(nil, "component", "cron_job", "job_id", job.ID, "job_name", job.Name)
defer s.InvalidateCache()
s.executeOneJob(job, handler, true)
}(job)
}
+3 -7
View File
@@ -12,6 +12,7 @@ import (
"github.com/google/uuid"
"github.com/nextlevelbuilder/goclaw/internal/cron"
"github.com/nextlevelbuilder/goclaw/internal/safego"
"github.com/nextlevelbuilder/goclaw/internal/store"
)
@@ -201,13 +202,8 @@ func (s *SQLiteCronStore) checkAndRunDueJobs() {
// due jobs. Now each job runs independently; cache is invalidated per-job.
for _, job := range claimedJobs {
go func(job store.CronJob) {
defer func() {
if r := recover(); r != nil {
slog.Error("cron: job execution panicked", "job_id", job.ID, "job_name", job.Name, "panic", fmt.Sprint(r))
}
// Invalidate cache so the next tick picks up the updated next_run_at.
s.InvalidateCache()
}()
defer safego.Recover(nil, "component", "cron_job", "job_id", job.ID, "job_name", job.Name)
defer s.InvalidateCache()
s.executeOneJob(job, handler, true)
}(job)
}