@@ -32,12 +32,38 @@ class RubyThreadLocalVar < AbstractThreadLocalVar
3232 FREE = [ ]
3333 LOCK = Mutex . new
3434 ARRAYS = { } # used as a hash set
35+ # noinspection RubyClassVariableUsageInspection
3536 @@next = 0
36- private_constant :FREE , :LOCK , :ARRAYS
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
59+ end
60+ end
61+
62+ private_constant :FREE , :LOCK , :ARRAYS , :QUEUE , :THREAD
3763
3864 # @!macro thread_local_var_method_get
3965 def value
40- if array = get_threadlocal_array
66+ if ( array = get_threadlocal_array )
4167 value = array [ @index ]
4268 if value . nil?
4369 default
@@ -57,10 +83,10 @@ def value=(value)
5783 # We could keep the thread-local arrays in a hash, keyed by Thread
5884 # But why? That would require locking
5985 # Using Ruby's built-in thread-local storage is faster
60- unless array = get_threadlocal_array ( me )
86+ unless ( array = get_threadlocal_array ( me ) )
6187 array = set_threadlocal_array ( [ ] , me )
6288 LOCK . synchronize { ARRAYS [ array . object_id ] = array }
63- ObjectSpace . define_finalizer ( me , self . class . thread_finalizer ( array ) )
89+ ObjectSpace . define_finalizer ( me , self . class . thread_finalizer ( array . object_id ) )
6490 end
6591 array [ @index ] = ( value . nil? ? NULL : value )
6692 value
@@ -69,6 +95,7 @@ def value=(value)
6995 protected
7096
7197 # @!visibility private
98+ # noinspection RubyClassVariableUsageInspection
7299 def allocate_storage
73100 @index = LOCK . synchronize do
74101 FREE . pop || begin
@@ -77,37 +104,19 @@ def allocate_storage
77104 result
78105 end
79106 end
80- ObjectSpace . define_finalizer ( self , self . class . threadlocal_finalizer ( @index ) )
107+ ObjectSpace . define_finalizer ( self , self . class . thread_local_finalizer ( @index ) )
81108 end
82109
83110 # @!visibility private
84- def self . threadlocal_finalizer ( index )
85- proc do
86- Thread . new do # 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
111+ def self . thread_local_finalizer ( index )
112+ # avoid error: can't be called from trap context
113+ proc { QUEUE . push [ :thread_local_finalizer , index ] }
98114 end
99115
100116 # @!visibility private
101- def self . thread_finalizer ( array )
102- proc do
103- Thread . new do # 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 ( array . object_id )
108- end
109- end
110- end
117+ def self . thread_finalizer ( id )
118+ # avoid error: can't be called from trap context
119+ proc { QUEUE . push [ :thread_finalizer , id ] }
111120 end
112121
113122 private
@@ -136,21 +145,22 @@ def set_threadlocal_array(array, thread = Thread.current)
136145 # This exists only for use in testing
137146 # @!visibility private
138147 def value_for ( thread )
139- if array = get_threadlocal_array ( thread )
148+ if ( array = get_threadlocal_array ( thread ) )
140149 value = array [ @index ]
141150 if value . nil?
142- default_for ( thread )
151+ get_default
143152 elsif value . equal? ( NULL )
144153 nil
145154 else
146155 value
147156 end
148157 else
149- default_for ( thread )
158+ get_default
150159 end
151160 end
152161
153- def default_for ( thread )
162+ # @!visibility private
163+ def get_default
154164 if @default_block
155165 raise "Cannot use default_for with default block"
156166 else
0 commit comments