There are different reasons why a ActiveRecord::StaleObjectError
would occur. Sometimes it is caused because rails does not have a stable identity map yet. This means that if the same object would be retrieved via different associations, your rails process would not know they point to the same object, would keep different copies of the same object and that could easily cause a StaleObjectError
if you attempt to change both.
Such an occurrence needs to be fixed structurally in your code. But sometimes you can get StaleObjectError
because things happen at the same time. There are two ways to handle this:
- make sure that controller actions which affect the same object, are always executed sequentially. One way to achieve this is to lock the object you want to update in the database. Then all other processes wanting to update the same object will have to wait until the lock is released (and the lock can be acquired). This is a valid approach but costly, intrusive (you need to explicitly add code), and possibly dangerous. It is normally easy to avoid, but you have to be careful for deadlocks and locks that are never released.
- when a
StaleObjectError
occurs, just retry the request
Now that second option seems valid, if only it would be easy to handle. Luckily there is a very easy and unobtrusive way to automatically retry all requests when a StaleObjectError
occurs. Let's just create a middleware that catches the exception and retries the complete request a few times.
Create a new file lib/middleware/handle_stale_object_error.rb
containing the following:
module Middleware
class HandleStaleObjectError
RETRY_LIMIT = 3
def initialize(app)
@app = app
end
def call(env)
retries = 0
begin
@app.call(env)
rescue ActiveRecord::StaleObjectError
raise if retries >= RETRY_LIMIT
retries += 1
Rails.logger.warn("HandleStaleObjectError::automatically retrying after StaleObjectError (attempt = #{retries})")
retry
end
end
end
end
Then in your config/application.rb
you need to add the following lines at the bottom to activate the middleware (this requires a restart of your rails server):
#
# Middleware configuration
#
require 'middleware/handle_stale_object_error'
config.middleware.insert_after ActiveRecord::SessionStore, Middleware::HandleStaleObjectError
Warning : this code will retry all occurences of StaleObjectError
. For some occurrences this will not help at all, so you have to use your own judgement if this middleware is something you want in your codebase. I like this approach because it is an "optimistic" approach: it only adds extra processing when a StaleObjectError
occurs, and when a not-fixable StaleObjectError
it still fails as it should.
Hope this helps.