Rails。Rails 3.1中的缓存错误——类型错误:不能使用默认的proc转储散列

时间:2022-07-17 00:13:18

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.

我遇到了Rails的问题。3.1.0缓存方法。rc4 (ruby 1.9.2p180(2011-02-18修订版30909)[x86_64-达尔文10])。该代码在2.3.12 (ruby 1.8.7 (2011-02-18 patchlevel 334) [i686-linux], MBARI 0x8770, ruby Enterprise Edition 2011.03)的同一个应用程序中运行良好,但在升级后开始返回错误。我还不知道为什么。

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

当试图缓存具有多个作用域的对象时,似乎会出现错误。

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

同样,任何使用lambdas的作用域都不会不管有多少作用域。

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

This is the error that I receive:

这是我收到的错误:

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>'

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.

我尝试过使用:raw => true选项作为替代,但这并不奏效,因为Rails.cache。获取块正在尝试缓存对象。

Any suggestions? Thanks in advance!

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

4 个解决方案

#1


95  

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.

这可能有点冗长,但我不得不花些时间使用Rails源代码来了解缓存内部如何工作。把事情写下来有助于我的理解,我认为分享一些事情是如何工作的笔记不会有什么坏处。如果你赶时间,就跳到最后。


Why It Happens

This is the offending method inside ActiveSupport:

这是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

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.

注意序列化_value的赋值。如果你在高速缓存里闲逛。rb,你会看到它使用Marshal将对象序列化为字节字符串,然后再进行封送以反序列化对象。压缩问题在这里并不重要,重要的是封送的使用。

The problem is that:

问题是:

Some objects cannot be dumped: if the objects to be dumped include bindings, procedure or method objects, instances of class IO, or singleton objects, a TypeError will be raised.

有些对象不能被转储:如果要转储的对象包括绑定、过程或方法对象、类IO实例或单例对象,则将引发一个TypeError。

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:

有些东西的状态(比如OS文件描述符或块)不能被封送器序列化。你注意到的错误是:

can't dump hash with default proc

无法使用默认proc转储散列

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.

你的模型中有一个实例变量是一个散列,这个哈希用一个块来提供默认值。column_methods_hash方法使用这样的散列,甚至在@dynamic_methods_hash中缓存散列;column_methods_hash会被诸如respond_to之类的公共方法(间接地)调用?和method_missing。

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.

respond_to之一?或者method_missing可能迟早会在每个AR模型实例上被调用,调用其中任何一个方法都会使你的对象不可序列化。因此,AR模型实例在Rails 3中本质上是不可序列化的。

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.

有趣的是,respond_to吗?2.3.8中的method_missing实现也由一个使用块作为默认值的散列支持。2.3.8缓存是“[……”是用来缓存字符串的所以你很幸运有一个后端可以处理整个对象或者它在对象包含hashwithprocs之前使用了Marshal;或者,您可能使用的是MemoryStore缓存后端,这只是一个大散列。

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.

使用多个作用域-with-lambdas可能最终在AR对象中存储过程;我希望lambda用类(或单例类)而不是对象来存储lambdas,但我并没有因为respond_to的问题而费心进行分析。method_missing使得范围问题变得无关紧要。

What You Can Do About It

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.

我想你把错误的东西储存在你的缓存里了,你很幸运。您可以正确地开始使用Rails缓存(例如,存储简单生成的数据而不是整个模型),也可以实现marshal_dump/marshal_load或_dump/_load方法,如Marshal所述。或者,您可以使用MemoryStore中的一个后端,并将自己限制为每个服务器进程一个不同的缓存。


Executive Summary

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缓存中存储ActiveRecord模型对象,除非您准备自己处理封送,或者您希望将自己限制在MemoryStore缓存后端。

#2


5  

Thanks to mu-is-too-short for his excellent analysis. I've managed to get my model to serialize now with this:

多亏了穆太短的分析。我已经设法使我的模型现在连载如下:

def marshal_dump
  {}.merge(attributes)
end

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

I also have some "virtual attributes" set by a direct SQL join query using AS e.g. SELECT DISTINCT posts.*, name from authors AS author_name FROM posts INNER JOIN authors ON author.post_id = posts.id WHERE posts.id = 123. For these to work I need to declare an attr_accessor for each, then dump/load them too, like so:

我也有一些由直接SQL join查询设置的“虚拟属性”,例如,选择不同的帖子。*,来自作者的名称为author_name,来自作者的内部连接作者。post_id =。id的帖子。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

Using Rails 3.2.18

使用Rails 3.2.18

#3


3  

I realized that using where or some scope created ActiveRecord::Relation objects. I then noticed that doing a simple Model.find worked. I suspected that it didn't like the ActiveRecord::Relation object so I forced conversion to a plain Array and that worked for me.

我意识到使用where或某个范围创建ActiveRecord::关系对象。然后我注意到做一个简单的模型。找到工作。我怀疑它不喜欢ActiveRecord::关系对象,所以我强制转换为一个普通的数组,这对我很有效。

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


0  

just remove the default proc after you finished altering it. something like:

只需在修改后删除默认的proc。喜欢的东西:

your_hash.default = nil # clear the default_proc

#1


95  

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.

这可能有点冗长,但我不得不花些时间使用Rails源代码来了解缓存内部如何工作。把事情写下来有助于我的理解,我认为分享一些事情是如何工作的笔记不会有什么坏处。如果你赶时间,就跳到最后。


Why It Happens

This is the offending method inside ActiveSupport:

这是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

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.

注意序列化_value的赋值。如果你在高速缓存里闲逛。rb,你会看到它使用Marshal将对象序列化为字节字符串,然后再进行封送以反序列化对象。压缩问题在这里并不重要,重要的是封送的使用。

The problem is that:

问题是:

Some objects cannot be dumped: if the objects to be dumped include bindings, procedure or method objects, instances of class IO, or singleton objects, a TypeError will be raised.

有些对象不能被转储:如果要转储的对象包括绑定、过程或方法对象、类IO实例或单例对象,则将引发一个TypeError。

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:

有些东西的状态(比如OS文件描述符或块)不能被封送器序列化。你注意到的错误是:

can't dump hash with default proc

无法使用默认proc转储散列

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.

你的模型中有一个实例变量是一个散列,这个哈希用一个块来提供默认值。column_methods_hash方法使用这样的散列,甚至在@dynamic_methods_hash中缓存散列;column_methods_hash会被诸如respond_to之类的公共方法(间接地)调用?和method_missing。

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.

respond_to之一?或者method_missing可能迟早会在每个AR模型实例上被调用,调用其中任何一个方法都会使你的对象不可序列化。因此,AR模型实例在Rails 3中本质上是不可序列化的。

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.

有趣的是,respond_to吗?2.3.8中的method_missing实现也由一个使用块作为默认值的散列支持。2.3.8缓存是“[……”是用来缓存字符串的所以你很幸运有一个后端可以处理整个对象或者它在对象包含hashwithprocs之前使用了Marshal;或者,您可能使用的是MemoryStore缓存后端,这只是一个大散列。

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.

使用多个作用域-with-lambdas可能最终在AR对象中存储过程;我希望lambda用类(或单例类)而不是对象来存储lambdas,但我并没有因为respond_to的问题而费心进行分析。method_missing使得范围问题变得无关紧要。

What You Can Do About It

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.

我想你把错误的东西储存在你的缓存里了,你很幸运。您可以正确地开始使用Rails缓存(例如,存储简单生成的数据而不是整个模型),也可以实现marshal_dump/marshal_load或_dump/_load方法,如Marshal所述。或者,您可以使用MemoryStore中的一个后端,并将自己限制为每个服务器进程一个不同的缓存。


Executive Summary

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缓存中存储ActiveRecord模型对象,除非您准备自己处理封送,或者您希望将自己限制在MemoryStore缓存后端。

#2


5  

Thanks to mu-is-too-short for his excellent analysis. I've managed to get my model to serialize now with this:

多亏了穆太短的分析。我已经设法使我的模型现在连载如下:

def marshal_dump
  {}.merge(attributes)
end

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

I also have some "virtual attributes" set by a direct SQL join query using AS e.g. SELECT DISTINCT posts.*, name from authors AS author_name FROM posts INNER JOIN authors ON author.post_id = posts.id WHERE posts.id = 123. For these to work I need to declare an attr_accessor for each, then dump/load them too, like so:

我也有一些由直接SQL join查询设置的“虚拟属性”,例如,选择不同的帖子。*,来自作者的名称为author_name,来自作者的内部连接作者。post_id =。id的帖子。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

Using Rails 3.2.18

使用Rails 3.2.18

#3


3  

I realized that using where or some scope created ActiveRecord::Relation objects. I then noticed that doing a simple Model.find worked. I suspected that it didn't like the ActiveRecord::Relation object so I forced conversion to a plain Array and that worked for me.

我意识到使用where或某个范围创建ActiveRecord::关系对象。然后我注意到做一个简单的模型。找到工作。我怀疑它不喜欢ActiveRecord::关系对象,所以我强制转换为一个普通的数组,这对我很有效。

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


0  

just remove the default proc after you finished altering it. something like:

只需在修改后删除默认的proc。喜欢的东西:

your_hash.default = nil # clear the default_proc