Okay, I think I've improved it some more:
[...]
> > recv ||= eval("self", self)
> > klass = recv.class
> > MethodMutexes[klass => name].synchronize do
> ============================
>
> This makes the memleak even heavier, going from potentially ~70 bytes to
> nearly 300 bytes per call...
I don't understand this though. isn't the memory freed when done? Or is
some symbol getting created in the process, or ?
Two things:
* you're using a klass => name hash as the key (instead of [klass, name],
which would take less memory)
* MethodMutexes grows monotonically since you don't delete the key => mutex
associations when you're done, so you keep the keys _and_ the mutexes,
which are pretty heavy
In the first version, the name included #{object_id}, so many values were
possible (as many as different Procs you call to_method on, modulo object_id
recycling). Different symbols and entries in the MethodMutexes hash would
accumulate.
> > begin
> > klass.send(:define_method, name, &self)
> ==========
>
> This will fail if the class was frozen.
Ah, I see if the _klass_ is frozen. But that will fail regardless won't
it? You can;t even include a module (ef InstanceExecHelper) in a frozen
class. Right?
Yes, that's why InstanceExecHelper must be included in Object (and you only
have to do that once).
Here's an improved (hastily written, not thoroughly tested) version:
class Object
module InstanceExecHelper; end
include InstanceExecHelper
end
class Proc
# Creates a local method based on a Proc.
def to_method(name=nil, recv=nil)
recv ||= eval("self", self)
do_remove = false
begin
old, Thread.critical = Thread.critical, true
unless name
do_remove = true
n = 0
n += 1 while recv.respond_to?(name = "__to_method_#{n}")
end
me = self
InstanceExecHelper.module_eval{ define_method(name, &me) }
return recv.method(name)
ensure
if do_remove
InstanceExecHelper.module_eval{ remove_method(name) rescue nil }
end
Thread.critical = old
end
end
end
module Kernel
def instance_exec(*args, &block)
block.to_method(nil,self).call(*args)
end
end
class Dummy
def f
:dummy_value
end
end
Dummy.freeze
require 'test/unit'
class TestInstanceEvalWithArgs < Test::Unit::TestCase
def test_instance_exec
# Create a block that returns the value of an argument and a value
# of a method call to +self+.
block = lambda { |a| [a, f] }
assert_equal [:arg_value, :dummy_value],
Dummy.new.instance_exec(:arg_value, &block)
end
def test_instance_exec_with_frozen_obj
block = lambda { |a| [a, f] }
obj = Dummy.new
obj.freeze
assert_equal [:arg_value, :dummy_value],
obj.instance_exec(:arg_value, &block)
end
def test_instance_exec_nested
i = 0
obj = Dummy.new
block = lambda do |arg|
[arg] + instance_exec(1){|a| [f, a] }
end
# the following assertion expanded by the xmp filter automagically from:
# obj.instance_exec(:arg_value, &block) #=>
assert_equal([:arg_value, :dummy_value, 1], obj.instance_exec(:arg_value, &block))
end
end
# >> Loaded suite -
# >> Started
# >> ...
# >> Finished in 0.00091 seconds.
# >>
# >> 3 tests, 3 assertions, 0 failures, 0 errors
···
On Tue, Jul 11, 2006 at 01:21:47AM +0900, transfire@gmail.com wrote:
--
Mauricio Fernandez - http://eigenclass.org - singular Ruby