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:
StaleObjectError
occurs, just retry the requestNow 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.
Comments
Looks good! Never had that error, but it's interesting to know a possible solution. One thing though. It would be better to set RETRY_LIMIT = 3 as an instance variable so it's easier to customize. def initialize(app, retry_limit = 3) @app = app @retry_limit = retry_limit end and change the line on call method: raise if retries >= @retry_limit So you can customize it when you add it to the middleware stack: config.middleware.insert_after ActiveRecord::SessionStore, Middleware::HandleStaleObjectError, 3 It could even receive a hash to be more explicit.
Add comment