在自定义中间件中未捕获的异常后,类重新加载停止

时间:2009-08-27 09:12:30

标签: ruby-on-rails middleware

我编写了自己的中间件来为我们的应用程序提供API端点。中间件加载提供API方法的类,并将请求路由到适当的类/方法。这些类通过String#constantize动态加载。

在开发模式下运行时,会自动重新加载类。但是,如果存在未捕获的异常 - 随后由故障安全中间件处理 - 自动重新加载将停止工作。 constantize仍然被调用,但它似乎返回了旧班。

看起来还有其他东西可以卸载类,而未捕获的异常会破坏它。这可能是什么?

运行Ruby 1.8.7,Rails 2.3.3和Thin 1.2.2。

2 个答案:

答案 0 :(得分:1)

我认为这种效果来自ActionController::Reloader的编写方式。这是2.3.3中的ActionController::Reloader#call,请注意评论:

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不会删除自动加载的常量Dispatcher.cleanup_application。编写BodyWrapper#close时可能会出现例外情况:

def close
  @body.close if @body.respond_to?(:close)
ensure
  Dispatcher.cleanup_application
end

但是这没有用,因为如果@app.call中的ActionController::Reloader#call引发异常,BodyWrapper不会被实例化,Dispatcher.cleanup_application也不会被调用。

想象一下以下场景:

  • 我在其中一个影响API调用的文件中进行了更改
  • 我点击API调用并看到错误,此时所有文件包括带有错误的文件都没有卸载
  • 我制作了一个codefix并点击相同的 API调用以检查它是否有效
  • 调用以与以前相同的方式路由到旧类/对象/模块。这会引发相同的错误并再次将加载的常量留在内存中

传统控制器出现错误时不会发生这种情况,因为这些错误由ActionController::Rescue处理。此类异常未命中ActionController::Reloader

最简单的解决方案是将fallback rescue子句放入API路由中间件,其中有一些变体:

def call(env)
  # route API call
resuce Exception
  Dispatcher.cleanup_application
  raise
end

请注意,这是我对3岁问题的回答,我跟随了2.3.3的调用堆栈。较新版本的rails可能会以不同的方式处理事情。

答案 1 :(得分:0)

Rails缓存了许多类,并在开发模式下或在config.cache_classes设置为true时卸载并重新加载它们。以下是关于该主题的一些想法,也解释了它的工作原理。 http://www.spacevatican.org/2008/9/28/required-or-not/

不要告诉你,你做错了,但重载String#constantize似乎是一种重新加载你的代码的hacky方式。您是否考虑使用watchr之类的东西在开发中运行您的应用服务器,并在您的API子树中保存文件时重新启动它? https://github.com/mynyml/watchr/

此外,有关如何进一步调试的随机提示,请查看以下答案:https://stackoverflow.com/a/7907289/632022