public
Last active

Trying to reproduce the AR memory leak

  • Download Gist
leak.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
# Showing how regular ruby objects behave
# Run this with 'ruby lib/leak.rb' instead of 'rails r'
unless defined? Rails
class MyModel
def self.first;MyModel.new;end
def self.all;[MyModel.new] * 1000;end
def self.limit(*a);MyModel;end
def id;0;end
end
class MyAssociation
def self.first;MyAssociation.new;end
def self.all;[MyAssociation.new] * 1000;end
def self.where(*a);MyAssociation;end
def my_model;MyModel.new;end
end
end
 
# Helper to make sure we run things out of scope
def run
# This seems to prevent GC object count oddities. Go figure
1.times { yield }
nil
end
 
# Simple helper to count references in memory
def c(klass)
ObjectSpace.each_object(klass) {}
end
 
# Helper to check in memory objects
def list_objects
# Now, GC in the same thread causes leaks and object count oddities. Go figure
Thread.new { 10.times { GC.start }}.join
"MyModel -> #{c MyModel}, MyAssociation -> #{c MyAssociation} (1 is fine, 2+ is leak)"
end
 
#########
 
run { MyModel.first }
run { MyAssociation.first }
puts "1) No leaks: #{list_objects}"
 
# Again just to show the GC 1 instance weirdness
run { MyModel.first }
run { MyAssociation.first }
puts "2) No leaks: #{list_objects}"
 
# AR::Base#all runs fine
run { MyModel.all }
run { MyAssociation.all }
puts "3) No leaks: #{list_objects}"
 
# AR::Base#all without to_proc is also fine
run { MyModel.limit(1).all }
run { MyAssociation.where(:id => 1).all }
puts "4) No leaks: #{list_objects}"
 
# This is where it gets interesting
# group_by doesn't leak memory with a block
run { MyModel.all.group_by {|my_model| my_model.id } }
puts "5) No Leaks: #{list_objects}"
 
# But it does leak if block is #to_proc
run { MyModel.all.group_by(&:id) }
puts "6) Leaks memory: #{list_objects}"
 
# We can't reclaim this memory back :(
100.times { Thread.new { GC.start }.join }
puts "7) Still leaking memory: #{list_objects}"
 
# We also usually map for associations on our code
# This also works fine if using a block
run { MyAssociation.all.map {|my_association| my_association.my_model } }
puts "8) Doesn't leak more memory: #{list_objects}"
 
# But it leaks more memory when using #to_proc
run { MyAssociation.all.map(&:my_model) }
puts "9) Leaks more memory: #{list_objects}"
 
# We can't reclaim this memory either
100.times { Thread.new { GC.start }.join }
puts "10) Still leaking memory: #{list_objects}"
 
# Calling AR::Relation#reset doesn't help
run { ObjectSpace.each_object(ActiveRecord::Relation) {|r| r.reset} } if defined? ActiveRecord
puts "11) Still leaking memory: #{list_objects}"
 
# Final results
GC.start
Thread.new { GC.start }
sleep 1
Thread.new { GC.start; sleep 1 }.join
puts "Final) #{list_objects}"
results on multiple rubies and rails
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
[rbx-head]~/Projects/memory_leak % rails r lib/leak.rb
1) No leaks: MyModel -> 0, MyAssociation -> 1 (1 is fine, 2+ is leak)
2) No leaks: MyModel -> 0, MyAssociation -> 1 (1 is fine, 2+ is leak)
3) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
4) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
5) No Leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
6) Leaks memory: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
7) Still leaking memory: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
8) Doesn't leak more memory: MyModel -> 100, MyAssociation -> 100 (1 is fine, 2+ is leak)
9) Leaks more memory: MyModel -> 100, MyAssociation -> 9 (1 is fine, 2+ is leak)
10) Still leaking memory: MyModel -> 100, MyAssociation -> 9 (1 is fine, 2+ is leak)
11) Still leaking memory: MyModel -> 100, MyAssociation -> 9 (1 is fine, 2+ is leak)
Final) MyModel -> 100, MyAssociation -> 9 (1 is fine, 2+ is leak)
 
[rbx-head]~/Projects/memory_leak % ruby lib/leak.rb
1) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
2) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
3) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
4) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
5) No Leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
6) Leaks memory: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
7) Still leaking memory: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
8) Doesn't leak more memory: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
9) Leaks more memory: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
10) Still leaking memory: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
11) Still leaking memory: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
Final) MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
 
[rbx-head]~/Projects/memory_leak % rvm use 1.9.3
Using /Users/mtoledo/.rvm/gems/ruby-1.9.3-p194
 
[ruby-1.9.3-p194]~/Projects/memory_leak % rails r lib/leak.rb
1) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
2) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
3) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
4) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
5) No Leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
6) Leaks memory: MyModel -> 100, MyAssociation -> 0 (1 is fine, 2+ is leak)
7) Still leaking memory: MyModel -> 100, MyAssociation -> 0 (1 is fine, 2+ is leak)
8) Doesn't leak more memory: MyModel -> 100, MyAssociation -> 0 (1 is fine, 2+ is leak)
9) Leaks more memory: MyModel -> 200, MyAssociation -> 100 (1 is fine, 2+ is leak)
10) Still leaking memory: MyModel -> 200, MyAssociation -> 100 (1 is fine, 2+ is leak)
11) Still leaking memory: MyModel -> 200, MyAssociation -> 100 (1 is fine, 2+ is leak)
Final) MyModel -> 200, MyAssociation -> 100 (1 is fine, 2+ is leak)
 
[ruby-1.9.3-p194]~/Projects/memory_leak % ruby lib/leak.rb
1) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
2) No leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
3) No leaks: MyModel -> 0, MyAssociation -> 1 (1 is fine, 2+ is leak)
4) No leaks: MyModel -> 0, MyAssociation -> 1 (1 is fine, 2+ is leak)
5) No Leaks: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
6) Leaks memory: MyModel -> 1, MyAssociation -> 0 (1 is fine, 2+ is leak)
7) Still leaking memory: MyModel -> 1, MyAssociation -> 0 (1 is fine, 2+ is leak)
8) Doesn't leak more memory: MyModel -> 1, MyAssociation -> 1 (1 is fine, 2+ is leak)
9) Leaks more memory: MyModel -> 1, MyAssociation -> 1 (1 is fine, 2+ is leak)
10) Still leaking memory: MyModel -> 1, MyAssociation -> 1 (1 is fine, 2+ is leak)
11) Still leaking memory: MyModel -> 1, MyAssociation -> 1 (1 is fine, 2+ is leak)
Final) MyModel -> 1, MyAssociation -> 1 (1 is fine, 2+ is leak)
 
[ruby-1.9.3-p194]~/Projects/memory_leak % rvm use 1.8.7
Using /Users/mtoledo/.rvm/gems/ruby-1.8.7-p370
 
[ruby-1.8.7-p370]~/Projects/memory_leak % ruby lib/leak.rb
1) No leaks: MyModel -> 0, MyAssociation -> 1 (1 is fine, 2+ is leak)
2) No leaks: MyModel -> 0, MyAssociation -> 2 (1 is fine, 2+ is leak)
3) No leaks: MyModel -> 0, MyAssociation -> 2 (1 is fine, 2+ is leak)
4) No leaks: MyModel -> 0, MyAssociation -> 2 (1 is fine, 2+ is leak)
5) No Leaks: MyModel -> 1, MyAssociation -> 1 (1 is fine, 2+ is leak)
6) Leaks memory: MyModel -> 2, MyAssociation -> 1 (1 is fine, 2+ is leak)
7) Still leaking memory: MyModel -> 1, MyAssociation -> 0 (1 is fine, 2+ is leak)
8) Doesn't leak more memory: MyModel -> 1000, MyAssociation -> 1 (1 is fine, 2+ is leak)
9) Leaks more memory: MyModel -> 2000, MyAssociation -> 2 (1 is fine, 2+ is leak)
10) Still leaking memory: MyModel -> 1000, MyAssociation -> 1 (1 is fine, 2+ is leak)
11) Still leaking memory: MyModel -> 0, MyAssociation -> 1 (1 is fine, 2+ is leak)
Final) MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
 
[ruby-1.8.7-p370]~/Projects/memory_leak % rvm use jruby
Using /Users/mtoledo/.rvm/gems/jruby-1.6.7.2
 
[jruby-1.6.7.2]~/Projects/memory_leak % ruby -X+O lib/leak.rb
1) No leaks: MyModel -> 1, MyAssociation -> 1 (1 is fine, 2+ is leak)
2) No leaks: MyModel -> 2, MyAssociation -> 2 (1 is fine, 2+ is leak)
3) No leaks: MyModel -> 3, MyAssociation -> 3 (1 is fine, 2+ is leak)
4) No leaks: MyModel -> 4, MyAssociation -> 4 (1 is fine, 2+ is leak)
5) No Leaks: MyModel -> 5, MyAssociation -> 4 (1 is fine, 2+ is leak)
6) Leaks memory: MyModel -> 6, MyAssociation -> 4 (1 is fine, 2+ is leak)
7) Still leaking memory: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
8) Doesn't leak more memory: MyModel -> 1000, MyAssociation -> 1 (1 is fine, 2+ is leak)
9) Leaks more memory: MyModel -> 2000, MyAssociation -> 2 (1 is fine, 2+ is leak)
10) Still leaking memory: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
11) Still leaking memory: MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)
Final) MyModel -> 0, MyAssociation -> 0 (1 is fine, 2+ is leak)

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.