Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'm a novice but would like to know how these issues can arise. What kind of backend setup is needed for it to be a problem? What is happening when a race condition occurs in these examples?


Its actually quite simple as example for the promo code the code looks like this:

1. Code sent.

2. Check if valid.

3. Redeem code.

4. Invalid code.

Now if i send 10 requests at the same time with the same code maybe 4-6 will hit the code part after 2.

And your window of opportunity is the time it takes to go from 3 to 4. Sometimes certain tasks are put inside async queue, you have a slight delay to your database server or you need to wait for db replication to kick in.

Because normally there is no code part to recheck how often this code was used.


Can this issue be prevented if we use the promo code as the table primary key or document ID?


That won't be enough because the promo codes are shared amongst many users. If the promo code became the primary key, then only one user would be able to redeem it.

If you introduced some combination of a user ID and promo code, then it won't prevent a race of one user firing many queries with different promo codes and stacking them up. It would, however, fix the original problem.


A simple Discount domain model with validations:

  Class Discount
    belongs_to :promo_code
    belongs_to :customer
    belongs_to :order

    validates_presence_of :promo_code, :customer, :order
    validates_associated :promo_code
    validates_uniqueness_of :promo_code_id, :scope => [:customer_id, :order_id]
  end
Limiting down to a single Promo-code per order:

  Class Discount
    # ...
    validates_uniqueness_of :order_id, :scope => :customer_id
  end


This right here is the heart of race condition bugs, and is NOT race condition safe. When running multiple web servers and without a "validates_uniqueness_of" constraint on your database, multiple requests hitting multiple different web servers can claim multiple discounts for the same user. Problem only grows as your number of web servers grow!


Read the part "Concurrency and integrity" in the Rails documentation: http://apidock.com/rails/ActiveRecord/Validations/ClassMetho...

You need to enforce the uniqueness in the DB.


Therefore the only thing left to do is run the following migration:

  add_index :discounts, [:promo_code_id, :customer_id, :order_id], :unique => true


>I'm a novice but would like to know how these issues can arise.

The problem is concurrency. Whenever you have multiple things happening at once, you have concurrency and programming concurrent system is always really hard.

Unfortunately the software industry has never really got a grip on this problem and there are lots of developers who have never really studied multi-threading at all. That's a problem, because it's something that takes a lot of practice and you have to just incorporate it into the way you think. After a while you do get a sixth sense for race conditions, but you'll still write racy code from time to time anyway. It's just tricky to get it right 100% of the time.

spdy has already outlined what is happening here, but this problem is something that is covered in literally any introductory course to database systems or multi-threaded programming. If you have two threads (or processes) in flight simultaneously that are reading and writing to a shared data store, then you need some kind of mutual exclusion. That can mean a lock:

1. Request for /reviews/add is received.

2. Database lock on the page reviews table is acquired.

3. Check if the user has already posted a review. If so, release the lock and abort (any good framework will release locks for you automatically if you throw an exception).

4. Add review to table.

5. Release lock.

At the point where the lock is acquired if another web server is in the middle of the operation, conceptually speaking the first one will stop and wait for the table to become available.

Real implementations don't actually "stop and wait" - that would be too slow. They use database transactions instead where both web server processes/threads proceed optimistically, and at the end the database will undo one of the changes if they detect that there was a conflict .... but you can imagine it as being like stop and wait.

Of course once you have concurrency, you have all the joy that comes with it like various kinds of deadlock.

It's funny, a few days ago I was letting my mind wonder and ended up thinking about web apps for some reason. Oh yes, now I remember, I was thinking about concurrency strategies in a software library I maintain and how to explain it to people. And then I was thinking how hard multi-threading is and how many people are selling snake-oil silver bullets to it, and started to wonder how many web apps had race conditions in them. And then I just discarded the thought as one of no consequence and got on with my day, haha :) Perhaps I should have tried to earn a bit of money this way instead.


This is really helpful. Thanks for sharing!


The article links to an article that explains it really well:

https://defuse.ca/race-conditions-in-web-applications.htm




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: