Перезагрузка класса останавливается после неперехваченного исключения в пользовательском промежуточном программном обеспечении

Я написал свое собственное промежуточное программное обеспечение, чтобы предоставить конечную точку API нашему приложению. Среднее ПО загружает классы, которые предоставляют методы API, и направляет запрос соответствующему классу/методу. Классы загружаются динамически через String#constantize.

При запуске в режиме разработки классы автоматически перезагружаются. Однако, если есть неперехваченное исключение, которое впоследствии обрабатывается промежуточным программным обеспечением Failsafe, автоматическая перезагрузка перестает работать. constantize все еще вызывается, но, похоже, возвращает старый класс.

Казалось бы, есть что-то еще, что выгружает классы, а неперехваченное исключение нарушает его. Что это может быть?

Запуск Ruby 1.8.7, Rails 2.3.3 и Thin 1.2.2.

Ответ 1

Я думаю, что этот эффект исходит из того, как написано 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

Однако это не помогает, потому что если @app.call в ActionController::Reloader#call выдает исключение, BodyWrapper не получает экземпляр, а Dispatcher.cleanup_application не вызывается.

Представьте себе следующий сценарий:

  • Я вношу изменения в один из моих файлов, который влияет на вызов API
  • Я нахожу вызов API и вижу ошибку, в этот момент все файлы, в том числе и с ошибкой, не выгружаются
  • Я делаю кодfix и нажимаю тот же API-код, чтобы проверить, работает ли он
  • вызов маршрутизируется так же, как и раньше, в старые классы/объекты/модули. Это вызывает такую ​​же ошибку и снова оставляет загруженные константы в памяти

Этого не происходит, когда традиционные контроллеры вызывают ошибки, потому что они обрабатываются ActionController::Rescue. Такие исключения не попадают ActionController::Reloader.

Простейшим решением было бы поставить резервное предложение спасения в промежуточное ПО маршрутизации API, некоторые изменения этого:

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

Обратите внимание, что это мой ответ на 3-летний вопрос, и я выполнил стек вызовов 2.3.3. Более новые версии рельсов могут обрабатывать разные вещи.

Ответ 2

Rails кэширует множество классов и выгружает их и перезагружает их в режиме разработки или когда для параметра config.cache_classes установлено значение true. Вот некоторые мысли по теме, которые также объясняют, как это работает. http://www.spacevatican.org/2008/9/28/required-or-not/

Не сказать вам, что вы делаете это неправильно, но перегрузка String # constantize кажется халатным способом перезагрузить ваш код. Считаете ли вы использовать что-то вроде watchr для запуска своего сервера приложений в процессе разработки и перезапустить его при сохранении файлов в поддереве API? https://github.com/mynyml/watchr/

Кроме того, для некоторых случайных идей о том, как продолжить отладку, ознакомьтесь с этим ответом: fooobar.com/info/509323/...