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

Ensure that if the CachedAsyncIterable is called multiple times in parallel, it does return the correct value #4

Merged
merged 2 commits into from
Oct 21, 2018

Conversation

zbraniecki
Copy link
Collaborator

Nasty little race condition here that was actually messing with Firefox in production already :(

@zbraniecki zbraniecki requested a review from stasm July 30, 2018 21:28
@zbraniecki
Copy link
Collaborator Author

I had to also modify touchNext to avoid a scenario (experienced in production) where touchNext is still operating and code calls for await (let ctx of this.ctxs) and we start fetching things again because this.seen is still waiting for touchNext to complete.

I'm afraid that this makes the "sync iterator over cached items in async iterator" scenario not work as shown by the two failing tests.

Copy link
Contributor

@stasm stasm left a comment

Choose a reason for hiding this comment

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

We'll need sync iteration for projectfluent/fluent.js#100. I think there might be a way to keep it if we maintain two caches: one for the resolved {value, done} objects and one for the promises returned by iterator.next().

Unless I'm mistaken, this would also make it possible to remove the explicit [Symbol.iterator] method from CachedAsyncIterable. We can just use the default implementation from the Array, which CachedIterables extend.

}
return cached[cur++];
return await cached[cur++];
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you create a new array for caching the promises returned by iterator.next() rather than pushing directly to this? This way, you should be able to do:

let nxt = await cachedPromises[cur++];
this.push(nxt);
return nxt;

@stasm
Copy link
Contributor

stasm commented Aug 20, 2018

@zbraniecki Any updates on this? Should we try to publish a new version of cached-iterable with this fix soon?

@zbraniecki zbraniecki self-assigned this Aug 20, 2018
@zbraniecki
Copy link
Collaborator Author

Sorry @stasm - I know it's important, and it also is blocking bmo bug 1483038, but I haven't had the time to properly consume your idea on how to preserve the sync in async, and in result couldn't update the patch. :(

Any chance you know how you'd like an update to this patch to look like and can provide a patch to my PR?

@stasm
Copy link
Contributor

stasm commented Aug 21, 2018

Any chance you know how you'd like an update to this patch to look like and can provide a patch to my PR?

I know what I want it to look like but I might not have time to do it this week nor the next week. Let me see if I can put together a WIP tomorrow morning. To reiterate, I think we should keep a separate cache for promises returned from next() and another one for the {value,done} objects these promises resolve to. We'll need to keep track of which promises have already resolved so that we don't add their resolved values to the sync cache more than once. Perhaps changing CachedIterable to extend Set rather than Array would help?

@zbraniecki
Copy link
Collaborator Author

To reiterate, I think we should keep a separate cache for promises returned from next() and another one for the {value,done} objects these promises resolve to.

I have to admit that I struggle to see the value in this. It seems to me like an additional allocation either via strong or weak ref to store data that the async iterator doesn't need.

My confusion may come from the fact that I don't understand the use case of a sync iterator over async iterator.

I'll wait for your WIP - maybe it'll enlighten me :)

@stasm
Copy link
Contributor

stasm commented Aug 23, 2018

My confusion may come from the fact that I don't understand the use case of a sync iterator over async iterator.

I explained the use-case in #1.

@stasm
Copy link
Contributor

stasm commented Aug 24, 2018

I haven't had the time to look into this this week and I doubt my next week looks any better. If this fix is important, let's remove the sync iterator for now. projectfluent/fluent.js#100 is still weeks from landing. I'll be happy to review an updated PR.

@zbraniecki
Copy link
Collaborator Author

Removed sync iterator over async cachediterable and marked the tests to be skipped for now.

Copy link
Contributor

@stasm stasm left a comment

Choose a reason for hiding this comment

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

Thanks. Please remove the skipped tests, there's no need to keep them right now. We can always resurrect them from git history once we need them. Please also add a short description of this change to CHANGELOG. With that, r=me, feel free to land it yourself. Thanks again!

const iterable = new CachedAsyncIterable(generate());
await Promise.all([
iterable[Symbol.asyncIterator]().next(),
await iterable[Symbol.asyncIterator]().next(),
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for the await here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please add another test in which you call next twice on the same asyncIterator.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, and please assert in all tests that these are actually the same promises.


// We're testing that if the first call to asyncIterator has been
// made, but the value of it has not been returned yet,
// the consequitive call returns the same Promise rather than,
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: consecutive.

async function *generate() {
while (true) {
counter++;
yield await Promise.resolve();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think just yield null; is enough here.

const iterable = new CachedAsyncIterable(generate());
await Promise.all([
iterable.touchNext(2),
iterable[Symbol.asyncIterator]().next(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe add another test where touchNext is actually called multiple times :)

@zbraniecki zbraniecki merged commit fb65ee7 into projectfluent:master Oct 21, 2018
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.

2 participants