Подтвердить что ты не робот

Ошибка Rails.cache в Rails 3.1 - TypeError: не может сбрасывать хеш с обработкой по умолчанию

У меня возникла проблема с методами 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), но начал возвращать ошибку после обновления. Я еще не смог понять, почему еще.

Произошла ошибка при попытке кэширования объектов с несколькими областями.

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

У меня есть неудачи с этих шаблонов:

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 пытаются кэшировать объекты.

Любые предложения? Спасибо заранее!

4b9b3361

Ответ 1

Это может быть немного многословно, но мне пришлось потратить некоторое время с исходным кодом 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.


Точный источник проблемы изменился в более поздних версиях Rails, но все еще есть много экземпляров default_proc, связанных с хэшами.

Ответ 2

Благодаря му-слишком-короткому для его превосходного анализа. Мне удалось получить мою серию сериализации теперь с этим:

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

Я понял, что использование того или другого объекта создало объекты 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

Ответ 4

просто удалите proc по умолчанию после его изменения. что-то вроде:

your_hash.default = nil # clear the default_proc