[8.x] Allow queueing application and service provider callbacks while callbacks are already being processed #39175
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
As of now, queueing a
terminating
-callback while the application is already in the termination process will simply cause the callback to be lost silently, possibly resulting in hard to trace bugs. Same holds true for the applicationbooting
andbooted
callbacks, as well as for the service provider callbacks of the same name.This is problematic, since Job dispatching is using the terminating callbacks internally and nested afterResponse Jobs will not work.
This PR aims to fix this issue by allowing those callbacks to be added while the callback queue is already being processed.
Real Life Usage
We are developing an app that sends notifications to the users, utilising many different kind of Jobs. One Job is responsible for sending Push notifications, which is dispatched directly in a controller. It is also dispatched from another Job, tasked with handling the results of certain user interactions. Dispatching Jobs from other Jobs works fine in Laravel and I see nothing in the documentation that indicates this usage is not supported.
When we attempted to decrease the load on our queue handler, we switched both Jobs to use
Job::dispatchAfterResponse
/dispatch($job)->afterResponse()
. Since this uses something along the lines ofapp()->terminating($job->dispatchNow)
internally, any nested Jobs did not run.Solution
By switching the
foreach
loop to awhile
loop doing acount
on every iteration, we gain the ability to add to our callback stack while processing it. This comes at a slight performance penalty - a naive test of using both methods to iterate over an array of 100,000 entries on my development machine showed the new method to be around 15% / 0.5ms slower. In real world applications, with no more than a few dozen callbacks - realistically something between zero and slightly more than zero 😁 - the performance difference will be no more than at maximum a 1/100th of a millisecond.