Skip to content

Commit e71fa2e

Browse files
committed
Do not create threads in finalisers
1 parent 6c4df24 commit e71fa2e

File tree

1 file changed

+31
-25
lines changed

1 file changed

+31
-25
lines changed

lib/concurrent/atomic/ruby_thread_local_var.rb

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,32 @@ class RubyThreadLocalVar < AbstractThreadLocalVar
3333
LOCK = Mutex.new
3434
ARRAYS = {} # used as a hash set
3535
@@next = 0
36-
private_constant :FREE, :LOCK, :ARRAYS
36+
QUEUE = Queue.new
37+
THREAD = Thread.new do
38+
while true
39+
method, i = QUEUE.pop
40+
case method
41+
when :thread_local_finalizer
42+
LOCK.synchronize do
43+
FREE.push(i)
44+
# The cost of GC'ing a TLV is linear in the number of threads using TLVs
45+
# But that is natural! More threads means more storage is used per TLV
46+
# So naturally more CPU time is required to free more storage
47+
ARRAYS.each_value do |array|
48+
array[i] = nil
49+
end
50+
end
51+
when :thread_finalizer
52+
LOCK.synchronize do
53+
# The thread which used this thread-local array is now gone
54+
# So don't hold onto a reference to the array (thus blocking GC)
55+
ARRAYS.delete(i)
56+
end
57+
end
58+
end
59+
end
60+
61+
private_constant :FREE, :LOCK, :ARRAYS, :QUEUE, :THREAD
3762

3863
# @!macro thread_local_var_method_get
3964
def value
@@ -77,37 +102,18 @@ def allocate_storage
77102
result
78103
end
79104
end
80-
ObjectSpace.define_finalizer(self, self.class.threadlocal_finalizer(@index))
105+
ObjectSpace.define_finalizer(self, self.class.thread_local_finalizer(@index))
81106
end
82107

83108
# @!visibility private
84-
def self.threadlocal_finalizer(index)
85-
proc do
86-
Thread.new(index) do |index| # avoid error: can't be called from trap context
87-
LOCK.synchronize do
88-
FREE.push(index)
89-
# The cost of GC'ing a TLV is linear in the number of threads using TLVs
90-
# But that is natural! More threads means more storage is used per TLV
91-
# So naturally more CPU time is required to free more storage
92-
ARRAYS.each_value do |array|
93-
array[index] = nil
94-
end
95-
end
96-
end
97-
end
109+
def self.thread_local_finalizer(index)
110+
proc { QUEUE.push [:thread_local_finalizer, index] }
98111
end
99112

100113
# @!visibility private
101114
def self.thread_finalizer(id)
102-
proc do
103-
Thread.new(id) do |id| # avoid error: can't be called from trap context
104-
LOCK.synchronize do
105-
# The thread which used this thread-local array is now gone
106-
# So don't hold onto a reference to the array (thus blocking GC)
107-
ARRAYS.delete(id)
108-
end
109-
end
110-
end
115+
# avoid error: can't be called from trap context
116+
proc { QUEUE.push [:thread_finalizer, id] }
111117
end
112118

113119
private

0 commit comments

Comments
 (0)