Rails: слишком глубокая ошибка уровня стека при вызове метода первичного ключа id

Это репост на еще одна проблема, на этот раз лучше изолированная. В моем файле environment.rb я изменил эту строку:

config.time_zone = 'UTC'

к этой строке:

config.active_record.default_timezone = :utc

С тех пор этот звонок:

Category.find(1).subcategories.map(&:id)

Сбой из-за ошибки «Слишком глубокий уровень стека» после второго запуска в среде разработки, когда config.cache_classes = false. Если config.cache_classes = true, проблема не возникает. Ошибка является результатом следующего кода в файле active_record/attribute_methods.rb в строке 252:

def method_missing(method_id, *args, &block)
...

    if self.class.primary_key.to_s == method_name
        id
    ....

Вызов функции «id» повторно вызывает method_missing, и ничто не мешает вызывать идентификатор снова и снова, что приводит к слишком глубокому уровню стека.

Я использую Rails 2.3.8. Модель категории has_many :subcategories. Вызов не выполняется для вариантов этой строки выше (например, Category.first.subcategory_ids, использование «каждого» вместо «карты» и т. д.).

Любые мысли будут высоко оценены.

Спасибо! Амит


person AmitA    schedule 09.11.2010    source источник
comment
Какую версию рубина вы используете?   -  person Shadwell    schedule 09.11.2010


Ответы (2)


Несмотря на то, что это решено, я просто хотел сообщить об этом и сообщить, как я исправил эту проблему. У меня были те же симптомы, что и у OP, первоначальный запрос .id() работал нормально, последующие запросы .id() выдавали сообщение об ошибке «слишком глубокий стек». Это странная ошибка, поскольку обычно она означает, что у вас где-то есть бесконечный цикл. Я исправил это, изменив:

config.action_controller.perform_caching = true
config.cache_classes                     = false

to

config.action_controller.perform_caching = true
config.cache_classes                     = true

в средах/production.rb.

ОБНОВЛЕНИЕ: основной причиной этой проблемы оказался файл cache_store. MemoryStore по умолчанию не сохраняет модели ActiveRecord. Это довольно старая ошибка, и довольно серьезная, я не уверен, почему она не была исправлена. В любом случае, обходным путем является использование другого cache_store. Попробуйте использовать это в вашем config/environments/development.rb:

config.cache_store = :file_store

ОБНОВЛЕНИЕ № 2: К. Бедард опубликовал этот анализ проблемы. Кажется, это красиво.

Сам столкнувшись с этой проблемой (и неоднократно застряв на ней), я исследовал ошибку (и, надеюсь, нашел хорошее решение). Вот что я знаю об этом: Это происходит, когда ActiveRecord::Base#reset_subclasses вызывается диспетчером между запросами (только в режиме разработки).

ActiveRecord::Base#reset_subclasses стирает хэш inheritable_attributes (где хранится #skip_time_zone_conversion_for_attributes). Это произойдет не только с объектами, сохраняемыми через запросы, как показывает «приложение для тестирования обезьян» из № 1290, но и при попытке доступа к сгенерированным методам ассоциации в AR, даже для объектов, которые живут только по текущему запросу.

Эта ошибка была вызвана этой фиксацией, где объявление #skip_time_zone_conversion_for_attributes было изменено с base.cattr_accessor в base.class_inheritable_accessor. Но опять же, тот же самый коммит исправил еще кое-что. Первоначально представленный здесь патч, который просто избегает очистки instance_variables и instance_methods в reset_subclasses, действительно приводит к массивным утечкам, и суммы утечек кажутся прямо пропорциональными сложности приложения (то есть количеству моделей, ассоциаций и атрибутов в каждой из них). У меня есть довольно сложное приложение, которое пропускает почти 1 МБ при каждом запросе в режиме разработки, когда применяется патч. Так что это нежизнеспособно (для меня во всяком случае).

Пытаясь решить эту проблему разными способами, я исправил первоначальную ошибку (skip_time_zone_conversion_for_attributes был равен нулю во втором запросе), но обнаружил еще одну ошибку (которой просто не произошло, потому что первое исключение было бы возбуждено до того, как добраться до него). Похоже, что об этой ошибке сообщается в # 774 (переполнение стека в method_missing для метода 'id').

Теперь, что касается решения, мой патч (прилагается) делает следующее: он добавляет методы-оболочки для методов #skip_time_zone_conversion_for_attributes, гарантируя, что он всегда читает/записывает значение как class_inheritable_attribute. Таким образом, ноль больше никогда не возвращается.

Это гарантирует, что метод id не будет стерт при вызове reset_subclasses. AR немного странный в этом, потому что он сначала определяет его непосредственно в исходном коде, но переопределяет себя с помощью #define_read_method при первом вызове. И именно это приводит к сбою после перезагрузки (поскольку reset_subclasses затем стирает его).

Я также добавил тест в reload_models_test.rb, который вызывает reset_subclasses, чтобы попытаться имитировать перезагрузку между запросами в режиме разработки. На данный момент я не могу сказать, действительно ли он запускает механизм перезагрузки, как это происходит в цикле запросов диспетчера в реальном времени. Я также проверил со скрипта/сервера, и ошибка исчезла.

Извините за длинную вставку, это отстой, что проект маяка рельсов является частным. Патч, упомянутый выше, является частным.

person Cory    schedule 26.05.2011
comment
Это личное, то есть вы не можете получить к нему доступ? Или нельзя поделиться? - person Matt Huggins; 13.03.2012
comment
Личное значение, когда я писал этот пост, администраторы закрыли проект маяка и мигрировали на Github. Весь старый контент был скрыт. Как ни странно, сейчас он открыт, и билет можно найти здесь - person Cory; 13.03.2012

-- Этот ответ скопирован из моего исходного сообщения здесь.

Наконец-то решено! После публикации третьего вопроса и с помощью trptcolin, я могу подтвердить рабочее решение.

Проблема: я использовал require для включения моделей из моделей без таблиц (классы, которые находятся в приложении/моделях, но не расширяют ActiveRecord::Base). Например, у меня был класс FilterCategory, который выполнял require 'category'. Это испортило кэширование классов Rails. Мне пришлось использовать require в первую очередь, так как такие строки, как Category.find :all, не сработали.

Решение (кредит принадлежит trptcolin): замените Category.find :all на ::Category.find :all. Это работает без необходимости явно запрашивать какую-либо модель и, следовательно, не вызывает проблем с кэшированием классов.

Проблема «слишком глубокого стека» также исчезает при использовании config.active_record.default_timezone = :utc

person AmitA    schedule 11.11.2010