-
-
Notifications
You must be signed in to change notification settings - Fork 658
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
fix: concurrency issue when running multiple requests #3442
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job Gaston. 👍
@@ -42,7 +42,8 @@ export class ProxyService { | |||
|
|||
private readonly services: Services; | |||
|
|||
private readonly clients: Map<ApiUser['secret'], Unleash> = new Map(); | |||
private readonly clients: Map<ApiUser['secret'], Promise<Unleash>> = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's weird that we store clients wrapped in promises instead of clients themselves. Why is that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question! And open to alternative solutions, but this is to store "we're processing/creating this unleash client, it might be ready soon", but we don't want to do that asynchronously as before because another request (or 15) might come while we're resolving the promise, and another client will be created. Ultimately, we want to know if someone is already processing a client for that secret, and wait until they finished before we proceed to create a new one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The challenge is that the current frontend API creates new node SDK instances on the fly for new tokens discovered.
Creating an SDK instance is an async operation, as it will require to fetch data to be ready.
This is probably not the best design, but fixing the bug at hand we need to make sure we await the client. I do not think there is a way to force that in to not being async, with the current implementation, where SDK instances are created on the fly.
In an optimal implementation of this, we should not use the SDK for other than the evaluation part. This would allow us to keep the caching and async part separate from the evaluation part.
@@ -99,14 +100,13 @@ export class ProxyService { | |||
private async clientForProxyToken(token: ApiUser): Promise<Unleash> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd expect that clientForProxyToken return a client not a promise, what's the reason for this decision?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is currently unchanged and I was lucky it was that way because it fits well with the change I did.
The reason for this is probably that we need to execute a client.start()
here and that's async, which bubbles up until this line
); | ||
let client = this.clients.get(token.secret); | ||
if (!client) { | ||
client = this.createClientForProxyToken(token); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we awaited this one we could return client without the promise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This causes the same problem as before (used the test to validate my assumption). The await will be executed in the event loop and meanwhile, another thread will execute clients.get
, receive an empty response and yield a new createClientForProxyToken
async deleteClientForProxyToken(secret: string): Promise<void> { | ||
let clientPromise = this.clients.get(secret); | ||
if (clientPromise) { | ||
const client = await clientPromise; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is promise storage leaking out to this method. It would be easier to work with the client here
} | ||
|
||
stopAll(): void { | ||
this.clients.forEach((client) => client.destroy()); | ||
this.clients.forEach((promise) => promise.then((c) => c.destroy())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, we pay the price of storing promises and not clients
@gastonfournier I think @kwasniew 's comments are on point here. I know why we went this way but my main worry is that some helpful person will read this code, decide this could be optimised and bring back the bug. If there's no cleaner way to do this, would a comment explaining why this code looks funny help prevent that in future? |
The test should help preventing this issue in the future, but I'll meet with @kwasniew tomorrow morning to look into alternative ways of doing this |
As we discussed, we're going to merge the PR and continue the conversation around improving this offline. |
## About the changes Fix issue when running multiple calls to the /frontend endpoint concurrently, which ends up creating many instances of unleash SDK client.
About the changes
Running multiple calls to the frontend concurrently creates many instances of unleash SDK client.
The test added in commit 34004b5 is to verify we can reproduce the issue with a test case, and we can see the error here
The next commit introduces a fix for the issue
How to reproduce manually
You should see some errors, such as:
[2023-04-03T10:48:49.818] [ERROR] services/proxy-service.ts - Error: The unleash SDK has been initialized more than 10 times at /home/ivar/code/unleash/node_modules/unleash-client/lib/unleash.js:96:29