Garbage collection oddities

D

David Masover

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?
 
R

Roger Pack

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
 
D

David Masover

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.
 
R

Roger Pack

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
 
D

David Masover

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.
 

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

No members online now.

Forum statistics

Threads
473,961
Messages
2,570,131
Members
46,689
Latest member
liammiller

Latest Threads

Top