Ruby Symbol#to_proc leaks references in 1.9.2-p180

2019-02-22 09:54发布

问题:

Ok, this is my second attempt at debugging the memory issues with my Sinatra app. I believe I have it nailed down into simple sample code this time.

It seems when I filter an array through .map(&:some_method), it causes the items in that array to not get garbage collected. Running the equivalent .map{|x| x.some_method} is totally fine.

Demonstration: Given a simple sample class:

class C
  def foo
    "foo"
  end
end

If I run the following in IRB, it gets collected normally:

ruby-1.9.2-p180 :001 > a = 10.times.map{C.new}
 => [...]
ruby-1.9.2-p180 :002 > b = a.map{|x| x.foo}
 => ["foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo"]
ruby-1.9.2-p180 :003 > ObjectSpace.each_object(C){}
 => 10
ruby-1.9.2-p180 :004 > a = nil
 => nil
ruby-1.9.2-p180 :005 > b = nil
 => nil
ruby-1.9.2-p180 :006 > GC.start
 => nil
ruby-1.9.2-p180 :007 > ObjectSpace.each_object(C){}
 => 0

So no references to C exist anymore. Good. But substituting map{|x| x.foo} with map(&:foo) (which is advertised as equivalent), it doesn't get collected:

ruby-1.9.2-p180 :001 > a = 10.times.map{C.new}
 => [...]
ruby-1.9.2-p180 :002 > b = a.map(&:foo)
 => ["foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo"]
ruby-1.9.2-p180 :003 > ObjectSpace.each_object(C){}
 => 10
ruby-1.9.2-p180 :004 > a = nil
 => nil
ruby-1.9.2-p180 :005 > b = nil
 => nil
ruby-1.9.2-p180 :006 > GC.start
 => nil
ruby-1.9.2-p180 :007 > ObjectSpace.each_object(C){}
 => 10
ruby-1.9.2-p180 :008 >

Is this a ruby bug? I'll try in more versions of ruby to be sure but this seems like an obvious issue. Anyone know what I'm doing wrong?

Edit:

I've tried this in 1.8.7-p352 and it doesn't have the issue. 1.9.3-preview1 does however still have the issue. Is a bug report in order or am I doing something wrong?

Edit2: formatting (why does putting four spaces before each line produce syntax highlighting while <pre> tags don't?)

回答1:

As a.map(&:foo) should be the exact equivalent to a.map{|x| x.foo}, it seems like you really hit a bug in the Ruby code here. It cannot hurt to file a bug report on (http://redmine.ruby-lang.org/), the worst that can happen is that its being ignored. You can decrease the chances of that by providing a patch for the issue.

EDIT: I threw on my IRB and tried your code. I can reproduce the issue you describe on ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-linux]. However, explicitely calling to_proc on the symbol does not suffer from the same problem:

irb(main):001:0> class C; def foo; end; end
=> nil
irb(main):002:0> a = 10.times.map { C.new }
=> [...]
irb(main):004:0> b = a.map(&:foo.to_proc)
=> [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]
irb(main):005:0> ObjectSpace.each_object(C){}
=> 10
irb(main):006:0> a = b = nil
=> nil
irb(main):007:0> GC.start
=> nil
irb(main):008:0> ObjectSpace.each_object(C){}
=> 0

It seems we are facing an issue with the implicit Symbol -> Proc conversion here. Maybe I will try to dive a bit into the Ruby source later. If so, I will keep you updated.

EDIT 2:

Simple workaround for the problem:

class Symbol
  def to_proc
    lambda { |x| x.send(self) }
  end
end

class C
  def foo; "foo"; end
end

a = 10.times.map { C.new }
b = a.map(&:foo)
p b
a = b = nil
GC.start
p ObjectSpace.each_object(C) {}

prints 0.