本文介绍了Rails 3.1 中的 Rails.cache 错误 - TypeError:无法使用默认 proc 转储哈希的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 3.1.0.rc4(ruby 1.9.2p180(2011-02-18 修订版 30909)[x86_64-darwin10])上遇到了 Rails.cache 方法的问题.该代码在 2.3.12(ruby 1.8.7 (2011-02-18 patchlevel 334) [i686-linux], MBARI 0x8770, Ruby Enterprise Edition 2011.03)上的同一应用程序中运行良好,但在升级后开始返回错误.我还没有弄清楚为什么.

I running into an issue with the Rails.cache methods on 3.1.0.rc4 (ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10]). The code works fine within the same application on 2.3.12 (ruby 1.8.7 (2011-02-18 patchlevel 334) [i686-linux], MBARI 0x8770, Ruby Enterprise Edition 2011.03), but started returning an error following the upgrade. I haven't been able to figure out why yet.

尝试缓存具有多个作用域的对象时似乎会发生该错误.

The error seems to occur when trying to cache objects that have more than one scope on them.

此外,无论有多少作用域,任何使用 lambda 的作用域都会失败.

Also, any scopes using lambdas fail regardless of how many scopes.

我曾因这些模式而失败:

I have hit failures from these patterns:

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 块正在尝试缓存对象.

I have tried using the :raw => true option as an alternative, but that isn't working because the Rails.cache.fetch blocks are attempting to cache objects.

有什么建议吗?提前致谢!

Any suggestions? Thanks in advance!

推荐答案

这可能有点冗长,但我不得不花一些时间使用 Rails 源代码来了解缓存内部的工作原理.把事情写下来有助于我的理解,我认为分享一些关于事情如何运作的笔记不会受到伤害.如果你赶时间,请跳到最后.

This might be a little verbose but I had to spend some time with the Rails source code to learn how the caching internals work. Writing things down aids my understanding and I figure that sharing some notes on how things work can't hurt. Skip to the end if you're in a hurry.

这是 ActiveSupport 中的违规方法:

This is the offending method inside 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 在对象进入缓存之前将对象序列化为字节字符串,然后再次 Marshal 以反序列化对象.压缩问题在这里不重要,重要的是Marshal的使用.

Note the assignment to serialized_value. If you poke around inside cache.rb, you'll see that it uses Marshal to serialize objects to byte strings before they go into the cache and then Marshal again to deserialize objects. The compression issue isn't important here, the important thing is the use of Marshal.

问题就是:

某些对象无法转储:如果要转储的对象包括绑定、过程或方法对象、类 IO 的实例或单例对象,则会引发 TypeError.

有些东西的状态(例如操作系统文件描述符或块)不能被 Marshal 序列化.您注意到的错误是:

Some things have state (such as OS file descriptors or blocks) that can't be serialized by Marshal. The error you're noting is this:

无法使用默认 proc 转储哈希

因此,您模型中的某个人有一个实例变量,它是一个 Hash 并且该 Hash 使用一个块来提供默认值.column_methods_hash 方法使用了这样一个Hash,甚至将Hash 缓存在@dynamic_methods_hash 中;column_methods_hash 将被respond_to?method_missing 等公共方法(间接)调用.

So someone in your model has an instance variable that is a Hash and that Hash uses a block to supply default values. The column_methods_hash method uses such a Hash and even caches the Hash inside @dynamic_methods_hash; column_methods_hash will be called (indirectly) by public methods such as respond_to? and method_missing.

respond_to?method_missing 中的一个可能迟早会在​​每个 AR 模型实例上被调用,调用任一方法都会使您的对象无法序列化.因此,AR 模型实例在 Rails 3 中本质上是不可序列化的.

One of respond_to? or method_missing will probably get called on every AR model instance sooner or later and calling either method makes your object unserializable. So, AR model instances are essentially unserializable in Rails 3.

有趣的是,2.3.8 中的 respond_to?method_missing 实现也由使用块作为默认值的哈希支持.2.3.8 缓存是 "[...] 用于缓存字符串." 所以你很幸运有一个可以处理整个对象的后端,或者它在你的对象中有哈希处理之前使用了 Marshal;或者您可能正在使用 MemoryStore 缓存后端,这只不过是一个大哈希.

Interestingly enough, the respond_to? and method_missing implementations in 2.3.8 are also backed by a Hash that uses a block for default values. The 2.3.8 cache is "[...]is meant for caching strings." so you were getting lucky with a backend that could handle whole objects or it used Marshal before your objects had hash-with-procs in them; or perhaps you were using the MemoryStore cache backend and that's little more than a big Hash.

使用多个作用域与 lambda 可能最终将 Procs 存储在您的 AR 对象中;我希望 lambdas 与类(或单例类)而不是对象一起存储,但我没有打扰分析,因为 respond_to?method_missing 使 scope 问题无关紧要.

Using multiple scope-with-lambdas might end up storing Procs in your AR objects; I'd expect the lambdas to be stored with the class (or singleton class) rather than the objects but I didn't bother with an analysis as the problem with respond_to? and method_missing makes the scope issue irrelevant.

我认为您在缓存中存储了错误的内容并且很幸运.您可以开始正确使用 Rails 缓存(即存储简单的生成数据而不是整个模型),也可以实现 marshal_dump/marshal_load_dump/_load 方法,如 Marshal 中所述.或者,您可以使用 MemoryStore 后端之一,并限制每个服务器进程使用一个不同的缓存.

I think you've been storing the wrong things in your cache and getting lucky. You can either start using the Rails cache properly (i.e. store simple generated data rather than whole models) or you can implement the marshal_dump/marshal_load or _dump/_load methods as outlined in Marshal. Alternatively, you can use one of the MemoryStore backends and limit yourself to one distinct cache per server process.

您不能依赖于将 ActiveRecord 模型对象存储在 Rails 缓存中,除非您准备好自己处理编组或希望将自己限制在 MemoryStore 缓存后端.

You can't depend on storing ActiveRecord model objects in the Rails cache unless you're prepared to handle the marshalling yourself or you want to limit yourself to the MemoryStore cache backends.

在较新版本的 Rails 中,问题的确切来源已发生变化,但仍有许多 default_proc 实例与哈希相关联.

The exact source of the problem has changed in more recent versions of Rails but there are still many instances of default_procs associated with Hashes.

这篇关于Rails 3.1 中的 Rails.cache 错误 - TypeError:无法使用默认 proc 转储哈希的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-27 08:35