Removing a class for good from ObjectSpace

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

Florian Weber wrote:

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

Here is something to think about:

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

class Foo
end

f = Foo.new
p f

Object.flush(Foo)

p Object.const_defined?(:Foo)
f2 = 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

···

On 10/20/05, Florian Weber <csshsh@gmail.com> wrote:

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.

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

Basically I want this test to pass:

class Foo
end

# removing the class somehow

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

···

On 10/20/05, Ryan Leavengood <leavengood@gmail.com> wrote:

On 10/20/05, Florian Weber <csshsh@gmail.com> wrote:
> 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 = Foo.new
p f

Object.flush(Foo)

p Object.const_defined?(:Foo)
f2 = 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

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

···

On Fri, Oct 21, 2005 at 08:45:38PM +0900, Florian Weber wrote:

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

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

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?

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

···

On Sat, Oct 22, 2005 at 12:14:42AM +0900, Florian Weber wrote:

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.

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=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

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!

Instead of:

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

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.

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

···

On Sat, Oct 22, 2005 at 03:09:57AM +0900, Florian Weber wrote:

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?

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

···

On Sat, Oct 22, 2005 at 03:22:00AM +0900, Francis Hwang wrote:

You can also write:

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