我认为这种效果来自于ActionController::Reloader
书写方式。这是ActionController::Reloader#call
来自 2.3.3,请注意评论:
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
然而这并没有帮助,因为 if @app.call
inActionController::Reloader#call
抛出异常,BodyWrapper
不会被实例化,Dispatcher.cleanup_application
也不会被调用。
想象以下场景:
- 我对影响 API 调用的文件之一进行了更改
- 我点击 API 调用并看到错误,此时所有文件,包括有错误的文件都没有被卸载
- 我做了一个代码修复并点击相同的API 调用来检查它是否有效
- call 以与以前相同的方式路由到旧的类/对象/模块。这会引发相同的错误,并再次将加载的常量留在内存中
当传统控制器引发错误时不会发生这种情况,因为这些错误是由ActionController::Rescue
. 此类异常不命中ActionController::Reloader
。
最简单的解决方案是将后备救援子句放入 API 路由中间件,这是一些变体:
def call(env)
# route API call
resuce Exception
Dispatcher.cleanup_application
raise
end
请注意,这是我对 3 岁问题的回答,我遵循了 2.3.3 的调用堆栈。较新版本的 rails 可能会以不同的方式处理事情。