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

fix(reviewRequestService): upsert in case of concurrent api calls #861

Merged
merged 4 commits into from
Aug 1, 2023

Conversation

kishore03109
Copy link
Contributor

@kishore03109 kishore03109 commented Jul 26, 2023

Problem

Currently, when calling the the route /viewed twice, this might lead to errors thrown in the backend. While the logic is sane, the reason why the errors occur is because there is a race condition when two threads attempt to create at the same time.
Screenshot 2023-07-26 at 10 16 01 AM

Solution

Upsert rather than create. Note that the comments.spec.ts already needed these file changes to work.

Limitations

This not the ideal way to solve race conditions. The ideal method is to create a lock and then keep the function markAllReviewRequestsAsViewed atomic. However, at this point of time, that might be over-engineering here as the only impact that could happen with unlikely concurrent calls to markAllReviewRequestsAsViewed is the dashboard does not turn blue to mark it as unread.

Following Chin's suggestion of using transactions, this limitation is no longer valid.

Tests

e2e for notifications pass (failed without these changes)
Screenshot 2023-07-27 at 2 19 31 PM

Misc
There is another issue of our /viewed being called twice, which is why this bug was noticed in the e2e testing in the first place. While that might incur separate effort to investigate, this PR intends to quick fix this regardless as this can still occur say when a user has 2 tabs open and refreshes them simultaneously.

@kishore03109 kishore03109 marked this pull request as ready for review July 26, 2023 02:27
@kishore03109 kishore03109 requested a review from a team July 26, 2023 02:28
@@ -512,7 +512,7 @@ export default class ReviewRequestService {
// Using map here to allow creations to be done concurrently
// But we do not actually need the result of the view creation
requestIdsToMarkAsViewed.map(async (requestId) =>
this.reviewRequestView.create({
this.reviewRequestView.upsert({
Copy link
Contributor

Choose a reason for hiding this comment

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

hmm, maybe the correct thing to do would be to use a transaction here. wdyt?

Copy link
Contributor Author

@kishore03109 kishore03109 Jul 27, 2023

Choose a reason for hiding this comment

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

@kishore03109 kishore03109 requested a review from seaerchin July 27, 2023 06:20
@kishore03109 kishore03109 marked this pull request as draft July 27, 2023 06:39
@kishore03109 kishore03109 marked this pull request as ready for review July 27, 2023 08:35
@kishore03109 kishore03109 force-pushed the fix/race-conditions-review-request-views branch from 342ff99 to 3658a17 Compare July 28, 2023 01:26
Copy link
Contributor

@alexanderleegs alexanderleegs left a comment

Choose a reason for hiding this comment

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

lgtm - let's still file an issue for the root cause of the issue though

})
)
)
await Promise.all(
Copy link
Contributor

Choose a reason for hiding this comment

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

just realised that this is cos of repeated attempts to create - in this case, the transaction might not help at all actually because we'd still try to create the repeated entry.

maybe we should just use _.unique(requestIdsToMarkAsViewed) to prevent dups and accept hte race condition of having some views being outdated (actually seen but presented as not), as it's relatively minor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in this case, the transaction might not help at all actually because we'd still try to create the repeated entry.

hmm i am confused here, why would it try to create a repeated entry ya? there is still a race condition here, but in this case we are "safely" failing here right due to a rollback? so rather than making our server return 500, we are increasing the stability of our be here no?

maybe we should just use _.unique(requestIdsToMarkAsViewed) to prevent dups and accept hte race condition of having some views being outdated (actually seen but presented as not), as it's relatively minor.

hmm wait ah this is due to duplicate api calls leh, how would this help here ah?

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 the error stems from an attempt to create when there's an existing entry + a unique constraint. this is not due to concurrency issues and you can replicate this just by having the requestIds to be identical and do the creation sequentially.

because the error is due to duplicates in requestIds, the fix is to either remove the duplicates or allow the update but have it do nth.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

wait we do have an initial call here right, in line 493?

 const requestsViewed = await this.reviewRequestView.findAll({
          where: {
            siteId: site.id,
            userId,
          },
          transaction,
        })

we get the list of requestsViews, diff it using the current open review requests, and iff there are active requests that have not been viewed yet, create a entry in the reviewRequestViews?

Copy link
Contributor

Choose a reason for hiding this comment

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

if you've tried that this solution works on local, then please merge it. i might have a misconception, so it shouldn't block the PR

Copy link
Contributor

Choose a reason for hiding this comment

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

should write a simple spec to prevent regression

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

actually because the transaction here is mocked, it mihgt not be very useful. we might wish to write an integration spec and verify that it fails initially (without the fix) and passes later on with the fix

Copy link
Contributor Author

Choose a reason for hiding this comment

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

^ ok, i tackle this in a separate pr cuz at least the changes should be merged in by tmr's release uh

@kishore03109 kishore03109 requested a review from seaerchin July 28, 2023 07:35
@kishore03109 kishore03109 merged commit aaebd9d into develop Aug 1, 2023
@mergify mergify bot deleted the fix/race-conditions-review-request-views branch August 1, 2023 06:58
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.

3 participants