I've written my own middleware to provide an API endpoint to our application. The middleware loads classes that provide the API methods, and routes the request to the appropriate class/method. The classes are loaded dynamically through String#constantize
.
While running in development mode, the classes are automatically reloaded. However, if there is an uncaught exception – which is subsequently handled by the Failsafe middleware – the automatic reloading stops working. constantize
is still being called but it seems to return the old class.
It would appear there is something else that unloads classes, and an uncaught exception breaks it. What could this be?
Running Ruby 1.8.7, Rails 2.3.3 and Thin 1.2.2.
I think this effect comes from the way ActionController::Reloader
is written. Here's ActionController::Reloader#call
from 2.3.3, note the comment:
def call(env)
Dispatcher.reload_application
status, headers, body = @app.call(env)
# We do not want to call 'cleanup_application' in an ensure block
# because the returned Rack response body may lazily generate its data. This
# is for example the case if one calls
#
# render :text => lambda { ... code here which refers to application models ... }
#
# in an ActionController.
#
# Instead, we will want to cleanup the application code after the request is
# completely finished. So we wrap the body in a BodyWrapper class so that
# when the Rack handler calls #close during the end of the request, we get to
# run our cleanup code.
[status, headers, BodyWrapper.new(body)]
end
Dispatcher.reload_application
doesn't remove auto-loaded constants, Dispatcher.cleanup_application
does. BodyWrapper#close
is written with possible exceptions in mind:
def close
@body.close if @body.respond_to?(:close)
ensure
Dispatcher.cleanup_application
end
However this doesn't help, because if @app.call
in ActionController::Reloader#call
throws an exception, BodyWrapper
doesn't get instantiated, and Dispatcher.cleanup_application
doesn't get called.
Imagine the following scenario:
This doesn't happen when traditional controllers raise errors because those are handled by ActionController::Rescue
. Such exceptions do not hit ActionController::Reloader
.
Simplest solution would be to put fallback rescue clause into API routing middleware, some variation of this:
def call(env)
# route API call
resuce Exception
Dispatcher.cleanup_application
raise
end
Note that this is my answer to 3 year old question and I followed call stack of 2.3.3. Newer versions of rails may handle things differently.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With