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

Avoid the possibility to do simultaneous calls to the token endpoint #664

Merged
merged 16 commits into from
Dec 4, 2020

Conversation

frederikprijck
Copy link
Member

@frederikprijck frederikprijck commented Dec 4, 2020

This PR ensures the SPA is less likely to do concurrent API calls to the token endpoint.

  • In a single tab we are relying on a promise map that is tracking exising requests that are in flight for the same clientId, audience and scope.
  • In a cross tab scenario, we are relying on browser-tabs-lock, where we have implemented their library as it should, including a retry mechanism to acquire the lock.

This solves #553 and #657

@frederikprijck frederikprijck requested a review from a team as a code owner December 4, 2020 11:35
@frederikprijck frederikprijck marked this pull request as draft December 4, 2020 11:35
@frederikprijck frederikprijck marked this pull request as ready for review December 4, 2020 11:35
@frederikprijck frederikprijck added the review:large Large review label Dec 4, 2020
const canProceed = await acquireLockSpy(...args);
if (canProceed) {
return super.acquireLock(...args);
}
Copy link
Member Author

@frederikprijck frederikprijck Dec 4, 2020

Choose a reason for hiding this comment

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

Changing this enables us to control the locking success/failure from inside the tests.

@@ -663,6 +664,68 @@ describe('Auth0Client', () => {
);
});

describe('concurrency', () => {
it('should call _getTokenSilently multiple times when no call in flight concurrently', async () => {
Copy link
Member Author

Choose a reason for hiding this comment

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

In order to test the promise concurrency fix, we can not rely on the actual HTTP call as this will be avoided by the locking library. So instead of testing that, we are verifying that _getTokenSilently is or isn't called as expected.

);
}

private async _getTokenSilently({ ignoreCache, getTokenOptions }) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Moved the original code to get a token to its own private method in order to be able to easily wrap it.

getTokenOptions
}),
`${this.options.client_id}::${getTokenOptions.audience}::${getTokenOptions.scope}`
);
Copy link
Member Author

Choose a reason for hiding this comment

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

singlePromise is a function that is ensuring we only have a single concurrent promise based on the provided key.

await retryPromise(
() => lock.acquireLock(GET_TOKEN_SILENTLY_LOCK_KEY, 5000),
10
)
Copy link
Member Author

Choose a reason for hiding this comment

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

Instead of using a while loop, retryPromise will control the retrying for us.

Copy link
Contributor

@stevehobbsdev stevehobbsdev left a comment

Choose a reason for hiding this comment

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

Just a couple of questions

__tests__/Auth0Client/getTokenSilently.test.ts Outdated Show resolved Hide resolved
Comment on lines +723 to +724
expect(client1['_getTokenSilently']).toHaveBeenCalledTimes(1);
expect(client2['_getTokenSilently']).not.toHaveBeenCalled();
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 this is true, but just to clarify - is client1 guaranteed to claim the lock first or could this potentially be a flakey test?

Copy link
Contributor

Choose a reason for hiding this comment

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

Promise.all is strictly ordered, so I would say this is fine

Copy link
Member Author

Choose a reason for hiding this comment

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

That is actually a good question. This should be flakey as the lock is acquired async https://github.com/supertokens/browser-tabs-lock/blob/master/index.ts#L93 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

@adamjmcgrath Also when considering the delay linked above?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CH: Changed PR is changing something review:large Large review
Projects
None yet
3 participants