Rails Session not being persisted
I ran into an issue today around the session object in Rails. The session object works pretty much like a hash and allows you to store data between requests. A number of persistent stores exist, but overall it acts like a hash that is persisted between requests.
In the end in turned out to be a race condition in the Rails session management code, so it’s very difficult to debug (although I did manage to reproduce it with perfect consistency). It turns out that every action in a Rails controller does the following:
- Load the session (or create a new one if necessary)
- Execute the action
- Persist the session
This all seems pretty straightforward, except that it’s important to note that Rails will persist the session even if you didn’t make any changes to the session object. If you’re working in an AJAX-heavy application this can lead to the following scenario:
- Request A is serviced and loads the session
- Request B is serviced and loads the session
- Request A makes changes to the session
- Request A completes and persists the session
- Request B completes and persists the original session, destroying the changes Request A made
Although this doesn’t seem like a very likely scenario, I was able to reproduce it with perfect consistency and it was also showing up in one of our deployed environments.
The Solution?
As far as I know there is no easy solution to this problem (as with most race conditions). Ideally I would prefer it if Rails didn’t persist the session unless we actually make a change to it. For the moment we managed to solve this problem by not making multiple AJAX calls.
An alternative solution is to simply not use the session for any kind of storage and simply store data in the database. With the kind of data we were storing it made perfect sense to store it in the session, but storing it in the database will obviously fix the problem.
In theory using a JavaScript library like SpineJS should also solve this problem since Spine is supposed to queue all AJAX calls (although I haven’t actually tested this).
The best solution would probably be to delve into the Rails code and try and change the session management code to not persist the session unless it is modified by the action. I had a quick look at the session management code, but I haven’t spent the proper time to see if this can be done.
Happy coding.