hexinpeter
hexinpeter

Reputation: 1570

Rails balance withdraw action threading issue

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? Balance Withdrawal Threading Issue

Upvotes: 0

Views: 220

Answers (1)

mtkcs
mtkcs

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

Related Questions