Skip to content
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

Mitigate problems with EntityManager::flush() reentrance since 2.16.0 (Take 2) #10915

Merged
merged 1 commit into from
Aug 25, 2023

Conversation

mpdude
Copy link
Contributor

@mpdude mpdude commented Aug 21, 2023

The changes from #10547, which landed in 2.16.0, cause problems for users calling EntityManager::flush() from within postPersist event listeners.

  • When UnitOfWork::commit() is re-entered, the "inner" (reentrant) call will start working through all changesets. Eventually, it finishes with all insertions being performed and UoW::$entityInsertions being empty. After return, the entity insertion order, an array computed at the beginning of UnitOfWork::executeInserts(), still contains entities that now have been processed already. This leads to a strange-looking SQL error where the number of parameters used does not match the number of parameters bound. This has been reported as 2.16.0 - Issue with persisting entities #10869.

  • The fixes made to the commit order computation may lead to a different entity insertion order than previously. postPersist listener code may be affected by this when accessing generated IDs for other entities than the one the event has been dispatched for. This ID may not yet be available when the insertion order is different from the one that was used before 2.16. This has been mentioned in Mitigate problems with EntityManager::flush() reentrance since 2.16.0 #10906 (comment).

This PR suggests to address both issues by dispatching the postPersist event only after all new entities have their rows inserted into the database. Likewise, dispatch postRemove only after all deletions have been executed.

This solves the first issue because the sequence of insertions or deletions has been processed completely before we start calling event listeners. This way, potential changes made by listeners will no longer be relevant.

Regarding the second issue, I think deferring postPersist a bit until all entities have been inserted does not violate any promises given, hence is not a BC break. In 2.15, this event was raised after all insertions for a particular class had been processed - so, it was never an "immediate" event for every single entity. #10547 moved the event handling to directly after every single insertion. Now, this PR moves it back a bit to after all insertions.

Fixes #10869.

lib/Doctrine/ORM/UnitOfWork.php Outdated Show resolved Hide resolved
lib/Doctrine/ORM/UnitOfWork.php Outdated Show resolved Hide resolved
]);
}

public function testPostPersistListenerUpdatingObjectFieldWhileOtherInsertPending(): void
Copy link
Member

Choose a reason for hiding this comment

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

What exactly are we testing here? It's unclear to me. There is a listener that alters field, but then we don't check that field contains test, so we're not even sure it comes into play.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would have to check whether it’s a leftover from when I tried to reproduce things, or actually necessary to make the subsequent flush() call do anything.

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 guess it has to do with checking that the ID is available at the time the listener is called... I'll try to improve that.

@mpdude
Copy link
Contributor Author

mpdude commented Aug 22, 2023

@greg0ire Better like this?

@greg0ire
Copy link
Member

Yes 👍 I trust you've already noticed the CS issues?

….0 (Take 2)

The changes from doctrine#10547, which landed in 2.16.0, cause problems for users calling `EntityManager::flush()` from within `postPersist` event listeners.

* When `UnitOfWork::commit()` is re-entered, the "inner" (reentrant) call will start working through all changesets. Eventually, it finishes with all insertions being performed and `UoW::$entityInsertions` being empty. After return, the entity insertion order, an array computed at the beginning of `UnitOfWork::executeInserts()`, still contains entities that now have been processed already. This leads to a strange-looking SQL error where the number of parameters used does not match the number of parameters bound. This has been reported as doctrine#10869.

* The fixes made to the commit order computation may lead to a different entity insertion order than previously. `postPersist` listener code may be affected by this when accessing generated IDs for other entities than the one the event has been dispatched for. This ID may not yet be available when the insertion order is different from the one that was used before 2.16. This has been mentioned in doctrine#10906 (comment).

This PR suggests to address both issues by dispatching the `postPersist` event only after _all_ new entities have their rows inserted into the database. Likewise, dispatch `postRemove` only after _all_ deletions have been executed.

This solves the first issue because the sequence of insertions or deletions has been processed completely _before_ we start calling event listeners. This way, potential changes made by listeners will no longer be relevant.

Regarding the second issue, I think deferring `postPersist` a bit until _all_ entities have been inserted does not violate any promises given, hence is not a BC break. In 2.15, this event was raised after all insertions _for a particular class_ had been processed - so, it was never an "immediate" event for every single entity. doctrine#10547 moved the event handling to directly after every single insertion. Now, this PR moves it back a bit to after _all_ insertions.
@greg0ire greg0ire merged commit 2f9e987 into doctrine:2.16.x Aug 25, 2023
42 checks passed
@greg0ire greg0ire added this to the 2.16.2 milestone Aug 25, 2023
@greg0ire
Copy link
Member

Thanks @mpdude !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2.16.0 - Issue with persisting entities
4 participants