【问题标题】:Class reloading stops after uncaught exception in custom middleware自定义中间件中未捕获的异常后类重新加载停止
【发布时间】:2009-08-27 09:12:29
【问题描述】:

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

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

似乎还有其他东西会卸载类,并且未捕获的异常会破坏它。这可能是什么?

运行 Ruby 1.8.7、Rails 2.3.3 和 Thin 1.2.2。

【问题讨论】:

    标签: ruby-on-rails middleware


    【解决方案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
    

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

    想象以下场景:

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

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

    最简单的解决方案是将备用救援子句放入 API 路由中间件,这是一些变体:

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

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

    【讨论】:

      【解决方案2】:

      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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-24
        • 1970-01-01
        • 2013-06-19
        • 1970-01-01
        • 2019-06-30
        • 2019-01-24
        • 1970-01-01
        相关资源
        最近更新 更多