Skip to content

fix(core): run getSources promises in a concurrent-safe way#347

Merged
francoischalifour merged 8 commits intonextfrom
fix/core-concurrent-safe-promise
Nov 19, 2020
Merged

fix(core): run getSources promises in a concurrent-safe way#347
francoischalifour merged 8 commits intonextfrom
fix/core-concurrent-safe-promise

Conversation

@francoischalifour
Copy link
Copy Markdown
Contributor

Description

This fixes an issue where promises coming from getSources or getSuggestions resolve out of call order.

+----------------------------------+
|        100ms                     | 
| run(1) +--->  R1                 |
|        300ms                     |
| run(2) +-------------> R2 (SKIP) |
|        200ms                     |
| run(3) +--------> R3             |
+----------------------------------+

In the scenario above, although R2 is the result resolving last, we shouldn't update the results because R3 is more fresh (although it resolved earlier).

Related


cc @redox @Shipow

@codesandbox-ci
Copy link
Copy Markdown

codesandbox-ci Bot commented Oct 21, 2020

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit bf6a07f:

Sandbox Source
@algolia/js-example Configuration

Copy link
Copy Markdown
Contributor

@redox redox left a comment

Choose a reason for hiding this comment

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

That's great! I like how clear you made (and tested) this. I'll let the team figure out whether it's mergeable.

Comment thread packages/autocomplete-core/src/utils.ts Outdated
Promise.all(
sources.filter(Boolean).map((source) => {
return Promise.resolve(normalizeSource<TItem>(source));
return runConcurrentSafePromiseForSource(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If I understand correctly, don't we need to have a difference instance of createConcurrentSafePromise per source here? For each source, we want to keep the last result, not the last result of all the sources.
Let me know if I'm misunderstanding!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah exactly. This case is complex because of the dynamic nature of sources. This PR doesn't cover this case anymore.

Copy link
Copy Markdown
Contributor

@Haroenv Haroenv left a comment

Choose a reason for hiding this comment

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

I need to look even more in detail to the core usage of this, but the util lgtm

Comment thread packages/autocomplete-core/src/__tests__/createConcurrentSafePromise.test.ts Outdated
Comment thread packages/autocomplete-core/src/__tests__/createConcurrentSafePromise.test.ts Outdated
Comment thread packages/autocomplete-core/src/utils.ts Outdated
@francoischalifour
Copy link
Copy Markdown
Contributor Author

The last commits make sure that the concurrency fix works at the getSources level.

It's however not fixed for getItems because we can't keep a same reference to create the wrapper on. This is a limitation that I propose we tackle in another PR.

Copy link
Copy Markdown
Contributor

@eunjae-lee eunjae-lee left a comment

Choose a reason for hiding this comment

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

Looks good to me. The concurrency test is super clear.

Comment thread packages/autocomplete-core/src/__tests__/concurrency.test.ts
Comment thread packages/autocomplete-core/src/__tests__/concurrency.test.ts
// The last item must be the one corresponding to the last query.
expect(itemsHistory[itemsHistory.length - 1]).toEqual(
expect.objectContaining({ label: 'abc' })
);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe add a test to check for the presence of { label: 'a' } as well as the first item?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe a snapshot of all the labels could make sense?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

A snapshot will be fragile because there's no certainty of how many times onStateChange is called. It's called whenever a setter is called (e.g. setIsOpen, etc.), and a setter can be called user-land. It means that if we refactor something, this test will become a false negative.

I'll check for the presence of a though.

@francoischalifour francoischalifour changed the title fix(core): run promises in a concurrent-safe way fix(core): run getSources promises in a concurrent-safe way Nov 19, 2020
@francoischalifour francoischalifour merged commit 5c6ab2e into next Nov 19, 2020
@francoischalifour francoischalifour deleted the fix/core-concurrent-safe-promise branch November 19, 2020 10:04
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.

6 participants