@@ -28,38 +28,27 @@ class RubyThreadLocalVar < AbstractThreadLocalVar
28
28
# But when a Thread is GC'd, we need to drop the reference to its thread-local
29
29
# array, so we don't leak memory
30
30
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 )
59
48
end
60
49
end
61
50
62
- private_constant :FREE , :LOCK , :ARRAYS , :QUEUE , :THREAD
51
+ private_constant :FREE , :LOCK , :THREAD_LOCAL_ARRAYS
63
52
64
53
# @!macro thread_local_var_method_get
65
54
def value
@@ -85,7 +74,7 @@ def value=(value)
85
74
# Using Ruby's built-in thread-local storage is faster
86
75
unless ( array = get_threadlocal_array ( me ) )
87
76
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 }
89
78
ObjectSpace . define_finalizer ( me , self . class . thread_finalizer ( array . object_id ) )
90
79
end
91
80
array [ @index ] = ( value . nil? ? NULL : value )
@@ -95,32 +84,50 @@ def value=(value)
95
84
protected
96
85
97
86
# @!visibility private
98
- # noinspection RubyClassVariableUsageInspection
99
87
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
+
107
90
ObjectSpace . define_finalizer ( self , self . class . thread_local_finalizer ( @index ) )
108
91
end
109
92
110
93
# @!visibility private
111
94
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
114
105
end
115
106
116
107
# @!visibility private
117
108
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
120
116
end
121
117
122
118
private
123
119
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
+
124
131
if Thread . instance_methods . include? ( :thread_variable_get )
125
132
126
133
def get_threadlocal_array ( thread = Thread . current )
0 commit comments