Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django session race condition?

Summary: is there a race condition in Django sessions, and how do I prevent it?

I have an interesting problem with Django sessions which I think involves a race condition due to simultaneous requests by the same user.

It has occured in a script for uploading several files at the same time, being tested on localhost. I think this makes simultaneous requests from the same user quite likely (low response times due to localhost, long requests due to file uploads). It's still possible for normal requests outside localhost though, just less likely.

I am sending several (file post) requests that I think do this:

  1. Django automatically retrieves the user's session*
  2. Unrelated code that takes some time
  3. Get request.session['files'] (a dictionary)
  4. Append data about the current file to the dictionary
  5. Store the dictionary in request.session['files'] again
  6. Check that it has indeed been stored
  7. More unrelated code that takes time
  8. Django automatically stores the user's session

Here the check at 6. will indicate that the information has indeed been stored in the session. However, future requests indicate that sometimes it has, sometimes it has not.

What I think is happening is that two of these requests (A and B) happen simultaneously. Request A retrieves request.session['files'] first, then B does the same, changes it and stores it. When A finally finishes, it overwrites the session changes by B.

Two questions:

  1. Is this indeed what is happening? Is the django development server multithreaded? On Google I'm finding pages about making it multithreaded, suggesting that by default it is not? Otherwise, what could be the problem?
  2. If this race condition is the problem, what would be the best way to solve it? It's an inconvenience but not a security concern, so I'd already be happy if the chance can be decreased significantly.

Retrieving the session data right before the changes and saving it right after should decrease the chance significantly I think. However I have not found a way to do this for the request.session, only working around it using django.contrib.sessions.backends.db.SessionStore. However I figure that if I change it that way, Django will just overwrite it with request.session at the end of the request.

So I need a request.session.reload() and request.session.commit(), basically.

like image 357
Mark Avatar asked Dec 06 '12 16:12

Mark


3 Answers

  1. Yes, it is possible for a request to start before another has finished. You can check this by printing something at the start and end of a view and launch a bunch of request at the same time.

  2. Indeed the session is loaded before the view and saved after the view. You can reload the session using request.session = engine.SessionStore(session_key) and save it using request.session.save().

Reloading the session however does discard any data added to the session before that (in the view or before it). Saving before reloading would destroy the point of loading late. A better way would be to save the files to the database as a new model.

The essence of the answer is in the discussion of Thomas' answer, which was incomplete so I've posted the complete answer.

like image 193
Mark Avatar answered Sep 17 '22 15:09

Mark


Mark just nailed it, only minor addition from me, is how to load that session:

for key in session.keys():  # if you have potential removals
    del session[key]
session.update(session.load())
session.modified = False  # just making it clean

First line optional, you only need it if certain values might be removed meanwhile from the session.

Last line is optional, if you update the session, then it does not really matter.

like image 35
Vajk Hermecz Avatar answered Sep 18 '22 15:09

Vajk Hermecz


That is true. You can confirm it by having a look at the django.contrib.sessions.middleware.SessionMiddleware.

Basically, request.session is loaded before request hits your view (in process_request), and it is updated in the session backend (if needed) after the response has left your view (in process_response).

If what I mean is unclear, you might want to have a look at the django documentation for Middleware.


The best way to solve the issue will depend on what you're trying to achieve with that information. I'll update my answer if you provide that information!

like image 31
Thomas Orozco Avatar answered Sep 19 '22 15:09

Thomas Orozco