Skip to content

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
Contributor

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
Contributor

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
Contributor

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
Contributor

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.

3 participants