The "on demand" Account creation logic on context can be flaky and should use locking #6345
Open
1 task done
Labels
bug
For issues that describe a defect or regression in the released software
needs triage
For issues that are awaiting triage by the core development team
priority medium
Projects
Prerequisites
Issue Description
User Accounts in the API currently work like this:
createAccount
will start.To migitate this, the API does a simple thing: Swallow the error, and continue with the request like it was a normal unauthenticated one: reactioncommerce/api-core/src/util/buildContext.js#L75
If errrors occur, the client will never know since no error is thrown.
(And neither does the server, the Logger.error won't actually log and in turn is swallowed by some Apollo Context logic)
This doesn't match well with the expectation of the api client, which might either be a storefront or some other microservice.
Expected result would be: Send five authenticated requests, await response including account being created, receive five responses, dedicated to that authenticated account.
Actual result: Send five authenticated requests, first one creates the Account and returns, up to 4 of the other requests will cause a silent
createAccount
failure and the client will receive a result as if the requests where send as unauthenticated.Steps to Reproduce
I made a sandbox which demonstrates this behavior: https://codesandbox.io/s/account-concurrency-locking-demo-50qi4?file=/src/index.ts
Notice how the array(which would be our DB) will contain several entires/accounts, since the other requests fired before the first one finished.
Now in the RC api, this would gladly not happen, since MongoDB will deny insertion due to a unique key constraint. Instead, the RC api would do the aforementioned error swallowing and treat the subsequent requests as unauthenticated.
Notice how only one entry is created, since a lock is placed on the ID "someOtherRandomId" and subsequent function calls will pick up that now existing entry. The console logs give some insight on the order of things.
Note: Since codesandbox runs this on an actual server, and multiple people might be trying this out at the same time, pick a unique long ID for testing, or simply fork it.
Possible Solution
Add a locking mechanism.
Wait for a lock on the current accountId(or could also be some other auth header, depends on implementation), then, recheck if an account was created in the meantime, else create it.
For simple setups that either have a single server or have sticky proxy sessions(although no guarantee), a builtin solution storing an object in memory might be fine, just like shown in the sandbox above.
For distributed systems, using Redis might be reasonable, Redlock is a default implementation that could do the job.
Since account creation doesn't happen too frequently compared to e.g. a catalog query though, and for each new account, the period where locks are necesary is rather short, the performance gain of redis might not outweight the increased complexity in infrastructure - It could also work fine to just reuse mongodb to keep track of locks.
I assume it would be optimal if the locking mechanism is pluggable, similar to the
appEvents
here: reactioncommerce/api-core/src/ReactionAPICore.js#L126Further context
Accounts
to the accounts-js account creation logic which operates onUsers
same as Meteor Accounts did, we still want to keep the Accounts logic pluggable for other, potentially oauth2-based solutions.Versions
latest
The text was updated successfully, but these errors were encountered: