Removing a class for good from ObjectSpace

F

Florian Weber

Hi!

Is there a way to remove a class for good from the ObjectSpace? I
would like to redefine a class entirely without any 'leftovers' of the
previous definition.

Thanks!

Ciao!
Florian
 
D

David Vallner

Florian said:
Hi!

Is there a way to remove a class for good from the ObjectSpace? I
would like to redefine a class entirely without any 'leftovers' of the
previous definition.

Thanks!

Ciao!
Florian
Might be a tad bit too tricky, AFAIK, classes are more or less global
constants and reference roots. Hack up something to undef all the
methods that aren't inherited from the class and the singleton class? E. g.:

Foo.class_eval {
public_instance_methods(false).each { | method_name |
eval "undef #{method_name}"
}
}

Similar for its singleton class. At least that's as good a way as I can
think of, if you mean to redefine the class anyway. Whether the parse
trees for the undeffed methods are deallocated too, I'll leave to the
savvier hackers to answer.

David Vallner
 
R

Ryan Leavengood

Hi!

Is there a way to remove a class for good from the ObjectSpace? I
would like to redefine a class entirely without any 'leftovers' of the
previous definition.

Here is something to think about:

class Module
def flush(klass)
remove_const(klass.name.intern)
end
end

class Foo
end

f =3D Foo.new
p f

Object.flush(Foo)

p Object.const_defined?:)Foo)
f2 =3D Foo.new # Causes exception
__END__

The object referred to by variable f will remain in the object space
until you set f to nil and start the GC (or let it run on its own.)

Ryan
 
F

Florian Weber

When I simply remove the class using remove_const it will still be
available via the ObjectSpace though, so that doesn't really work :/


Basically I want this test to pass:

class Foo
end

# removing the class somehow

ObjectSpace.each_object(Class) do |klass|
assert !(Foo > klass)
end
 
P

Paul Brannan

When I simply remove the class using remove_const it will still be
available via the ObjectSpace though, so that doesn't really work :/


Basically I want this test to pass:

class Foo
end

# removing the class somehow

ObjectSpace.each_object(Class) do |klass|
assert !(Foo > klass)
end

I think this is the same or a similar issue the Rails guys were dealing
with at the conference with their memory leak.

One solution I know of is to remove the constant and then set it to an
empty class:

Object.class_eval do
remove_const :Foo
const_set :Foo, Class.new { }
end
GC.start

I think this is because Ruby is storing the class in rb_class_tbl when
it is defined, and there is no way to remove an entry from the table,
only replace it. This may be a bug or an oversight; I don't know.

Paul
 
F

Florian Weber

I think this is the same or a similar issue the Rails guys were dealing
with at the conference with their memory leak.

One solution I know of is to remove the constant and then set it to an
empty class:

Object.class_eval do
remove_const :Foo
const_set :Foo, Class.new { }
end
GC.start

Thanks, Paul. Unfortunately it doesn't seem to work for me:

class Bar
end

class Foo < Bar
end

Object.class_eval do
remove_const :Foo
const_set :Foo, Class.new(Bar) { }
end
GC.start

class Foo < Bar
end

ObjectSpace.each_object(Class) do |klass|
puts klass if Bar > klass
end


It still prints out both Foo classes in this case. Any ideas?
 
P

Paul Brannan

Thanks, Paul. Unfortunately it doesn't seem to work for me:

class Bar
end

class Foo < Bar
end

Object.class_eval do
remove_const :Foo
const_set :Foo, Class.new(Bar) { }
end
GC.start

class Foo < Bar
end

ObjectSpace.each_object(Class) do |klass|
puts klass if Bar > klass
end

Your test looks odd to me. First you create Foo. Next you destroy it with
remove_const and const_set, but the class you set Foo to also inherits from
Bar. The next section of code has no effect, because Foo already
exists. Lastly you check to see if there are any classes that inherit
from Bar, which will of course be true, because Foo still inherits from
Bar.

Your other problem is that the original Foo class is probably still on
the stack, and the GC is picking that up. So here's a fixed test:

class Bar
end

def create_Foo(n=1000)
return create_Foo(n-1) if n > 0
eval "class Foo < Bar; end"
end
create_Foo

Object.class_eval do
remove_const :Foo
const_set :Foo, Class.new
end
GC.start

ObjectSpace.each_object(Class) do |klass|
puts klass if Bar > klass
end

Paul
 
F

Florian Weber

Your test looks odd to me. First you create Foo. Next you destroy it wi=
th
remove_const and const_set, but the class you set Foo to also inherits fr= om
Bar. The next section of code has no effect, because Foo already
exists. Lastly you check to see if there are any classes that inherit
from Bar, which will of course be true, because Foo still inherits from
Bar.

Yup, but I want that only one Foo class inheriting from Bar is found
in the ObjectSpace. Not two. My example above finds two classes named
Foo. The original one and the one created using Class.new.

Can you think of any way around that? To completely remove the old
version and make only the new one, the one created via Class.new,
available in the ObjectSpace

Your other problem is that the original Foo class is probably still on
the stack, and the GC is picking that up. So here's a fixed test:

class Bar
end

def create_Foo(n=3D1000)
return create_Foo(n-1) if n > 0
eval "class Foo < Bar; end"
end
create_Foo

Object.class_eval do
remove_const :Foo
const_set :Foo, Class.new
end
GC.start

ObjectSpace.each_object(Class) do |klass|
puts klass if Bar > klass
end

Ah! cool! thanks! that works. But unfortunately I can't use it like
that. the original class definition is done in another way, I can't do
it with eval and/or inside a method. Can you think of any other ways?

Thanks a lot!
 
F

Francis Hwang

Instead of:

You can also write:

Object.send :remove_const, :Foo
Object.send :const_set, :Foo, Class.new

That's what I did in my MockFS override.rb hack. In Ruby, "private"
just means "inconvenient".

f.
 
P

Paul Brannan

You can also write:

Object.send :remove_const, :Foo
Object.send :const_set, :Foo, Class.new

Not in 1.9:

$ irb-1.9
irb(main):001:0> class Foo; end
=> nil
irb(main):002:0> Object.send :remove_const, :Foo
NoMethodError: private method `remove_const' called for Object:Class
from (irb):2:in `send'
from (irb):2

Paul
 
P

Paul Brannan

Ah! cool! thanks! that works. But unfortunately I can't use it like
that. the original class definition is done in another way, I can't do
it with eval and/or inside a method. Can you think of any other ways?

Do not depend on the garbage collector's behavior in this way.

The class will eventually be collected once there are no longer any
leftovers on the stack.

Paul
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,015
Latest member
AmbrosePal

Latest Threads

Top