问题描述
我在3.1.0.rc4(红宝石1.9.2p180(2011-02-18修订版30909)[x86_64-darwin10])上遇到Rails.cache方法问题.该代码在2.3.12(ruby 1.8.7(2011-02-18补丁程序级别334)[i686-linux],MBARI 0x8770,Ruby Enterprise Edition 2011.03)上的同一应用程序中均能正常工作,但升级后开始返回错误.我还不能弄清楚为什么.
尝试缓存具有多个作用域的对象时,似乎会发生错误.
此外,无论使用多少个范围,任何使用lambda的范围都会失败.
我遇到了以下几种故障:
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块正在尝试缓存对象.
有什么建议吗?预先感谢!
这可能有点冗长,但是我不得不花一些时间来了解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
内部查看,您会发现它使用元帅将对象序列化为字节字符串,然后再进入缓存,然后再次编组以反序列化对象.压缩问题在这里并不重要,重要的是使用元帅.
问题是:
有些东西具有状态,例如元帅无法序列化的状态(例如OS文件描述符或块).您注意到的错误是这样的:
因此,模型中的某人具有一个实例变量,该实例变量是Hash,并且该Hash使用块提供默认值. column_methods_hash
方法使用这样的Hash,甚至将Hash缓存在@dynamic_methods_hash
内部. column_methods_hash
将由诸如respond_to?
和method_missing
的公共方法(间接)调用.
respond_to?
或method_missing
中的一个可能迟早会在每个AR模型实例上被调用,并且调用任一方法都会使您的对象无法序列化.因此,AR模型实例在Rails 3中基本上无法序列化.
有趣的是,2.3.8中的respond_to?
和method_missing
实现也由使用默认值块的哈希支持. 2.3.8缓存是"[...]用于缓存字符串. "因此,您很幸运地拥有一个可以处理整个对象的后端,或者在您的对象中包含带哈希值的对象之前使用了Marshal;或者您使用的是 MemoryStore
缓存后端,那么不仅仅是一个大哈希.
使用多个带lambda的作用域可能最终会将Procs存储在您的AR对象中;我希望lambda与类(或单例类)而不是对象一起存储,但是我没有为分析而烦恼,因为respond_to?
和method_missing
的问题使scope
问题变得无关紧要. /p>
您可以做什么
我认为您一直在将错误的内容存储在缓存中,并且很幸运.您可以开始正确使用Rails缓存(即存储简单的生成的数据,而不是存储整个模型),或者可以实现marshal_dump/marshal_load
或_dump
/_load
方法://ruby-doc.org/core/classes/Marshal.html"rel =" nofollow noreferrer>元帅.另外,您可以使用MemoryStore后端之一并将自己限制为每个服务器进程使用一个不同的缓存.
执行摘要
除非准备好自行处理编组或者想将自己限制在MemoryStore缓存后端,否则您不能依赖于将ActiveRecord模型对象存储在Rails缓存中.
该问题的确切来源在最新版本的Rails中已更改,但仍有许多与哈希关联的default_proc
实例.
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.
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
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.
Any suggestions? Thanks in advance!
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.
Why It Happens
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
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.
The problem is that:
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:
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
.
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.
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.
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.
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.
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.
The exact source of the problem has changed in more recent versions of Rails but there are still many instances of default_proc
s associated with Hashes.
这篇关于Rails 3.1中的Rails.cache错误-TypeError:无法使用默认proc转储哈希的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!