[10.x] After commit callback throwing an exception causes broken transactions afterwards #50423
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.
Hey again. It's another try for #50087.
The problem
The problem is that the transaction level is reset to 0 whenever an
DB::afterCommit()
callback fails. Normally, outside of tests that useDatabaseTransactions
trait, this not causing any issues:Because the after commit callbacks are only executed after the commit, where transaction level is already 0, this isn't actually doing anything, as the transaction level cannot be decremented below 0.
However, when you try to do the same in a test that uses
DatabaseTransactions
trait, you will face an issue as the "default" transaction level is 1 and an actual database commit should never happen:Why transaction level is important
As demonstrated above, having a transaction level differ from an actual connection's transaction level leads to broken transactions. If Laravel thinks the transaction level is 0 when it's actually 1, it will attempt to send a
BEGIN TRANSACTION;
query instead of theCREATE SAVEPOINT trans2;
query, which will cause an error on the DBMS side.Why no reports of this bug
This is only reproducible under these 4 conditions:
DatabaseTransactions
traitafterCommit
callbackIt's highly unlikely for anyone to encounter this.
Why fixing this is important
The test example above is simplified. Obviously, we don't have any tests like the above. However, we do have integration tests that call multiple endpoints in a single test, which is how we found out about this problem at all:
Context
We're a large project that has 20k tests, nearly all of which use DatabaseTransactions trait. We've had our own implementation of "after commit" jobs, notifications and events/listeners long before Laravel first introduced it and have tackled a lot of the nuances that come with it. We're now trying to move away from our custom implementation in favour of Laravel built-in methods. We did not encounter any issues with this in our own implementation because we relied on the
committed
event which is outside of the try-catch block, so it works as expected even if something throws.After switching to Laravel's implementation, this bug caused a few of "negative" integration tests to fail.