[GH-ISSUE #201] [BUG] panic in handler function is not caught #2093

Closed
opened 2026-03-15 19:07:54 +03:00 by kerem · 3 comments
Owner

Originally created by @rverton on GitHub (Sep 27, 2020).
Original GitHub issue: https://github.com/hibiken/asynq/issues/201

Originally assigned to: @hibiken on GitHub.

After a long time debugging why my tasks just fail without me giving any hint why, I noticed that some code in my handler result in a runtime panic. I found this commit here which should catch panics in handler as I understood, but it does not. The job just fails and is put into the retry queue.

To Reproduce

My code is like this:

func loggingMiddleware(h asynq.Handler) asynq.Handler {
	return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
		fmt.Println("before")
		err := h.ProcessTask(ctx, t)
		if err != nil {
			log.Errorf("task execution failed: %v", err)
			return err
		}
		fmt.Println("after")
		return nil
	})
}

func handler(ctx context.Context, t *asynq.Task) error {
	panic("foo")
	return nil
}

func worker() error {
	opts := asynq.RedisClientOpt{Addr: "localhost:6379"}
	srv := asynq.NewServer(opts, asynq.Config{
		Concurrency: WORKER,
	})

	mux := asynq.NewServeMux()
	mux.Use(loggingMiddleware)
	mux.HandleFunc(TASK_PIPE, handler)

	return srv.Run(mux)
}

Running this, I see before printed, but thats it. Nothing happens after this (and I can see with asynq stats that the task failed).

Expected behavior
The runtime panic should be converted to an error and returned.

Environment (please complete the following information):

  • OS: macOS
  • asynq v0.12.0
Originally created by @rverton on GitHub (Sep 27, 2020). Original GitHub issue: https://github.com/hibiken/asynq/issues/201 Originally assigned to: @hibiken on GitHub. After a long time debugging why my tasks just fail without me giving any hint why, I noticed that some code in my handler result in a runtime panic. I found [this](https://github.com/hibiken/asynq/pull/3) commit here which *should* catch panics in handler as I understood, but it does not. The job just fails and is put into the retry queue. **To Reproduce** My code is like this: ```go func loggingMiddleware(h asynq.Handler) asynq.Handler { return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error { fmt.Println("before") err := h.ProcessTask(ctx, t) if err != nil { log.Errorf("task execution failed: %v", err) return err } fmt.Println("after") return nil }) } func handler(ctx context.Context, t *asynq.Task) error { panic("foo") return nil } func worker() error { opts := asynq.RedisClientOpt{Addr: "localhost:6379"} srv := asynq.NewServer(opts, asynq.Config{ Concurrency: WORKER, }) mux := asynq.NewServeMux() mux.Use(loggingMiddleware) mux.HandleFunc(TASK_PIPE, handler) return srv.Run(mux) } ``` Running this, I see `before` printed, but thats it. Nothing happens after this (and I can see with `asynq stats` that the task failed). **Expected behavior** The runtime panic should be converted to an error and returned. **Environment (please complete the following information):** - OS: macOS - asynq v0.12.0
kerem 2026-03-15 19:07:54 +03:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@rverton commented on GitHub (Sep 27, 2020):

I just noticed that I'm also not able to catch a regular error in my logging middleware. Am I doing something wrong here? What is the common way to handle errors in handlers/logging them?

<!-- gh-comment-id:699690301 --> @rverton commented on GitHub (Sep 27, 2020): I just noticed that I'm also not able to catch a regular error in my logging middleware. Am I doing something wrong here? What is the common way to handle errors in handlers/logging them?
Author
Owner

@hibiken commented on GitHub (Sep 27, 2020):

@rverton Thank you for opening this issue.

The loggingMiddleware you provided seems fine, it should be catching errors returned from your Handler. I will look into that. As for the panic case, the middleware cannot catch that case with the current implementation. I may look into a way to enable that or I can simply clarify this in the documentation.

For error handling, there's a specific handler called ErrorHandler which should be invoked whenever the Handler returns an error or panics.
You can provide ErrorHandler in the Config to handle errors.

func reportError(ctx context.Context, task *asynq.Task, err error) {
        retried, _ := asynq.GetRetryCount(ctx)
        maxRetry, _ := asynq.GetMaxRetry(ctx)
        if retried >= maxRetry {
                // retry exhausted
                err = fmt.Errorf("retry exhausted for task %s: %w", task.Type, err)
        }
        fmt.Fprintf(os.Stderr, "[ERROR HANDLER] Notify Error Service about error: %v\n", err)
}

srv := asynq.NewServer(
    redisConnOpts,
    asynq.Config{
        // ... other config options
        ErrorHandler: asynq.ErrorHandlerFunc(reportError),
    },
)

Let me know if this works for you :)

<!-- gh-comment-id:699696791 --> @hibiken commented on GitHub (Sep 27, 2020): @rverton Thank you for opening this issue. The `loggingMiddleware` you provided seems fine, it should be catching errors returned from your Handler. I will look into that. As for the panic case, the middleware cannot catch that case with the current implementation. I may look into a way to enable that or I can simply clarify this in the documentation. For error handling, there's a specific handler called `ErrorHandler` which should be invoked whenever the `Handler` returns an error or panics. You can provide [`ErrorHandler`](https://pkg.go.dev/github.com/hibiken/asynq#ErrorHandler) in the `Config` to handle errors. ```go func reportError(ctx context.Context, task *asynq.Task, err error) { retried, _ := asynq.GetRetryCount(ctx) maxRetry, _ := asynq.GetMaxRetry(ctx) if retried >= maxRetry { // retry exhausted err = fmt.Errorf("retry exhausted for task %s: %w", task.Type, err) } fmt.Fprintf(os.Stderr, "[ERROR HANDLER] Notify Error Service about error: %v\n", err) } srv := asynq.NewServer( redisConnOpts, asynq.Config{ // ... other config options ErrorHandler: asynq.ErrorHandlerFunc(reportError), }, ) ``` Let me know if this works for you :)
Author
Owner

@rverton commented on GitHub (Sep 28, 2020):

Hey @hibiken,
thanks for the fast response. Awesome, defining the ErrorHandler in the server config allows me to print all occurred errors. It also did catch the runtime panic caused in my handler! I found the example code here now but did not know there is even a config option for this. Maybe you can add a hint in the docs for this, because it's hard to see why a handler fails when the default behavior is to be silent when an error occurred.

Thanks for your project and help!

<!-- gh-comment-id:700126285 --> @rverton commented on GitHub (Sep 28, 2020): Hey @hibiken, thanks for the fast response. Awesome, defining the `ErrorHandler` in the server config allows me to print all occurred errors. It also did catch the runtime panic caused in my handler! I found the example code [here](https://github.com/hibiken/asynq/blob/master/server.go#L105) now but did not know there is even a config option for this. Maybe you can add a hint in the docs for this, because it's hard to see why a handler fails when the default behavior is to be silent when an error occurred. Thanks for your project and help!
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/asynq#2093
No description provided.