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
5300 Avoid race conditions in Spree::StockItem #5406
5300 Avoid race conditions in Spree::StockItem #5406
Conversation
b958358
to
37b8d48
Compare
9d2a8e1
to
089dab4
Compare
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.
Nice one @kristinalim
From what I read, with lock_version there will be some ActiveRecord::StaleObjectError exceptions be thrown. Do you know where and how will this affect the user journey?
Will this be a generic checkout error shown to the user?
I wonder if this can be tested in a concurrency spec like the checkout_controller_concurrency_spec.
db/migrate/20200514174526_reset_negative_nonbackorderable_count_on_hand_in_stock_items.rb
Show resolved
Hide resolved
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.
There are some good changes in here but I'm not sure I understood where the root cause is. Are you assuming that an admin is updating the stock level while somebody checks out?
I also don't see any code dealing with the stale object errors. Is the plan to observe them in Bugsnag first to know when they actually happen?
@@ -10,6 +10,7 @@ class StockItem < ActiveRecord::Base | |||
# rubocop:disable Rails/UniqueValidationWithoutIndex | |||
validates :variant_id, uniqueness: { scope: :stock_location_id } | |||
# rubocop:enable Rails/UniqueValidationWithoutIndex | |||
validates :count_on_hand, numericality: { greater_than_or_equal_to: 0, unless: :backorderable? } |
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 will make the checkout fail. Did you test what the user would see in this scenario?
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.
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.
as far as I can see, this should never happen, if it happens and if it blows up we will see it in bugsnag.
@kristinalim please move back to Code Review when you think this is ready to be re-reviewed. Thanks! |
c03a678
to
e4b346d
Compare
I am having a look at this. |
b855fa2
to
e4e037f
Compare
And with master? Can you actually re-produce the error? I still don't know why the synchronisation doesn't work in this case. |
I was trying to extend https://github.com/openfoodfoundation/openfoodnetwork/blob/9e730e6445231c16396de2da7c33bff26c0aed74/spec/controllers/checkout_controller_concurrency_spec.rb With a spec that would test for different orders with the same stock limited variant. I realize that the existing spec still passes without this: And the second thread doesnt even get to the load_order call back. Which is exactly the same problem in my attempt here. I have spent too much time on this already without success. So I am leaving it. |
…em#stock_location
We are not using the return value of this method anywhere.
…r zero Fix setting of count on hand in line item specs
rebased to resolve conflicts. |
e4e037f
to
ba50491
Compare
This looks pretty good but I noticed this while reading https://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html:
We have two workers on our infra so this will be needed. It could come on a separate PR but the issue won't be fully fixed until we address this. |
yeah, this PR is not fixing 5300, just improving the situation. @sauloperez can you clarify what exactly is preventing you from approving this PR? Is it the question to @mkllnk ? |
I didn't notice this isn't closing #5300 . If this is about introducing optimistic locking while bringing in the necessary models, this is Test ready IMO. |
Hi @kristinalim , I tried to reproduce the issue as reported in #5300, but without success. I thank you for the detailed test cases! I went through them:
Session A: I've set the stock level of a product to the limited stock of 4. Session B: Reduced quantity in cart to 2; While in the /cart page, pressing Update triggered the warning in the red tab appear again, although the quantity in car was already compatible with the available stock -> a "memory-effect" was observed while testing another PR (#5766). I will investigate this issue - system retaining previous state and showing previously shown warnings - and report later. <- After pressing Update a second time and making the warning disappear redirection to the checkout page followed.
Session A: I've set the stock level of a product to limited stock, in this case, 4 items. and triggers Bugnsnag:
Scenario 2 doesn't seem to be working as expected. Perhaps this PR could be improved? I'm moving it into In Dev for now. The other option would be to merge and open an issue on this concern. Please let me know if you find this beneficial in this case. |
@filipefurtad0 I don't think Kristina will be able to pick this up as she has no time to contribute at the moment. As it looks to me that it is already improving the situation, without making what we have in prod worse, I would merge and open a dedicated issue. What do you think? |
It's great you can replicate the problem Filipe! I thought we would not be able to replicate it. I agree with the approach but I think 5784 is a sub-issue of 5300 and thus still an S2 to be worked on. Does that make sense? |
Cheers to @kristinalim and the wonderful testing notes! 🎉 Thanks @luisramos0 ! |
What? Why?
I've looked into #5300, exploring if the negative inventory levels could be related to a missing callback for guest checkout, subscriptions, and the now removed backorders, but this doesn't seem to be the case.
The issue seems to be that stock levels in
Spree::StockItem
, which we check for stock levels before completing an order, could be updated by another order before the stock level is updated by the current order.This PR introduces two changes:
Spree::StockItem
vialock_version
.count_on_hand
inSpree::StockItem
to be positive if not backorderable.What should we test?
Scenario 1: Stock restrictions for customer checking out should still work. Open two browser sessions, one logged in as admin (Session A), another logged in as customer (Session B).
Scenario 2: Stock restrictions for admin setting up an order should still work. Open two browser sessions, both logged in as admin.
Release notes
Address race condition resulting in orders exceeding available stock.
Changelog Category: Fixed