Garbage collection oddities

Given this code:

class WatchMeDie
  def self.finalizer
    proc do |id|
      puts "Finalized #{id}"
    end
  end
  def initialize
    ObjectSpace.define_finalizer self, &WatchMeDie.finalizer
  end
end

a = WatchMeDie.new
a = nil
GC.start
puts 'end of program'

# # #

This produces the expected outcome on 1.8.7:

Finalized 69858803067800
end of program

On 1.9.1, however, it doesn't:

end of program
Finalized 8054060

That is, on 1.9.1, the object is never collected. However, if I create two
such objects:

a = WatchMeDie.new
b = WatchMeDie.new
a = nil
b = nil
GC.start

Once I do this, the first object is collectible. It seems the most recent
WatchMeDie object will never be collected before the program ends.

Not that any of this really matters, but it complicates what I was actually
testing:

def make_a_thread
  Thread.new do
    sleep 1000
  end
end

def test_run
  make_a_thread
  WatchMeDie.new
end

a = test_run
b = WatchMeDie.new
a = nil
GC.start
puts 'end of program'

This example works as expected -- one WatchMeDie is finalized before program
end, and one after. However, if we change test_run to:

def test_run
  make_a_thread
  foo = WatchMeDie.new
end

1.9.1 still works as expected. However, 1.8 does not -- because I've now
assigned that object to a local variable, even one that didn't exist when I
called make_a_thread, it now can't be garbage collected.

One possible workaround, doesn't:

MyThreadProc = proc do
  sleep 1000
end
def make_a_thread
  Thread.new &MyThreadProc
end

In other words, for some bizarre reason, either the thread or the proc still
cares enough about that local scope, and the scope of all its callers, to hold
onto them for posterity.

I've worked around this in 1.8 by sending the thread spawning itself to
another thread, one created with a somewhat cleaner scope and call stack. But
it's a brutally ugly hack, and there have to be performance implications.

Fortunately, 1.9.1 appears to do the right thing, here. Unfortunately, this
kind of stuff is difficult to test. So I'm curious: Is this unique to 1.8.7, or
does it exist in 1.8.6, also? And is it likely to be fixed, or should I just
strongly encourage people to upgrade to 1.9?

That is, on 1.9.1, the object is never collected.

The kicker here is it may not be a bug--it may be that there is a "ghost
reference" to the object still kicking around somewhere [on the stack or
what not].
Because the C calls internally sometimes have extra references that
aren't used, they don't clean the whole stack just by using it, so
sometimes references are left there.

You could try going down deep in the stack then creating your objects
def go(n)
if n==100
   # do something
else
   go(n+1)
end
end

go(0)
GC.start

and see if that helps.

In general, however, you're not guaranteed that finalizers will be
called at any point--only guaranteed that they'll be called before the
program exits (I think that's what happens anyway).
GL!
=r

···

--
Posted via http://www.ruby-forum.com/\.

> That is, on 1.9.1, the object is never collected.

The kicker here is it may not be a bug--it may be that there is a "ghost
reference" to the object still kicking around somewhere [on the stack or
what not].

Right -- which I would consider to be a bug.

Or, at the very least, it's very inconvenient, and not at all what's expected.

In general, however, you're not guaranteed that finalizers will be
called at any point--only guaranteed that they'll be called before the
program exits (I think that's what happens anyway).

I understand, which is why I'm not bothered that for some reason, the first
object wasn't collected until the second one was created. I don't mind if the
GC is sloppy.

I do mind when it's an actual leak -- in this case, the objects in question
are pretty heavyweight, and if I create a few thousand threads that way, I'll
have a few thousand of them stuck forever.

···

On Thursday 23 July 2009 09:49:20 am Roger Pack wrote:

I do mind when it's an actual leak -- in this case, the objects in
question
are pretty heavyweight, and if I create a few thousand threads that way,
I'll
have a few thousand of them stuck forever.

If you're trying to have a multi-threaded app be GC-sane, your options
are MBARI patches, ruby 1.9, or jruby. AFAIK.
GL!
=r

···

--
Posted via http://www.ruby-forum.com/\.

Ah, thanks for that.

Yes, this does depend on GC. It does work with 1.9, but I'd been trying to
keep 1.8 compatibility. I guess I can drop that now, and test it on jruby.

···

On Monday 27 July 2009 07:29:08 pm Roger Pack wrote:

> I do mind when it's an actual leak -- in this case, the objects in
> question
> are pretty heavyweight, and if I create a few thousand threads that way,
> I'll
> have a few thousand of them stuck forever.

If you're trying to have a multi-threaded app be GC-sane, your options
are MBARI patches, ruby 1.9, or jruby. AFAIK.