On Tue, Jul 11, 2006 at 01:21:47AM +0900, transfire / gmail.com wrote: > 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
-- Mauricio Fernandez - http://eigenclass.org - singular Ruby