Archive for September, 2009

can’t be referred hell

September 15, 2009

Recently we upgraded from Rails 1.2.6 to 2.3.4, which was long overdue.  As you’d expect, lots of things broke, plugins needed updating etc.

Then we got this in the logs:

/!\ FAILSAFE /!\ Tue Sep 15 15:19:22 +0100 2009
Status: 500 Internal Server Error
Card can't be referred
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/session_store.rb:94:in `dump'
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/session_store.rb:94:in `marshal'
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/session_store.rb:150:in `marshal_data!'

Card being one of the models which we were trying to save into the session. I know that not everyone saves entire models into the session – I might write a post about that too. But whatever your preference, it should work, providing you are not serializing an object which can’t be serialized (more on that below).

It took days of messing about to figure out what to do about this. The quick answer is: require the model explicitly in the environment.rb file. No idea why. Only one of our 30+ models was affected, and there is nothing special about it that I can see.

If you’re not in a hurry, here’s the low-down on what we found out.

The error is thrown in Marshal.c, in the following code

00101 static VALUE
00102 class2path(klass)
00103     VALUE klass;
00104 {
00105     VALUE path = rb_class_path(klass);
00106     char *n = RSTRING(path)->ptr;
00107 
00108     if (n[0] == '#') {
00109         rb_raise(rb_eTypeError, "can't dump anonymous %s %s",
00110                  (TYPE(klass) == T_CLASS ? "class" : "module"),
00111                  n);
00112     }
00113     if (rb_path2class(n) != rb_class_real(klass)) {
00114         rb_raise(rb_eTypeError, "%s can't be referred", n);
00115     }
00116     return path;
00117 }

I don’t read C but I am told this means something along the lines of “check if when we recreate this object we end up with the same class and ancestry as the object we are trying to serialize”. For some reason, our Card object was failing this check.

Inserting a ruby-style equivalent in the ActiveRecord::Session::SessionStore code immediately before the call to Marshal.dump eg

data[:card].class.ancestors == Card.new.class.ancestors

failed to pick up the problem. Also, we couldn’t recreate this in the console no matter what – the object always serialized perfectly.

Marhal.c documentation says “Some objects cannot be dumped: if the objects to be dumped include bindings, procedure objects, instances of class IO, or singleton objects, a TypeError will be raised.” None of the above seems to apply – even so we commented our all the model’s custom methods and attributes – effectively making it a vanilla ActiveRecord::Base object – to no effect.

Some sort of namespace collision was the prime suspect but we eliminated all likely possibilities.

Clearly somewhere along the line the Card class definition is getting unloaded. It may be something to do with Rails Engines, which we use heavily. I don’t really like relying on a mystery solution, but so far – it works.