AbstractHydrator#iterate() may not cleanup #5562

Open
opened 2026-01-22 15:11:23 +01:00 by admin · 8 comments
Owner

Originally created by @issei-m on GitHub (May 31, 2017).

AFAIK, AbstractHydrator#iterate() doesn't cleanup anything as long as the iteration doesn't reach the final row.
So if I break the loop on the way like following:

foreach ($query->iterate() as $row) {
    if (/* some condition */) {
        break;
    }

    yield $row;
}

The statement cursor is never be closed anymore, this means some file-based DB (like SQLite) may not be released from locking.
Or that is expected result? and these usage is wrong?

Originally created by @issei-m on GitHub (May 31, 2017). AFAIK, `AbstractHydrator#iterate()` doesn't cleanup anything as long as the iteration doesn't [reach the final row](https://github.com/doctrine/doctrine2/blob/205ee72e3360175f687d01c72024c4854dff8363/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php#L166). So if I break the loop on the way like following: ```php foreach ($query->iterate() as $row) { if (/* some condition */) { break; } yield $row; } ``` The statement cursor is never be closed anymore, this means some file-based DB (like SQLite) may not be released from locking. Or that is expected result? and these usage is wrong?
Author
Owner

@Ocramius commented on GitHub (May 31, 2017):

That currently is the expected result, as hydration is highly dependent on previous results, sadly :-\

I can't remember if we already did this, but each query gets its own hydrator, which mitigates the problem (garbage collection does), when the iterator is released.

@Ocramius commented on GitHub (May 31, 2017): That currently is the expected result, as hydration is highly dependent on previous results, sadly :-\ I can't remember if we already did this, but each query gets its own hydrator, which mitigates the problem (garbage collection does), when the iterator is released.
Author
Owner

@issei-m commented on GitHub (May 31, 2017):

@Ocramius Thanks for your quick response!

First of all, I understood that is the expected result.
However, we have/use the Generator nowaday, so relying on whether iterator has reached to the final row doesn't make sense IMHO.
Could we solve it in 2.x? If so I'm willing to work for this.😏😏

@issei-m commented on GitHub (May 31, 2017): @Ocramius Thanks for your quick response! First of all, I understood that is the expected result. However, we have/use the Generator nowaday, so relying on whether iterator has reached to the final row doesn't make sense IMHO. Could we solve it in 2.x? If so I'm willing to work for this.😏😏
Author
Owner

@Ocramius commented on GitHub (May 31, 2017):

Could we solve it in 2.x?

No, changing this behavior will most likely cause a BC break here, so I strongly suggest that you try this against develop (3.x)

@Ocramius commented on GitHub (May 31, 2017): > Could we solve it in 2.x? No, changing this behavior will most likely cause a BC break here, so I strongly suggest that you try this against `develop` (3.x)
Author
Owner

@issei-m commented on GitHub (May 31, 2017):

I got it. Thanks a lot!

@issei-m commented on GitHub (May 31, 2017): I got it. Thanks a lot!
Author
Owner

@beberlei commented on GitHub (Dec 8, 2020):

@simPod Looping you into this very old issue here w.r.t. to toIterable.

Could we potentially solve this with a finally statement so that AbstractHydrator::cleanup is called when the iterable ends in any way?

@beberlei commented on GitHub (Dec 8, 2020): @simPod Looping you into this very old issue here w.r.t. to `toIterable`. Could we potentially solve this with a finally statement so that `AbstractHydrator::cleanup` is called when the iterable ends in any way?
Author
Owner

@simPod commented on GitHub (Feb 5, 2021):

@beberlei having a reproducible test case would help. I'll try to examine the execution flow.

@simPod commented on GitHub (Feb 5, 2021): @beberlei having a reproducible test case would help. I'll try to examine the execution flow.
Author
Owner

@JoniJnm commented on GitHub (Aug 17, 2023):

The problem is here:

5577d51c44/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php (L183-L192)

If the loop is broken, the cleanup is not executed. One possible solution is to wrap the while into a try-finally:

        try {
            while (true) {
                $row = $this->statement()->fetchAssociative();

                if ($row === false) {
                    // $this->cleanup(); // done on finally

                    break;
                }

                $result = [];

                // .... rest of code with yields
            }
        } finally {
            $this->cleanup();
        }
@JoniJnm commented on GitHub (Aug 17, 2023): The problem is here: https://github.com/doctrine/orm/blob/5577d51c447172ff8fdb81433277e625f4945cfe/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php#L183-L192 If the loop is broken, the cleanup is not executed. One possible solution is to wrap the while into a try-finally: ```php try { while (true) { $row = $this->statement()->fetchAssociative(); if ($row === false) { // $this->cleanup(); // done on finally break; } $result = []; // .... rest of code with yields } } finally { $this->cleanup(); } ```
Author
Owner

@oleg-andreyev commented on GitHub (Apr 19, 2024):

Today found exact same issue.

  1. consumers are running using UniqueEntity validator with custom method that uses toIterable
  2. because toIterable creates new hydrator but event-manager is shared number of onClear listeners are growing because cleanup in not called after iterating all results.
@oleg-andreyev commented on GitHub (Apr 19, 2024): Today found exact same issue. 1. consumers are running using UniqueEntity validator with custom method that uses `toIterable` 2. because `toIterable` creates new hydrator but event-manager is shared number of onClear listeners are growing because cleanup in not called after iterating all results.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5562