Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Race conditions in django

Here is a simple example of a django view with a potential race condition:

# myapp/views.py from django.contrib.auth.models import User from my_libs import calculate_points  def add_points(request):     user = request.user     user.points += calculate_points(user)     user.save() 

The race condition should be fairly obvious: A user can make this request twice, and the application could potentially execute user = request.user simultaneously, causing one of the requests to override the other.

Suppose the function calculate_points is relatively complicated, and makes calculations based on all kinds of weird stuff that cannot be placed in a single update and would be difficult to put in a stored procedure.

So here is my question: What kind of locking mechanisms are available to django, to deal with situations similar to this?

like image 979
Fragsworth Avatar asked Jun 23 '09 01:06

Fragsworth


People also ask

What is race condition in Django?

Database race condition with tasks and Django request handlers. Data races happen when two or more concurrent threads try to access the same memory address (or in this case, some specific data in a database) at the same time.

What is race condition?

A race condition is an undesirable situation that occurs when a device or system attempts to perform two or more operations at the same time, but because of the nature of the device or system, the operations must be done in the proper sequence to be done correctly.

What are race conditions in database?

A race condition occurs when two threads access a shared variable at the same time. The first thread reads the variable, and the second thread reads the same value from the variable.

Is Django Get_or_create Atomic?

NO, get_or_create is not atomic. It first asks the DB if a satisfying row exists; database returns, python checks results; if it doesn't exist, it creates it. In between the get and the create anything can happen - and a row corresponding to the get criteria be created by some other code.


2 Answers

Django 1.4+ supports select_for_update, in earlier versions you may execute raw SQL queries e.g. select ... for update which depending on underlying DB will lock the row from any updates, you can do whatever you want with that row until the end of transaction. e.g.

from django.db import transaction  @transaction.commit_manually() def add_points(request):     user = User.objects.select_for_update().get(id=request.user.id)     # you can go back at this point if something is not right      if user.points > 1000:         # too many points         return     user.points += calculate_points(user)     user.save()     transaction.commit() 
like image 122
Anurag Uniyal Avatar answered Sep 21 '22 15:09

Anurag Uniyal


As of Django 1.1 you can use the ORM's F() expressions to solve this specific problem.

from django.db.models import F  user = request.user user.points  = F('points') + calculate_points(user) user.save() 

For more details see the documentation:

https://docs.djangoproject.com/en/1.8/ref/models/instances/#updating-attributes-based-on-existing-fields

https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F

like image 24
bjunix Avatar answered Sep 22 '22 15:09

bjunix