Panic in user-provided callbacks (HealthCheckFunc, GroupAggregator, PeriodicTaskConfigProvider) crashes worker process
Author: blackwell-systemsCreated May 14, 2026Updated May 14, 2026
Problem
Three user-provided callbacks are called inside library goroutines without defer recover(). A panic in any of them kills the entire worker process, terminating all in-flight tasks.
Affected callbacks
| Callback | File | Called in goroutine at |
|---|---|---|
HealthCheckFunc |
healthcheck.go:75 | healthcheck.go:64 |
GroupAggregator.Aggregate |
aggregator.go:161 | aggregator.go:122 (go a.aggregate(t)) |
PeriodicTaskConfigProvider.GetConfigs |
periodic_task_manager.go:209 | periodic_task_manager.go:131 |
Example: common HealthCheckFunc bug
srv := asynq.NewServer(redisOpt, asynq.Config{
HealthCheckFunc: func(err error) {
// Bug: err is nil when ping succeeds
metrics.Record("health", err.Error()) // PANIC: nil pointer dereference
},
})The library calls HealthCheckFunc(nil) when the broker ping succeeds. The user's code dereferences err.Error() on nil. The goroutine panics. The entire worker process dies. All in-flight tasks are lost.
Why this matters
processor.perform() already recovers from panics in task handlers (processor.go:424). The same principle applies to these other user-facing interfaces: the library spawns the goroutine, the library calls the user's code, so the library is responsible for recovering if that code panics.
Source: hibiken/asynq