Home > Software design >  How to make a thread safe in ruby on rails?
How to make a thread safe in ruby on rails?

Time:04-01

i have a calculation, but my calculation is not thread-safe, it must be using increment or increment_counter, but I have no idea how to implement it. here's my code

def create
if @purchase.check_quantity(@product, purchase_params[:quantity].to_i)
  return render :new  
end

#calculation is not thread-safe
new_quantity = @product.quantity - purchase_params[:quantity].to_i

@purchase.assign_attributes(purchase_params)
if @purchase.save
   @product.update(:quantity => new_quantity)
   redirect_to product_url(@product)
else
  flash[:error] = @purchase.errors.full_messages.join(', ')
  render :new
end

end

CodePudding user response:

I'm not sure what about your code is "not thread-safe" but I don't believe your issue is thread safety.

If you think the @purchase or @product objects are changing just before that code gets hit, I'd recommend using .reload. If you believe your calculation needs to run asynchronously you need to consider using an async job framework like ActiveJob or Sidekiq.

CodePudding user response:

You already noticed that you might run into race conditions in which two requests to your application enter that method almost at the same time. Both read @product.quantity, both calculate new_quantity and then you end up with one of those requests writing the new value into the database, and the other, slightly slower request overrides that value.

There are two approaches to solve this issue.

  1. Lock the record for an update. Another process trying to update the same record at the same time will then raise an error. Ruby on Rails supports Locking::Optimistic and Locking::Pessimistic. It depends a bit on your specific use-case what might be the better approach.

  2. An alternative might be to do the update in SQL that atomically updates records. Rails has the method update_counters that would allow you to calculate and update the record in the database like this:

    Product.update_counters(@product.id, quantity: -purchase_params[:quantity].to_i)
    

    But this way might (depending on what your check_quantity internally does) might still cause race condition issues because the check in that method will not be aware of any other requests passing the check immediately before and updating the database immediately after.

Both methods might help to solve the issue. But which method to choose depends on your specific use case and the level of security, user experience, and data integrity you need.

  • Related