Created
August 7, 2012 18:09
-
-
Save mboeh/3287930 to your computer and use it in GitHub Desktop.
thread and GC nuance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Summary: In MRI 1.9, if you keep threads in an array in a local variable, the threads may outlive the local variable. | |
# I have not yet been able to produce a scenario in which the threads are ever garbage-collected. | |
# You have to remove the thread from the array before it goes out of scope, e.g. using #pop or #clear. | |
# INPUT DESIRED: If anyone can demonstrate a condition in which a local array keeps threads and those threads are eventually GC'd. | |
# INPUT DESIRED: Is this expected behavior or a bug? | |
# NOTE: This test will not work as expected in JRuby, because its garbage collection works differently. Whether the same behavior exists in JRuby is an exercise for someone smarter than I am. | |
class Foo | |
THREAD_PROC = lambda{ (0..10).to_a.map do Foo.new end } | |
def self.gc_bench(label) | |
before = ObjectSpace.each_object(Thread).count | |
yield | |
after = ObjectSpace.each_object(Thread).count | |
GC.start | |
gcd = ObjectSpace.each_object(Thread).count | |
puts "#{label}: #{before} -> #{after} -> #{gcd} (#{gcd - after})" | |
end | |
def self.do_it_one_local | |
gc_bench "local array" do | |
threads = 10.times.map do | |
Thread.new &THREAD_PROC | |
end | |
threads.each(&:join) | |
threads = nil | |
end | |
end | |
def self.do_it_one_local_clear | |
gc_bench "local array, use #clear" do | |
threads = 10.times.map do | |
Thread.new &THREAD_PROC | |
end | |
threads.each(&:join) | |
threads.clear | |
end | |
end | |
def self.do_it_one_local_pops | |
gc_bench "local array, use many #pops" do | |
threads = 10.times.map do | |
Thread.new &THREAD_PROC | |
end | |
threads.each(&:join) | |
:spin while threads.pop | |
end | |
end | |
def self.do_it_many_locals | |
gc_bench "many locals" do | |
thread0 = Thread.new &THREAD_PROC | |
thread1 = Thread.new &THREAD_PROC | |
thread2 = Thread.new &THREAD_PROC | |
thread3 = Thread.new &THREAD_PROC | |
thread4 = Thread.new &THREAD_PROC | |
thread5 = Thread.new &THREAD_PROC | |
thread6 = Thread.new &THREAD_PROC | |
thread7 = Thread.new &THREAD_PROC | |
thread8 = Thread.new &THREAD_PROC | |
thread9 = Thread.new &THREAD_PROC | |
(0..9).each do |i| | |
eval "thread#{i}.join" | |
end | |
end | |
end | |
end | |
Foo.do_it_one_local # Leaks 10 threads | |
Foo.do_it_many_locals # Leaks no threads | |
Foo.do_it_one_local_clear # Leaks no threads | |
Foo.do_it_one_local_pops # Leaks no threads |
1.9.3, wrapping do_it_one_local in 10.times do ... end:
local array: 1 -> 11 -> 11 (0)
local array: 11 -> 21 -> 11 (-10)
local array: 11 -> 21 -> 11 (-10)
local array: 11 -> 21 -> 11 (-10)
local array: 11 -> 21 -> 11 (-10)
local array: 11 -> 21 -> 11 (-10)
local array: 11 -> 21 -> 11 (-10)
local array: 11 -> 21 -> 11 (-10)
local array: 11 -> 21 -> 11 (-10)
local array: 11 -> 21 -> 11 (-10)
many locals: 11 -> 21 -> 11 (-10)
local array, use #clear: 11 -> 21 -> 11 (-10)
local array, use many #pops: 11 -> 21 -> 11 (-10)
???
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Interesting note:
Ruby 1.8.7 behaves correctly for the array but doesn't manage to clean up the many locals test until the next test.