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

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

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.

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

On Monday 27 July 2009 07:29:08 pm Roger P. 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.

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.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs