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

[9.x] Fix takeUntilTimeout method of LazyCollection #41354

Merged

Conversation

gdebrauwer
Copy link
Contributor

Bug was discovered in #41275 by @JosephSilber.

The current implementation of takeUntilTimeout method in LazyCollection always fetches the next item before checking if the timeout has been exceeded. This new implementation only fetches the next item when we are sure that the timeout has not been exceeded.

@taylorotwell taylorotwell merged commit 34bb59a into laravel:9.x Mar 6, 2022
return new static(function () use ($timeout) {
$iterator = $this->getIterator();

if (! $iterator->valid() || $this->now() > $timeout) {
Copy link
Member

Choose a reason for hiding this comment

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

This is actually still pulling that extra element. The iterator's valid method always pulls the next item 😞

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah sorry, I did not know that 😞

Comment on lines +1423 to +1429
yield $iterator->key() => $iterator->current();

while ($iterator->valid() && $this->now() < $timeout) {
$iterator->next();

yield $iterator->key() => $iterator->current();
}
Copy link
Member

Choose a reason for hiding this comment

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

This can be simplified into a single foreach block.

Comment on lines +199 to +223
public function testTakeUntilTimeoutWithPastTimeout()
{
$timeout = Carbon::now()->subMinute();

$mock = m::mock(LazyCollection::class.'[now]');

$results = $mock
->times(10)
->tap(function ($collection) use ($mock, $timeout) {
tap($collection)
->mockery_init($mock->mockery_getContainer())
->shouldAllowMockingProtectedMethods()
->shouldReceive('now')
->times(1)
->andReturn(
(clone $timeout)->add(1, 'minute')->getTimestamp(),
);
})
->takeUntilTimeout($timeout)
->all();

$this->assertSame([], $results);

m::close();
}
Copy link
Member

Choose a reason for hiding this comment

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

This test doesn't actually test that it's lazy. It simply tests the returned result. The actual values pulled out of the original generator are not inspected here.

Look at the SupportLazyCollectionIsLazyTest class for how to ensure it is actually lazy.

In fact, creating a laziness test for the code in this PR would fail, since it still always pulls an extra unnecessary item from the original generator.


yield $iterator->key() => $iterator->current();

while ($iterator->valid() && $this->now() < $timeout) {
Copy link
Member

Choose a reason for hiding this comment

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

Same here. It is always pulling an extra item.

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

Successfully merging this pull request may close these issues.

None yet

3 participants