From: Matthew Kerwin Date: 2013-02-16T19:00:22+09:00 Subject: Re: Fun with finalizers --e0cb4efe2dac86340804d5d4771a Content-Type: text/plain; charset=ISO-8859-1 Quick guess, it's the lambda. Replace it with #proc and try again? Sent from my phone, so excuse the typos. On Feb 16, 2013 4:39 PM, "Garthy D" wrote: > > Hi all, > > Just ran into something very interesting with finalizers. I've found a > workaround (it'll be obvious what it is from the code below), but I just > thought I'd share it for discussion's sake. > > Consider the code below: > > $fcount = 0 > > class A > def initialize > end > end > > class B > > def initialize > end > > def bar a > ObjectSpace.define_finalizer(**a, lambda {|oid| $fcount += 1}) > a = nil # xxx > nil > end > > def foo > > a = A.new > bar a > nil > end > end > > b = B.new > for i in 1 .. 10000 > GC.start > b.foo > GC.start > end > $stderr.print "Program ends. #{$fcount} finalizers called.\n" > > All but one of the finalizers run at the point of the trace. > > Now, comment the line marked with xxx. This shouldn't make any difference- > but it does. The program will report that 0 finalizers ran at the point of > the trace. You can confirm that the rest did run, but they *only* ran at > program exit, after the trace. Basically, the resources are never released > when finalizers are used. This is a big problem in a long-running program. > > If 10000 iterations isn't enough, you can always increase the counter. > > Note that I am using Ruby 1.9.2p136, Linux. Other versions may behave > differently. > > Why the code above? I was noticing in my code that finalizers were *never* > being run under any circumstances. The above is a stripped-down set of code > that acts similarly to mine. > > From a bit of research online, I've seen comments that say sometimes > values are left in registers, which affects GC. That seems fair enough in > general- but not here. There are 10000 objects here that aren't being > finalized- they're not in all in registers. If it's the stack, then the > first run should also have failed. It's not the return value, this is nil. > It's not the parameter coming in, the first test would have failed. > > Is it the current scope? I have a feeling that, based on the one line > change I made, that the current scope is somehow being captured by the > finaliser, so that if "a" remains set, the finaliser holds on to it, and > the object is never released. That's just my theory- I could be wrong. This > situation is particularly bad if you want to set up a finaliser and then > immediately return the value (say, as a result of caching a value)- the > finaliser will never be called, because you can't clear the value before > returning it. > > If you've followed me so far, you can probably guess the workaround- call > a separate method to set the finaliser, and clear the parameter afterward > in that call, then return to the caller with a nil return value. It's > annoying, but not too painful. > > What I am incredibly curious about is why this happens in the first place- > and why there doesn't seem too much talk of this specific problem when > using finalizers online. Finalizers failing to work when used in the > current scope without explicitly clearing the object afterward seems like > the sort of problem other people should be running into more often. > > It's bizarre. I'm wondering what everyone else thinks of it. Have I missed > something? > > Garth > > --e0cb4efe2dac86340804d5d4771a Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable

Quick guess, it's the lambda. Replace it with #proc and try again?

Sent from my phone, so excuse the typos.

On Feb 16, 2013 4:39 PM, "Garthy D" &l= t;garthy_lmkltybr@e= ntropicsoftware.com> wrote:

Hi all,

Just ran into something very interesting with finalizers. I've found a = workaround (it'll be obvious what it is from the code below), but I jus= t thought I'd share it for discussion's sake.

Consider the code below:

$fcount =3D 0

class A
=A0 def initialize
=A0 end
end

class B

=A0 def initialize
=A0 end

=A0 def bar a
=A0 =A0 ObjectSpace.define_finalizer(a, lambda {|oid| $fcount +=3D 1= })
=A0 =A0 a =3D nil # xxx
=A0 =A0 nil
=A0 end

=A0 def foo

=A0 =A0 a =3D A.new
=A0 =A0 bar a
=A0 =A0 nil
=A0 end
end

b =3D B.new
for i in 1 .. 10000
=A0 GC.start
=A0 b.foo
=A0 GC.start
end
$stderr.print "Program ends. #{$fcount} finalizers called.\n"

All but one of the finalizers run at the point of the trace.

Now, comment the line marked with xxx. This shouldn't make any differen= ce- but it does. The program will report that 0 finalizers ran at the point= of the trace. You can confirm that the rest did run, but they *only* ran a= t program exit, after the trace. Basically, the resources are never release= d when finalizers are used. This is a big problem in a long-running program= .

If 10000 iterations isn't enough, you can always increase the counter.<= br>
Note that I am using Ruby 1.9.2p136, Linux. Other versions may behave diffe= rently.

Why the code above? I was noticing in my code that finalizers were *never* = being run under any circumstances. The above is a stripped-down set of code= that acts similarly to mine.

From a bit of research online, I've seen comments that say sometimes va= lues are left in registers, which affects GC. That seems fair enough in gen= eral- but not here. There are 10000 objects here that aren't being fina= lized- they're not in all in registers. If it's the stack, then the= first run should also have failed. It's not the return value, this is = nil. It's not the parameter coming in, the first test would have failed= .

Is it the current scope? I have a feeling that, based on the one line chang= e I made, that the current scope is somehow being captured by the finaliser= , so that if "a" remains set, the finaliser holds on to it, and t= he object is never released. That's just my theory- I could be wrong. T= his situation is particularly bad if you want to set up a finaliser and the= n immediately return the value (say, as a result of caching a value)- the f= inaliser will never be called, because you can't clear the value before= returning it.

If you've followed me so far, you can probably guess the workaround- ca= ll a separate method to set the finaliser, and clear the parameter afterwar= d in that call, then return to the caller with a nil return value. It's= annoying, but not too painful.

What I am incredibly curious about is why this happens in the first place- = and why there doesn't seem too much talk of this specific problem when = using finalizers online. Finalizers failing to work when used in the curren= t scope without explicitly clearing the object afterward seems like the sor= t of problem other people should be running into more often.

It's bizarre. I'm wondering what everyone else thinks of it. Have I= missed something?

Garth

--e0cb4efe2dac86340804d5d4771a--