MCPcopy Create free account
hub / github.com/riverqueue/river / watchStuck

Method watchStuck

internal/jobexecutor/job_executor.go:277–321  ·  view source on GitHub ↗

Watches for jobs that may have become stuck. i.e. They've run longer than their job timeout (plus a small margin) and don't appear to be responding to context cancellation (unfortunately, quite an easy error to make in Go). Producers use stuck-job notifications for periodic stats and optional user

(ctx context.Context, jobTimeout time.Duration)

Source from the content-addressed store, hash-verified

275// Producers use stuck-job notifications for periodic stats and optional user
276// handlers.
277func (e *JobExecutor) watchStuck(ctx context.Context, jobTimeout time.Duration) context.CancelFunc {
278 // We add a WithoutCancel here so that this inner goroutine becomes
279 // immune to all context cancellations _except_ the one where it's
280 // cancelled because we leave JobExecutor.execute.
281 //
282 // This shadows the context outside the e.ClientJobTimeout > 0 check.
283 ctx, cancel := context.WithCancel(context.WithoutCancel(ctx))
284
285 go func() {
286 const stuckThresholdDefault = 5 * time.Second
287
288 select {
289 case <-ctx.Done():
290 // context cancelled as we leave JobExecutor.execute
291
292 case <-time.After(jobTimeout + cmp.Or(e.StuckThresholdOverride, stuckThresholdDefault)):
293 e.ProducerCallbacks.Stuck(ctx, e.JobRow)
294
295 e.Logger.WarnContext(ctx, e.Name+": Job appears to be stuck",
296 slog.Int64("job_id", e.JobRow.ID),
297 slog.String("kind", e.JobRow.Kind),
298 slog.Duration("timeout", e.ClientJobTimeout),
299 )
300
301 // context cancelled as we leave JobExecutor.execute
302 <-ctx.Done()
303
304 // In case the executor ever becomes unstuck, inform the
305 // producer. However, if we got all the way here there's a good
306 // chance this will never happen (the worker is really stuck and
307 // will never return).
308 defer e.ProducerCallbacks.Unstuck()
309
310 defer func() {
311 e.Logger.InfoContext(ctx, e.Name+": Job became unstuck",
312 slog.Duration("duration", time.Since(e.start)),
313 slog.Int64("job_id", e.JobRow.ID),
314 slog.String("kind", e.JobRow.Kind),
315 )
316 }()
317 }
318 }()
319
320 return cancel
321}
322
323func (e *JobExecutor) invokeErrorHandler(ctx context.Context, res *jobExecutorResult) bool {
324 invokeAndHandlePanic := func(funcName string, errorHandler func() *ErrorHandlerResult) *ErrorHandlerResult {

Callers 1

executeMethod · 0.95

Calls 2

DoneMethod · 0.80
AfterMethod · 0.80

Tested by

no test coverage detected