The Problem

To demonstrate common concurrency issues we are going to work on a bank account model:

class Account(models.Model): id = models.AutoField(

primary_key=True,

)

user = models.ForeignKey(

User,

)

balance = models.IntegerField(

default=0,

)

To get started we are going to implement a naive deposit and withdraw methods for an account instance:

def deposit(self, amount):

self.balance += amount

self.save() def withdraw(self, amount):

if amount > self.balance:

raise errors.InsufficientFunds() self.balance -= amount

self.save()

This seems innocent enough and it might even pass unit tests and integration tests on localhost. But, what happens when two users perform actions on the same account at the same time?

User A fetches the account — balance is 100$. User B fetches the account — balance is 100$. User B withdraws 30$ — balance is updated to 100$ — 30$ = 70$. User A deposits 50$ — balance is updated to 100$ + 50$ = 150$.

What happened here?

User B asked to withdraw 30$ and user A deposited 50$ — we expect the balance to be 120$, but we ended up with 150$.

Why did it happen?

At step 4, when user A updated the balance, the amount he had stored in memory was stale (user B had already withdrawn 30$).

To prevent this situation from happening we need to make sure the resource we are working on is not altered while we are working on it.