Reputation: 1570
To allow users create balance withdrawal request, I have a WithdrawalsController#create
action. The code checks if the user has sufficient balance before proceeding to creating the withdrawal.
def create
if amount > current_user.available_balance
error! :bad_request, :metadata => {code: "002", description: "insufficient balance"}
return
end
withdrawal = current_user.withdrawals.create(amount: amount, billing_info: current_user.billing_info)
exposes withdrawal
end
This can pose a serious issue in a multi-threaded server. When two create requests arrive simultaneously, and both requests pass the the balance check before creating the withdrawal, then both withdrawal can be created even if the sum of two could be exceeding the original balance.
Having a class variable of Mutex
will not be a good solution because that will lock this action for all users, where a lock on a per-user level is desired.
What is the best solution for this?
The following diagram illustrates my suspected threading issue, could it be occurring in Rails?
Upvotes: 0
Views: 220
Reputation: 1716
As far as I can tell your code is safe here, mutlithreading is not a much of a problem. Even with more app instances generate by your app server, each instance will end up testing amount > current_user.available_balance
.
If you are really paranoiac about it. you could wrap the all with a transacaction
:
ActiveRecord::Base.transaction do
withdrawal = current_user.withdrawals.create!(
amount: amount,
billing_info: current_user.billing_info
)
# if after saving the `available_balance` goes under 0
# the hole transaction will be rolled back and any
# change happened to the database will be undone.
raise ActiveRecord::Rollback if current_user.available_balance < 0
end
Upvotes: 1