Careful payments or race conditions in real life

Ivan Mylyanyk
3 min readJul 17, 2016

--

Hi! This is me, and I finally wrote the next post! Yeey :D

Today I am going to share my tiny experience in multi-threading programming, so… here we go ;)

DISCLAIMER: I might be wrong at some points. If you feel that I am wrong and you know how it should be correctly — please, comment or contact me directly — let’s spread the knowledge and make web safer place! My main purpose of writing this article — to raise awareness of the issue.

Task

We have a website with some booking system. And we have the following flow there: customer purchases some products, money is reserved on his account and he waits for the approval of purchase by the admin (approval is applied per product). Admin has the options to approve or decline the purchase. In a case of approval — the reserved money for that product is charged, and product is considered to be served. In a case of denial — money are released. Pretty simple, huh?

Key technologies: ASP.NET MVC 5, Entity Framework, C#.

General solution

Server sends the email to admin about a new purchase of every product with two links — to approve and to decline the purchased product. The link contains the GUID number (unique identifier of the order), a server performs the lookup in a database for the corresponding product. If the ordered product wasn’t approved or declined yet, the server marks it as a reviewed (approved or declined). Then interacts with payment system (release or charge money). That’s all.

Problem

What if a user performed a double click on that link? In this case, a browser will send two identical requests to the website, and, chances are that server will charge money twice (server will be processing all these requests simultaneously). Not cool!

Generally speaking, this issue is widely known as the race condition, where the software behavior depends on the sequence or timing of other uncontrollable events. It becomes a bug when events do not happen in the order the programmer intended. And that’s really a huge bug, which may lead to big problems. For example, the story of the guy who hacked Starbucks.

The first easy (but wrong) solution

Do nothing. Because IIS (Internet Information Services) cares about your safety. IIS doesn’t process simultaneous requests with the same session id.

However, the problem is that if I want to break someone's site — I will take care to make those two requests with different session ids.

The second solution (difficult, but correct)

It’s all about locks and cache. What if we manually disallow server to process requests with the same GUIDs?

We will need to store locks somewhere in the server’s memory. ASP.NET provides us with an access to the cache, where we can store the map, where the keys are the GUID of the every processing product from the order and values are the corresponding objects for locks.

So, in this case, our code, where we change the state of the order in the database, will be surrounded with the lock on the corresponding object from the cache.

The third solution (my favorite)

We cannot simply update the entry in the database, since we firstly have to retrieve the object from the database, and, secondly, you have to update the object. It assumes two calls to database, and in the meantime, the retrieved object may be changed in the database from the other parallel request (and we will not notice that).

And here the Extended EntityFramework appears on the stage. It gives us an opportunity to change the status of the “order” in one request with the Batch Update. That method returns the quantity of the changed entries, so, we will know if our order is processed in the other thread (nothing was updated).

Final thoughts

I hope I have raised the awareness about the race conditions and demonstrated you a few possible solutions to this issue. I believe you will be careful the next time you develop your application, and the smart guy with a laptop will not secretly take your money ;)

Also, if you noticed the holes in my approach — please, feel free to comment!

--

--