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