Ошибка Rails.cache в Rails 3.1 - TypeError: не удается вывести хэш с процедурой по умолчанию - PullRequest
40 голосов
/ 18 июня 2011

Я столкнулся с проблемой с методами Rails.cache на 3.1.0.rc4 (ruby 1.9.2p180 (2011-02-18 ревизия 30909) [x86_64-darwin10]). Код прекрасно работает в том же приложении на 2.3.12 (ruby 1.8.7 (2011-02-18 patchlevel 334) [i686-linux], MBARI 0x8770, Ruby Enterprise Edition 2011.03), но начал возвращать ошибку после обновления. Я пока не смог понять, почему.

Ошибка возникает при попытке кэширования объектов, имеющих более одной области видимости.

Кроме того, любые области, использующие лямбды, не работают независимо от того, сколько областей.

Я столкнулся с ошибками по следующим схемам:

Rails.cache.fetch("keyname", :expires_in => 1.minute) do
    Model.scope_with_lambda
end


Rails.cache.fetch("keyname", :expires_in => 1.minute) do
    Model.scope.scope
end

Это ошибка, которую я получаю:

TypeError: can't dump hash with default proc
    from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:627:in `dump'
    from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:627:in `should_compress?'
    from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:559:in `initialize'
    from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:363:in `new'
    from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:363:in `block in write'
    from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:520:in `instrument'
    from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:362:in `write'
    from /project/shared/bundled_gems/ruby/1.9.1/gems/activesupport-3.1.0.rc4/lib/active_support/cache.rb:299:in `fetch'
    from (irb):62
    from /project/shared/bundled_gems/ruby/1.9.1/gems/railties-3.1.0.rc4/lib/rails/commands/console.rb:45:in `start'
    from /project/shared/bundled_gems/ruby/1.9.1/gems/railties-3.1.0.rc4/lib/rails/commands/console.rb:8:in `start'
    from /project/shared/bundled_gems/ruby/1.9.1/gems/railties-3.1.0.rc4/lib/rails/commands.rb:40:in `<top (required)>'
    from script/rails:6:in `require'
    from script/rails:6:in `<main>'

Я попытался использовать опцию: raw => true в качестве альтернативы, но она не работает, потому что блоки Rails.cache.fetch пытаются кэшировать объекты.

Есть предложения? Заранее спасибо!

Ответы [ 4 ]

98 голосов
/ 18 июня 2011

Это может быть немного многословно, но мне пришлось потратить некоторое время с исходным кодом Rails, чтобы узнать, как работает внутреннее кэширование. Записывание вещей помогает моему пониманию, и я полагаю, что обмен некоторыми заметками о том, как все работает, не может повредить. Если вы спешите, переходите до конца.


Почему это происходит

Это неправильный метод в ActiveSupport:

def should_compress?(value, options)
  if options[:compress] && value
    unless value.is_a?(Numeric)
      compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
      serialized_value = value.is_a?(String) ? value : Marshal.dump(value)
      return true if serialized_value.size >= compress_threshold   
    end
  end
  false  
end

Обратите внимание на присвоение serialized_value. Если вы покопаетесь внутри cache.rb, вы увидите, что он использует Marshal для сериализации объектов в байтовые строки перед тем, как они попадут в кэш, а затем снова маршал для десериализации объектов. Проблема сжатия здесь не важна, важно использовать маршала.

Проблема в том, что :

Некоторые объекты не могут быть выгружены: если объекты, которые должны быть выгружены, включают в себя привязки, объекты процедур или методов, экземпляры класса IO или одноэлементные объекты, будет вызвано TypeError.

Некоторые вещи имеют состояние (например, дескрипторы файлов ОС или блоки), которые не могут быть сериализованы маршалом. Вы видите ошибку:

не может сбросить хэш с процедурой по умолчанию

Итак, у кого-то в вашей модели есть переменная экземпляра, которая является Hash, и этот Hash использует блок для предоставления значений по умолчанию. Метод column_methods_hash использует такой хэш и даже кэширует хеш внутри @dynamic_methods_hash; column_methods_hash будет вызываться (косвенно) открытыми методами, такими как respond_to? и method_missing.

Один из respond_to? или method_missing, вероятно, рано или поздно будет вызван на каждом экземпляре модели AR, и вызов любого из этих методов сделает ваш объект не сериализуемым. Таким образом, экземпляры AR-моделей в Rails 3 практически не могут быть проанализированы.

Интересно, что реализации respond_to? и method_missing в 2.3.8 также поддерживаются хэшем, который использует блок для значений по умолчанию. Кеш 2.3.8 «[...] предназначен для кэширования строк.» , так что вам повезло с бэкэндом, который может обрабатывать целые объекты, или он использовал Marshal до того, как ваши объекты имели хеш-с- процы в них; или, возможно, вы использовали MemoryStore бэкэнд кеша, и это немного больше, чем большой хэш.

Использование нескольких scope-with-lambdas может привести к хранению Procs в ваших объектах AR; Я ожидал, что лямбды будут храниться с классом (или синглтон-классом), а не с объектами, но я не стал заниматься анализом, поскольку проблема с respond_to? и method_missing делает проблему scope неактуальной.

Что вы можете с этим сделать

Я думаю, что вы хранили неправильные вещи в своем кеше и вам повезло. Вы можете либо начать использовать кеш Rails должным образом (т.е. хранить простые сгенерированные данные, а не целые модели), либо можете реализовать методы marshal_dump / marshal_load или _dump / _load, как описано в Marshal . Кроме того, вы можете использовать один из бэкэндов MemoryStore и ограничить себя одним отдельным кешем для каждого процесса сервера.


Резюме

Вы не можете зависеть от хранения объектов модели ActiveRecord в кэше Rails, если вы не готовы самостоятельно выполнять маршалинг или не хотите ограничивать себя бэкэндами кэша MemoryStore.

5 голосов
/ 17 июня 2014

Спасибо Му-С-Слишком короткому за его превосходный анализ. Мне удалось заставить сериализовать мою модель сейчас с помощью:

def marshal_dump
  {}.merge(attributes)
end

def marshal_load stuff
  send :initialize, stuff, :without_protection => true
end

У меня также есть некоторые "виртуальные атрибуты", установленные прямым запросом к соединению SQL с использованием AS например. SELECT DISTINCT posts.*, name from authors AS author_name FROM posts INNER JOIN authors ON author.post_id = posts.id WHERE posts.id = 123. Чтобы они работали, мне нужно объявить attr_accessor для каждого, а затем сбросить / загрузить их, например, так:

VIRTUAL_ATTRIBUTES = [:author_name]

attr_accessor *VIRTUAL_ATTRIBUTES

def marshal_dump
  virtual_attributes = Hash[VIRTUAL_ATTRIBUTES.map {|col| [col, self.send(col)] }]
  {}.with_indifferent_access.merge(attributes).merge(virtual_attributes)
end

def marshal_load stuff
  stuff = stuff.with_indifferent_access
  send :initialize, stuff, :without_protection => true
  VIRTUAL_ATTRIBUTES.each do |attribute|
    self.send("#{attribute}=", stuff[attribute])
  end
end

Использование Rails 3.2.18

3 голосов
/ 15 сентября 2014

Я понял, что используя where или некоторую область видимости создал ActiveRecord::Relation объекты.Затем я заметил, что выполнение простого Model.find сработало.Я подозревал, что ему не понравился объект ActiveRecord::Relation, поэтому я принудительно преобразовал его в обычный Array, и это сработало для меня.

Rails.cache.fetch([self.id, 'relA']) do
  relA.where(
      attr1: 'some_value'
  ).order(
      'attr2 DESC'
  ).includes(
      :rel_1,
      :rel_2
  ).decorate.to_a
end
0 голосов
/ 17 июня 2015

просто удалите процесс по умолчанию после того, как вы закончили его изменять. что-то вроде:

your_hash.default = nil # clear the default_proc
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...