Skip to content

[12.x] Introduce FailOnException job middleware #56037

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jun 17, 2025

Conversation

cosmastech
Copy link
Contributor

@cosmastech cosmastech commented Jun 13, 2025

Why

I want a job to not retry when a particular exception is thrown. For the life of me, outside of calling $this->fail() inside the job itself, I do not see a clean way of doing this. Hence this middleware.

Example

class MyJob implements ShouldQueue
{
    use InteractsWithQueue;
    
    public function __construct(private array $snsPayload) {}

    public function handle(GetUserFromExternalApiAction $getUserAction): void
    {
        // potentially throws a UserDoesNotExistException
        $user = $getUserAction->handle($this->snsPayload['user_id']);

        // do some other stuff with the user based on the SNS event.
    }

    public function middleware(): array
    {
        return [new FailOnException(fn (\Throwable $e) => ! $e instanceof UserDoesNotExistException)];
        // or more succinctly
        return [new FailOnException([UserDoesNotExistException::class])];
    }
}

@cosmastech cosmastech marked this pull request as draft June 13, 2025 16:17
@cosmastech cosmastech marked this pull request as ready for review June 13, 2025 18:13
@cosmastech cosmastech marked this pull request as draft June 13, 2025 18:14
* @param class-string<\Throwable> ...$exceptions
* @return static
*/
public static function failureIsNot(...$exceptions): static
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we have both failureIsNot and failureIs?

In a project, I want to retry only if the exception is related to the HTTP client, but not on other errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

I had a very similar middleware on this particular project, but your implementation is cleaner, and I already refactored it. Thanks again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was writing one this morning for a project I'm working on 😅 Good to see it's useful for more than just me.

@cosmastech cosmastech marked this pull request as ready for review June 13, 2025 19:04
@henzeb
Copy link
Contributor

henzeb commented Jun 14, 2025

Just my 2 cents: I would love if we can use RetryWhen instead. when is used everywhere else and sounds more like a sentence. We then could also implement the RetryUnless version.

@cosmastech
Copy link
Contributor Author

Just my 2 cents: I would love if we can use RetryWhen instead. when is used everywhere else and sounds more like a sentence. We then could also implement the RetryUnless version.

Been kind of going back and forth on the name this morning. It seems like a better name would be that this is a circuit breaker. As it's written now, it may seem that it's extending the number of tries (on the job or queue level).

@rodrigopedra
Copy link
Contributor

I was also thinking about the name, maybe FailIf would better convey the outcome?

Of course, in that case, the condition should be reversed to:

if (call_user_func($this->failIf, $e, $job) === true) {
    $job->fail($e);
}

But perhaps that communicates better the intent, while still achieving the desired outcome (allow to short circuit retrials on an specfic set of exceptions).

@cosmastech
Copy link
Contributor Author

I was also thinking about the name, maybe FailIf would better convey the outcome?

Of course, in that case, the condition should be reversed to:

if (call_user_func($this->failIf, $e, $job) === true) {
    $job->fail($e);
}

But perhaps that communicates better the intent, while still achieving the desired outcome (allow to short circuit retrials on an specfic set of exceptions).

FailIf may be a little bit wonky too, since technically middleware runs before the job is reached, so it may seem like it would prevent the job from running.

We'll see what the maintainer says. In my head, I'm thinking something like ShortCircuitRetriesWhen. Or even just making it an optional method on the job itself. (We still have all those naming problems if it were moved, but just wanted to float that thought out there.)

@taylorotwell
Copy link
Member

I would suggest renaming the class to reflect the main use case we have in mind... something like FailIfExceptions(fn|exceptions) or something like that.

@cosmastech cosmastech changed the title [12.x] introduce RetryIf job middleware [12.x] Introduce FailOnException job middleware Jun 16, 2025
@cosmastech
Copy link
Contributor Author

I would suggest renaming the class to reflect the main use case we have in mind... something like FailIfExceptions(fn|exceptions) or something like that.

Went with FailOnException. Modified the constructor to match your suggestion as well @taylorotwell.

@taylorotwell
Copy link
Member

Thanks! Could you send me a docs PR for this? @cosmastech

@taylorotwell taylorotwell merged commit ddad40d into laravel:12.x Jun 17, 2025
28 of 60 checks passed
@cosmastech cosmastech deleted the retry-if branch June 17, 2025 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants