[Feature request] On final flush event. #5770

Closed
opened 2026-01-22 15:17:18 +01:00 by admin · 2 comments
Owner

Originally created by @d-ph on GitHub (Nov 14, 2017).

Hello,

I have a very specific use case.

Imagine a function, that creates an entity and immediately flushes it with $em->flush($theNewEntity);. This function can be called from anywhere in the code. Recently I found out, that I need to call this function from some postUpdate listeners. That causes a problem for me, because the flush of the new entity must happen immediately, otherwise there's a race condition (I won't go into details here).

Point being, EntityManager::flush()es, happening in postUpdate listeners, are not actually flushed. They start a db transaction in an already running db transaction, and then commit the second-level transaction (so, e.g. I have the ID of the new entity), but it's not yet visible in the database at that point (because the "master transaction commit" happens slightly later).

In the nutshell, it looks like this:

  1. App calls $em->flush();
  2. Doctrine starts db transation.
  3. Doctrine runs postUpdates for entities.
  4. My function creates the new special entity.
  5. My function $em->flush($newSpecialEntity)
  6. Doctrine starts a second-level db transaction.
  7. Doctrine commits the second-level db transation.
  8. My function tells other systems that the new special entity is in db
    (this is where the race condition happens)
  9. postUpdates routine finishes.
  10. Doctrine commits the first-level db transaction.
  11. Entities and db are synchronised.

I thought, that I'd listen on the postFlush event, but unfortunately this one is called first for the second-level transaction and then for the first-level transaction, and I have no way to tell which is which. I tried to determine that from the $em->getUnitOfWork(), but had some problems with that, because the only difference in the UnitOfWork is the content of the UnitOfWork::$entityChangeSets, which is private and there's no getter for it.

As a workaround, I currently listen on both the onFlush and postFlush events and count the occurrence of them. Essentially I take advantage of the fact, that these 2 events are called in the following order:

  1. onFlush for first-level transation.
  2. onFlush for second-level transaction.
  3. postFlush for second-level transaction.
  4. postFlush for first-level transaction.

The pseudo code for the workaround:

$counter = 0;

function onFlush() {
    $counter += 1;
}

function postFlush() {
    $counter -= 1;

    if ($counter === 0) {
        runMyOnFinalFlushRoutine();
    }
}

It's not very pretty and internal doctrine implementation dependant.

Would it be possible to introduce an event, that's emitted on postFinalFlush, please? When this event is called, end devs should be able to assume, that the enities are definitelly flushed in the database, so they can run their custom logic safely then.

Thank you,
@d-ph

Originally created by @d-ph on GitHub (Nov 14, 2017). Hello, I have a very specific use case. Imagine a function, that creates an entity and immediately flushes it with `$em->flush($theNewEntity);`. This function can be called from anywhere in the code. Recently I found out, that I need to call this function from some postUpdate listeners. That causes a problem for me, because the flush of the new entity must happen immediately, otherwise there's a race condition (I won't go into details here). Point being, `EntityManager::flush()`es, happening in postUpdate listeners, are not actually flushed. They start a db transaction in an already running db transaction, and then commit the second-level transaction (so, e.g. I have the ID of the new entity), but it's not yet visible in the database at that point (because the "master transaction commit" happens slightly later). In the nutshell, it looks like this: 1. App calls $em->flush(); 2. Doctrine starts db transation. 3. Doctrine runs postUpdates for entities. 4. My function creates the new special entity. 5. My function `$em->flush($newSpecialEntity)` 6. Doctrine starts a second-level db transaction. 7. Doctrine commits the second-level db transation. 8. My function tells other systems that the new special entity is in db (this is where the race condition happens) 9. postUpdates routine finishes. 10. Doctrine commits the first-level db transaction. 11. Entities and db are synchronised. I thought, that I'd listen on the postFlush event, but unfortunately this one is called first for the second-level transaction and then for the first-level transaction, and I have no way to tell which is which. I tried to determine that from the `$em->getUnitOfWork()`, but had some problems with that, because the only difference in the UnitOfWork is the content of the `UnitOfWork::$entityChangeSets`, which is private and there's no getter for it. As a workaround, I currently listen on both the onFlush and postFlush events and count the occurrence of them. Essentially I take advantage of the fact, that these 2 events are called in the following order: 1. onFlush for first-level transation. 2. onFlush for second-level transaction. 3. postFlush for second-level transaction. 4. postFlush for first-level transaction. The pseudo code for the workaround: ```php $counter = 0; function onFlush() { $counter += 1; } function postFlush() { $counter -= 1; if ($counter === 0) { runMyOnFinalFlushRoutine(); } } ``` It's not very pretty and internal doctrine implementation dependant. Would it be possible to introduce an event, that's emitted on postFinalFlush, please? When this event is called, end devs should be able to assume, that the enities are definitelly flushed in the database, so they can run their custom logic safely then. Thank you, @d-ph
admin closed this issue 2026-01-22 15:17:18 +01:00
Author
Owner

@fesor commented on GitHub (Nov 14, 2017):

Please note that flush($entity) will not be available as for Doctrine3, it should be replaced by table gateway (#5550).

I currently listen on both the onFlush and postFlush events and count the occurrence of them.

You could just check transaction nesting level:

$connection = $em->getConnection();
if ($connection->getTransactionNestingLevel() > 0) {
    return;
}

// heres your final `postFlush`

race condition happens

This isn't race condition.

If you don't have dependencies from updated entities, you could just use another entity manager instance with separate connection in order to start another transaction and have immediate result in database.

If you have dependency from this entity, then you may consider to fire action on postFlush, not postUpdate to start brand new transaction (or you could use something like domain events).

@fesor commented on GitHub (Nov 14, 2017): Please note that `flush($entity)` will not be available as for Doctrine3, it should be replaced by table gateway (#5550). > I currently listen on both the onFlush and postFlush events and count the occurrence of them. You could just check transaction nesting level: ``` $connection = $em->getConnection(); if ($connection->getTransactionNestingLevel() > 0) { return; } // heres your final `postFlush` ``` > race condition happens This isn't race condition. If you don't have dependencies from updated entities, you could just use another entity manager instance with separate connection in order to start another transaction and have immediate result in database. If you have dependency from this entity, then you may consider to fire action on `postFlush`, not `postUpdate` to start brand new transaction (or you could use something like domain events).
Author
Owner

@d-ph commented on GitHub (Nov 16, 2017):

Hi,

Thanks for the reply.

Thanks for letting me know, that ::flush($entity) isn't available in Doctrine3 -- I didn't know that. I didn't even know, there are plans to release Doctrine3. Doctrine2 is already perfect to me ;p

I didn't know, that DBAL tracks the transaction nesting. I quickly read the code and it looks like something, that will work in my case. Thanks for mentioning this.

This isn't race condition.

Sorry, I should've said "this is where the race condition in my system happens". I shouldn't have mentioned this at all, really, since it's of little significance here.

Thanks for suggesting 2 other approaches. I evaluated them already and they have some downsides in my case. But I'm happy to be checking the transaction nesting level in onFlush, and do my thing then, so that's fine.

I'm closing this ticket now. Thanks again for your help.

@d-ph commented on GitHub (Nov 16, 2017): Hi, Thanks for the reply. Thanks for letting me know, that `::flush($entity)` isn't available in Doctrine3 -- I didn't know that. I didn't even know, there are plans to release Doctrine3. Doctrine2 is already perfect to me ;p I didn't know, that DBAL tracks the transaction nesting. I quickly read the code and it looks like something, that will work in my case. Thanks for mentioning this. > This isn't race condition. Sorry, I should've said "this is where the race condition _in my system_ happens". I shouldn't have mentioned this at all, really, since it's of little significance here. Thanks for suggesting 2 other approaches. I evaluated them already and they have some downsides in my case. But I'm happy to be checking the transaction nesting level in onFlush, and do my thing then, so that's fine. I'm closing this ticket now. Thanks again for your help.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5770