I currently have two strategies implemented for serialization and
persistence. Both require storing the runtime in a thread local
variable 'currentRuntime'. They fetch the runtime from there when
they need it via a Ruby.getCurrentRuntime() call. I would like to
hear your critiques and comments.
First (least intrusive) strategy:
Decouple an instance from the runtime by making RubyObject.metaClass
transient, and
Add a RubyObject.metaClassName field
When an object is serialized/persisted, it goes alone. When it is
deserialized, it uses the metaClassName to fetch its metaclass from
the runtime. Voila! It's ready to go
Second, more intrusive strategy:
Leave the object coupled to its metaClass by leaving the metaClass
variable non-transient.
Decouple the metaclass from the runtime by making the RubyClass/
RubyModule marshal, allocator, methods, superclass, and parent fields
transient.
Add a new transient field in RubyModule called 'loadedByRuntime' with
a default value of true.
Leave only the RubyModule.classId field serializable.
Serialize/persist the object and its metaclass.
When the object is deserialized/reloaded, it brings along a
duplicate, decoupled instance of the metaclass with it. On the first
method invocation, RubyObject.callMethod checks the 'loadedByRuntime'
boolean and swaps the object's reference to its metaclass with a
reference to the original. The deserialized metaclass is then garbage
collected and the object is hooked up to its original metaclass and
is ready to go again.
Pros and Cons
The first strategy is simple but requires and additional instance
field 'metaClassName' in RubyObject. Code that is only touched in a
couple of places:
1) the constructor, when it sets the metaClass variable, also sets
the metaClassName.
2) the getMetaClass() accessor in RubyObject. It lazily initializes
when the metaClass is null and the metaClassName isn't.
It trades space in every object for the simplicity of the solution
The second strategy only requires an additional field in RubyModule:
the transient 'loadedByRuntime' flag. However, to get rid of the
duplicate metaclass requires switching the reference at runtime and
the check for that is done in callMethod() on every method
invocation. This happens on every object, not just the persistent
ones. Code is touched in several places, but is mostly in RubyModule
and RubyClass.
1) The accessors in RubyModule cause a swap of all of the references
to the allocator, marshal, methods, etc. on the first invocation of a
method in the deserialized object. The persisted metaclass is now
ready to go.
2) RubyObject.callMethod calls a 'MetaClassSwapper' with tasks (kinda
like Runnables) that are configured at launch. One task switches the
metaclasses and would be needed by serialzation to get rid of the
duplicate metaclass
3) The second task is one that we would post to the swapper. It
switches what we call the POM (persistent object memory) Ids. Once
the POM Ids are switched, the persisted metaclass will not be
reloaded along with the persistent object.
I prefer the first solution. It's simpler. Whaddya think? I will be
happy to post a diff showing what I have done.