I have created an object that failed to marshal due to a "singleton metaclass definition executed on runtime" (Is this description of what the code does correct?).
This is performed by following code:
# define class X that my use singleton class metaprogramming features
# through call of method :break_marshalling!
class X
def break_marshalling!
meta_class = class << self
self
end
meta_class.send(:define_method, :method_y) do
return
end
end
end
# prepare my instance of X now
instance_of_x = X.new
# marshalling fine here
Marshal.dump instance_of_x
# break marshalling with metprogramming features
instance_of_x.break_marshalling!
Marshal.dump instance_of_x
# fails with TypeError: singleton can't be dumped
What can I do to make the object marshall correct? Is it possible to "remove" the singleton components from class X
of object instance_of_x
?
I really need an advise on that because of some of our objects needed to be cached through Marshal.dump serialization mechanism. This code is executed in ruby-1.9.3 but I expect it to behave similar in ruby-2.0 or ruby-2.1
You can define custom marshal_dump
and marshal_load
methods:
class X
def break_marshalling!
meta_class = class << self
self
end
meta_class.send(:define_method, :method_y) do
return
end
end
# This should return an array of instance variables
# needed to correctly restore any X instance. Assuming none is needed
def marshal_dump
[]
end
# This should define instance variables
# needed to correctly restore any X instance. Assuming none is needed
def marshal_load(_)
[]
end
end
# Works fine
restored_instance_of_x =
Marshal.load Marshal.dump(X.new.tap { |x| x.break_marshalling! })
# Does not work
restored_instance_of_x.method_y
If you want you can manage dynamic methods definitions via method_missing
:
class X
def method_missing(name, *args)
if name == :method_y
break_marshalling!
public_send name, *args
else
super
end
end
end
# Works fine
Marshal.load(Marshal.dump(X.new)).method_y
The one-liner way to (usually) remove singleton information from instance_of_x
:
instance_of_x = instance_of_x.dup
I don't think there is such a thing is remove_singleton_class_information!
, but you can try to programatically undefine things in the singleton class.
According to the documentation Marshal doesn't like objects with singleton methods. With some experimentation, it seems it also doesn't like objects with a singleton class that has instance variables.
class Foo
def break_marshal
singleton_class.class_eval do
@@x = true
@x = false
define_method :bar do
puts @@x
puts singleton_class.instance_variable_get(:@x)
puts singleton_class.send(:baz)
puts singleton_class.const_get(:X)
end
define_singleton_method :baz do
"Singleton method in a singleton"
end
end
end
def fix_marshal
singleton_methods.each { |m| singleton_class.send(:remove_method, m) }
singleton_class.class_eval do
instance_variables.each { |v| remove_instance_variable v }
end
end
end
f = Foo.new
f.break_marshal
f.singleton_class.const_set(:X, 1)
f.bar
f.fix_marshal
Marshal.dump f
Note how hard I tried to break Marshal. I gave the singleton a class variable, an instance variable, a method, its own singleton method, and a constant. But Marshal works fine if you just remove the singleton method and the instance variable.
However, I don't think this is the right way to marshal such an object. @mdesantis's answer of defining your own marshal_dump
and marshal_load
is more proper.
I came up with this method, which recursively removes singleton_methods from an object, and from objects assigned to instance variables:
def self.strip_singleton(obj)
obj = obj.dup unless (obj.nil? || obj.singleton_methods.empty?)
obj.instance_variables.each do |var|
obj.instance_variable_set(var, strip_singleton(obj.instance_variable_get(var)))
end
obj
end