Avoiding capturing local variables in closures

I do a lot of event-driven programming (Eventmachine, ZMQMachine). You
would think that by now I would know how to answer this question but I’m
a bit stumped.

I have a memory leak in a program. I am 80% sure that the leak is due to
a long-lived block having captured a local variable (containing a lot
of data). I would like to prevent this from happening, but I’m unsure of
the best way to accomplish it.

I’ve read Ola B.'s blog article on this topic [1]. However, I’m still
a bit unclear on the concept.

An example…

def some_callback(bigarg1, bigarg2)
foo = transform(bigarg1)
bar = transformagain(bigarg2)

schedule_for_execution { baz(foo, bar) }
end

If I am understanding things correctly, the block passed to
#schedule_for_execution is capturing the variables foo, bar, bigarg1 and
bigarg2. I could avoid capturing bigarg1 and bigarg2 with a small
refactoring.

def some_callback(bigarg1, bigarg2)
foo = transform(bigarg1)
bar = transformagain(bigarg2)

avoid_capture(foo, bar)
end

def avoid_capture(foo, bar)
schedule_for_execution { baz(foo, bar) }
end

Is this sufficient to avoid capturing those args in the closure? If not,
what do I need to do?

Further, what if I have an instance var that is referencing some large
data? Do I need to worry about that being captured by the block or does
it not matter because the block has already captured self?

e.g.

def some_callback(bigarg1, bigarg2)
foo = transform(bigarg1)
bar = transformagain(bigarg2)
@quxxo = bigarg1 + bigarg2

schedule_for_execution { baz(foo, bar) }
end

cr

[1]

On Jul 29, 2011, at 5:48 PM, Chuck R. wrote:

foo = transform(bigarg1)
bar = transformagain(bigarg2)

avoid_capture(foo, bar)
end

def avoid_capture(foo, bar)
schedule_for_execution { baz(foo, bar) }
end

Is this sufficient to avoid capturing those args in the closure? If not, what do
I need to do?

That should be sufficient - there’s no more reference to some_callback’s
environment where bigarg1 and bigarg2 are referenced. Another
approach might be, on 1.9:

def some_callback(bigarg1)
foo = transform(bigarg1)
schedule_for_execution { |;bigarg1| bad(foo) }
end

This is the only real reason I know of for using the Ruby 1.9
block-local syntax. As
long as the implementation is smart enough not to foolishly store
bigarg1.

Michael E.
[email protected]
http://carboni.ca/

Answered by Michael

Further, what if I have an instance var that is referencing some large data? Do
I need to worry >about that being captured by the block or does it not matter
because the block has already >captured self?

At first sight you need to worry if you pass the block to a method
outside of the receiver because that will create a new reference to
“self”.
As long as you pass the block around in the same object I do not see an
issue.

HTH
Robert

On Sat, Jul 30, 2011 at 10:14 AM, Robert D. [email protected]
wrote:

Answered by Michael

Further, what if I have an instance var that is referencing some large data? Do
I need to worry >about that being captured by the block or does it not matter
because the block has already >captured self?

At first sight you need to worry if you pass the block to a method
outside of the receiver because that will create a new reference to
“self”.
As long as you pass the block around in the same object I do not see an issue.

Generally care should be taken about the value of self when a long
lived block is created. Typically classes are better candidates than
instances.

Kind regards

robert

On Sat, Jul 30, 2011 at 6:25 PM, Chuck R. [email protected]
wrote:

On Jul 30, 2011, at 3:14 AM, Robert D. wrote:
Right?
That is what I meant, yes. Sorry I should have provided the code snippet

Cheers
Robert

On Jul 30, 2011, at 3:14 AM, Robert D. wrote:

Answered by Michael

Further, what if I have an instance var that is referencing some large data? Do
I need to worry >about that being captured by the block or does it not matter
because the block has already >captured self?

At first sight you need to worry if you pass the block to a method
outside of the receiver because that will create a new reference to
“self”.
As long as you pass the block around in the same object I do not see an issue.

I want to be clear that I understand you. Here’s some code that I think
illustrates what you mean.

class A
def some_callback(bigarg1, bigarg2)
foo = transform(bigarg1)
bar = transformagain(bigarg2)
@another_instance = B.new

avoid_capture(foo, bar)
end

def avoid_capture(foo, bar)
# self (from an instance of A) is captured in the block that
# is now held by @another_instance
@another_instance.schedule_for_execution { baz(foo, bar) }
end
end

Right?

cr

Robert D. wrote in post #1013853:

Answered by Michael

Further, what if I have an instance var that is referencing some large data? Do
I need to worry >about that being captured by the block or does it not
matter
because the block has already >captured self?

At first sight you need to worry if you pass the block to a method
outside of the receiver because that will create a new reference to
“self”.
As long as you pass the block around in the same object I do not see an
issue.

Huh?

class Dog

def initialize(x)
@greeting = x
end

def make_proc
return Proc.new { greet }
end

def greet
puts @greeting
end
end

d = Dog.new(‘lots of memory’)
p = d.make_proc

d = nil
p.call

–output:–
lots of memory

It’s pretty clear to me that the block creates a reference to self=d no
matter
where the block ends up, and until the block’s reference to d is
destroyed (or the block itself is destroyed), the
instance variable d will continue to exist in memory.

Robert D. wrote in post #1013952:

On Sun, Jul 31, 2011 at 9:27 AM, 7stud – [email protected]
wrote:

Robert D. wrote in post #1013853:

May I ask what your point is?
R.

That what you said in the text I quoted is wrong.

On Sun, Jul 31, 2011 at 9:27 AM, 7stud – [email protected]
wrote:

Robert D. wrote in post #1013853:

May I ask what your point is?
R.