# Concurrency issue

**URL:** <https://www.ruby-forum.com/t/concurrency-issue/104165>\
**Category:** Rails\
**Created:** [May 14, 2007, 10:32pm UTC](https://www.ruby-forum.com/t/concurrency-issue/104165 "2007-05-14T22:32:09Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [May 14, 2007, 10:32pm UTC](https://www.ruby-forum.com/t/concurrency-issue/104165/1 "2007-05-14T22:32:09Z")

</div>

Hi,

On my site, if a lot of people place orders at the same time, the  
available quantity left in stock for a product seems to get wrong  
every so often.

Here’s my execute\_purchase method, which is a before\_filter on the  
Order model:

```
def execute_purchase
  Product.transaction do
    product.lock!
    if product.quantity >= quantity # enough in stock
      if credit_card_needed?
        response = authorize_payment
        if response.success?
          product.update_attribute(:quantity, product.quantity -

```

self.quantity)  
else  
raise FulfillmentError, response.message  
end  
end  
else  
raise FulfillmentError, “not enough in stock”  
end  
end  
rescue FulfillmentError =\> e  
errors.add\_to\_base e.message  
return false  
end

So, I enter the transaction. Lock the product. I see if there’s  
enough in stock. If there’s enough in stock, then I authorize the  
payment – if that’s successful, then I decrement the available  
quantity. Since I’m in a transaction and I’ve locked the record,  
this method should be fine, right?

So, what happens is sometimes we sell more than we have. So, the  
product quantity isn’t always updated when an order happens.

---

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [May 14, 2007, 10:50pm UTC](https://www.ruby-forum.com/t/concurrency-issue/104165/2 "2007-05-14T22:50:11Z")

</div>

I’m just trying to guess so forgive me if I’m saying something stupid

Let’s think to two almost simultaneous request A and B

this is the timing

1. request A land on your page and load the product (quantity = 3) and  
A wants to by 1
2. request B land on your page and load the product (quantity = 3) and  
B wants to buy 1
3. request A execute the transaction setting quantity = 3 -1 =\> 2
4. request B execute the transaction setting quantity = 3 - 1 =\> 2

That’s how you end up with still 2 available even if you’ve sold 2  
items.

update\_attribute doesn’t run validations so in this case lock\_version  
doesn’t protect you.

Paolo

---

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [May 14, 2007, 10:54pm UTC](https://www.ruby-forum.com/t/concurrency-issue/104165/3 "2007-05-14T22:54:11Z")

</div>

Shouldn’t the  
product.lock!  
line give me a lock on that row? So, the other requests need to wait  
until the transaction is over with. Then the product should get  
reloaded with current data.

Right?

---

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [May 14, 2007, 11:23pm UTC](https://www.ruby-forum.com/t/concurrency-issue/104165/4 "2007-05-14T23:23:18Z")

</div>

Sorry for the mess in the previous message, cut and paste error.

Basically when you instantiate an AR object it loads the data at that  
moment and never updates until you call the .reload method.  
An AR object is not aware of what happens to its image in the database.

Paolo

---

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [May 15, 2007, 12:22am UTC](https://www.ruby-forum.com/t/concurrency-issue/104165/5 "2007-05-15T00:22:28Z")

</div>

I just noticed you decrease the quantity only when credit\_card\_needed?

Is that right?

Paolo

---

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [May 14, 2007, 11:07pm UTC](https://www.ruby-forum.com/t/concurrency-issue/104165/6 "2007-05-14T23:07:13Z")

</div>

Yes, you lock the row, but the point is at that point it

On 14/05/07, removed\_email\_address@domain.invalid [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> Shouldn’t the  
> product.lock!  
> line give me a lock on that row? So, the other requests need to wait  
> until the transaction is over with.

The above is correct, it waits, but what at this point product is has  
been already loaded and it contains quantity 3

> Then the product should get  
> reloaded with current data.

The above is incorrect

If you don’t reload explicitly the ActiveRecord object it doesn’t  
reload itself, it will keep forever a STALE image of the database.

Paolo

---

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [May 15, 2007, 12:24am UTC](https://www.ruby-forum.com/t/concurrency-issue/104165/7 "2007-05-15T00:24:28Z")

</div>

Hi,

What about doing preventive stock update:

Le 14 mai 07, à 22:31, removed\_email\_address@domain.invalid a écrit :

> Here’s my execute\_purchase method, which is a before\_filter on the  
> Order model:
> 
> ```
> def execute_purchase
> Product.transaction do
> if product.quantity >= quantity # enough in stock
> if credit_card_needed?
> 
> ```

UPDATE products SET quantity=quantity-#{product.quantity}

> ```
> response = authorize_payment
> if response.success?
> 
> ```

msg Success

> ```
> else
> 
> ```

UPDATE products SET quantity=quantity+#{product.quantity}

> ```
> raise FulfillmentError, response.message
> end
> 
> ```

else # what if credit card is not needed ??

> ```
> end
> else
> raise FulfillmentError, "not enough in stock"
> end
> end
> rescue FulfillmentError => e
> errors.add_to_base e.message
> return false
> end
> 
> ```

Imho no lock should be necessary in this case.  
You simply risk to answer that there’s no more stock whereas there is  
some 5 ms later.  
Better than the opposite.

## Jean-Christophe M.

> **[Édition et distribution de livres sur la musique, de partitions et de revues...](https://symetrie.com/fr)**
>
> Présentation des publications de Symétrie en musique, musicologie et médecine de la voix.

Better Nested Set for rails:  
[http://opensource.symetrie.com/trac/better\_nested\_set](http://opensource.symetrie.com/trac/better_nested_set)

---

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [September 25, 2007, 10:59pm UTC](https://www.ruby-forum.com/t/concurrency-issue/104165/8 "2007-09-25T22:59:57Z")

</div>

> **[ActiveRecord::Locking::Pessimistic](https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html#M000646)**
>
> Locking::Pessimistic provides support for row-level locking using SELECT … FOR UPDATE and other lock types.

Says that the record should be reloaded.

In fact, here’s the source code for lock:

71: def lock!(lock = true)  
72: reload(:lock =\> lock) unless new\_record?  
73: self  
74: end

Am I not understanding this correctly?

Joe

---

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [September 25, 2007, 11:03pm UTC](https://www.ruby-forum.com/t/concurrency-issue/104165/9 "2007-09-25T23:03:53Z")

</div>

On 14/05/07, removed\_email\_address@domain.invalid [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> 73: self  
> 74: end
> 
> Am I not understanding this correctly?
> 
> Joe

Sorry, you’re definitely right, it should reload the object so what  
I’ve said is not valid.

Paolo
