@@ -28,38 +28,27 @@ class RubyThreadLocalVar < AbstractThreadLocalVar
2828 # But when a Thread is GC'd, we need to drop the reference to its thread-local
2929 # array, so we don't leak memory
3030
31- # @!visibility private
32- FREE = [ ]
33- LOCK = Mutex . new
34- ARRAYS = { } # used as a hash set
35- # noinspection RubyClassVariableUsageInspection
36- @@next = 0
37- QUEUE = Queue . new
38- THREAD = Thread . new do
39- while true
40- method , i = QUEUE . pop
41- case method
42- when :thread_local_finalizer
43- LOCK . synchronize do
44- FREE . push ( i )
45- # The cost of GC'ing a TLV is linear in the number of threads using TLVs
46- # But that is natural! More threads means more storage is used per TLV
47- # So naturally more CPU time is required to free more storage
48- ARRAYS . each_value do |array |
49- array [ i ] = nil
50- end
51- end
52- when :thread_finalizer
53- LOCK . synchronize do
54- # The thread which used this thread-local array is now gone
55- # So don't hold onto a reference to the array (thus blocking GC)
56- ARRAYS . delete ( i )
57- end
58- end
31+ FREE = [ ]
32+ LOCK = Mutex . new
33+ THREAD_LOCAL_ARRAYS = { } # used as a hash set
34+
35+ # synchronize when not on MRI
36+ # on MRI using lock in finalizer leads to "can't be called from trap context" error
37+ # so the code is carefully written to be tread-safe on MRI relying on GIL
38+
39+ if Concurrent . on_cruby?
40+ # @!visibility private
41+ def self . semi_sync ( &block )
42+ block . call
43+ end
44+ else
45+ # @!visibility private
46+ def self . semi_sync ( &block )
47+ LOCK . synchronize ( &block )
5948 end
6049 end
6150
62- private_constant :FREE , :LOCK , :ARRAYS , :QUEUE , :THREAD
51+ private_constant :FREE , :LOCK , :THREAD_LOCAL_ARRAYS
6352
6453 # @!macro thread_local_var_method_get
6554 def value
@@ -85,7 +74,7 @@ def value=(value)
8574 # Using Ruby's built-in thread-local storage is faster
8675 unless ( array = get_threadlocal_array ( me ) )
8776 array = set_threadlocal_array ( [ ] , me )
88- LOCK . synchronize { ARRAYS [ array . object_id ] = array }
77+ self . class . semi_sync { THREAD_LOCAL_ARRAYS [ array . object_id ] = array }
8978 ObjectSpace . define_finalizer ( me , self . class . thread_finalizer ( array . object_id ) )
9079 end
9180 array [ @index ] = ( value . nil? ? NULL : value )
@@ -95,32 +84,50 @@ def value=(value)
9584 protected
9685
9786 # @!visibility private
98- # noinspection RubyClassVariableUsageInspection
9987 def allocate_storage
100- @index = LOCK . synchronize do
101- FREE . pop || begin
102- result = @@next
103- @@next += 1
104- result
105- end
106- end
88+ @index = FREE . pop || next_index
89+
10790 ObjectSpace . define_finalizer ( self , self . class . thread_local_finalizer ( @index ) )
10891 end
10992
11093 # @!visibility private
11194 def self . thread_local_finalizer ( index )
112- # avoid error: can't be called from trap context
113- proc { QUEUE . push [ :thread_local_finalizer , index ] }
95+ proc do
96+ semi_sync do
97+ # The cost of GC'ing a TLV is linear in the number of threads using TLVs
98+ # But that is natural! More threads means more storage is used per TLV
99+ # So naturally more CPU time is required to free more storage
100+ THREAD_LOCAL_ARRAYS . each_value { |array | array [ index ] = nil }
101+ # free index has to be published after the arrays are cleared
102+ FREE . push ( index )
103+ end
104+ end
114105 end
115106
116107 # @!visibility private
117108 def self . thread_finalizer ( id )
118- # avoid error: can't be called from trap context
119- proc { QUEUE . push [ :thread_finalizer , id ] }
109+ proc do
110+ semi_sync do
111+ # The thread which used this thread-local array is now gone
112+ # So don't hold onto a reference to the array (thus blocking GC)
113+ THREAD_LOCAL_ARRAYS . delete ( id )
114+ end
115+ end
120116 end
121117
122118 private
123119
120+ # noinspection RubyClassVariableUsageInspection
121+ @@next = 0
122+ # noinspection RubyClassVariableUsageInspection
123+ def next_index
124+ LOCK . synchronize do
125+ result = @@next
126+ @@next += 1
127+ result
128+ end
129+ end
130+
124131 if Thread . instance_methods . include? ( :thread_variable_get )
125132
126133 def get_threadlocal_array ( thread = Thread . current )
0 commit comments