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:
Note the assignment to
serialized_value. If you poke around insidecache.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_hashmethod uses such a Hash and even caches the Hash inside@dynamic_methods_hash;column_methods_hashwill be called (indirectly) by public methods such asrespond_to?andmethod_missing.One of
respond_to?ormethod_missingwill 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?andmethod_missingimplementations 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 theMemoryStorecache 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?andmethod_missingmakes thescopeissue 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_loador_dump/_loadmethods 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_procs associated with Hashes.