Skip to content

Commit f073cac

Browse files
committed
Remove JRuby-specific implementation of ThreadLocalVar
...because the pure-Ruby implementation is faster anyways!
1 parent 863cb51 commit f073cac

File tree

2 files changed

+37
-184
lines changed

2 files changed

+37
-184
lines changed

lib/concurrent/atomic/thread_local_var.rb

Lines changed: 37 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -33,91 +33,7 @@ module Concurrent
3333
#
3434
# @see https://docs.oracle.com/javase/7/docs/api/java/lang/ThreadLocal.html Java ThreadLocal
3535
#
36-
# @!visibility private
37-
class AbstractThreadLocalVar
38-
39-
# @!visibility private
40-
NIL_SENTINEL = Object.new
41-
private_constant :NIL_SENTINEL
42-
43-
# @!macro [attach] thread_local_var_method_initialize
44-
#
45-
# Creates a thread local variable.
46-
#
47-
# @param [Object] default the default value when otherwise unset
48-
def initialize(default = nil)
49-
@default = default
50-
allocate_storage
51-
end
52-
53-
# @!macro [attach] thread_local_var_method_get
54-
#
55-
# Returns the value in the current thread's copy of this thread-local variable.
56-
#
57-
# @return [Object] the current value
58-
def value
59-
value = get
60-
61-
if value.nil?
62-
@default
63-
elsif value == NIL_SENTINEL
64-
nil
65-
else
66-
value
67-
end
68-
end
69-
70-
# @!macro [attach] thread_local_var_method_set
71-
#
72-
# Sets the current thread's copy of this thread-local variable to the specified value.
73-
#
74-
# @param [Object] value the value to set
75-
# @return [Object] the new value
76-
def value=(value)
77-
bind value
78-
end
79-
80-
# @!macro [attach] thread_local_var_method_bind
81-
#
82-
# Bind the given value to thread local storage during
83-
# execution of the given block.
84-
#
85-
# @param [Object] value the value to bind
86-
# @yield the operation to be performed with the bound variable
87-
# @return [Object] the value
88-
def bind(value, &block)
89-
if value.nil?
90-
stored_value = NIL_SENTINEL
91-
else
92-
stored_value = value
93-
end
94-
95-
set(stored_value, &block)
96-
97-
value
98-
end
99-
100-
protected
101-
102-
# @!visibility private
103-
def allocate_storage
104-
raise NotImplementedError
105-
end
106-
107-
# @!visibility private
108-
def get
109-
raise NotImplementedError
110-
end
111-
112-
# @!visibility private
113-
def set(value)
114-
raise NotImplementedError
115-
end
116-
end
117-
118-
# @!visibility private
119-
# @!macro internal_implementation_note
120-
class RubyThreadLocalVar < AbstractThreadLocalVar
36+
class ThreadLocalVar
12137

12238
# Each thread has a (lazily initialized) array of thread-local variable values
12339
# Each time a new thread-local var is created, we allocate an "index" for it
@@ -140,40 +56,19 @@ class RubyThreadLocalVar < AbstractThreadLocalVar
14056
# But when a Thread is GC'd, we need to drop the reference to its thread-local
14157
# array, so we don't leak memory
14258

59+
# @!visibility private
60+
NIL_SENTINEL = Object.new
14361
FREE = []
14462
LOCK = Mutex.new
14563
ARRAYS = {} # used as a hash set
14664
@@next = 0
65+
private_constant :NIL_SENTINEL, :FREE, :LOCK, :ARRAYS
14766

148-
protected
149-
150-
# @!visibility private
151-
def self.threadlocal_finalizer(index)
152-
proc do
153-
LOCK.synchronize do
154-
FREE.push(index)
155-
# The cost of GC'ing a TLV is linear in the number of threads using TLVs
156-
# But that is natural! More threads means more storage is used per TLV
157-
# So naturally more CPU time is required to free more storage
158-
ARRAYS.each_value do |array|
159-
array[index] = nil
160-
end
161-
end
162-
end
163-
end
164-
165-
# @!visibility private
166-
def self.thread_finalizer(array)
167-
proc do
168-
LOCK.synchronize do
169-
# The thread which used this thread-local array is now gone
170-
# So don't hold onto a reference to the array (thus blocking GC)
171-
ARRAYS.delete(array.object_id)
172-
end
173-
end
174-
end
175-
176-
def allocate_storage
67+
# Creates a thread local variable.
68+
#
69+
# @param [Object] default the default value when otherwise unset
70+
def initialize(default = nil)
71+
@default = default
17772
@index = LOCK.synchronize do
17873
FREE.pop || begin
17974
result = @@next
@@ -184,10 +79,6 @@ def allocate_storage
18479
ObjectSpace.define_finalizer(self, self.class.threadlocal_finalizer(@index))
18580
end
18681

187-
public
188-
189-
# @!macro [attach] thread_local_var_method_get
190-
#
19182
# Returns the value in the current thread's copy of this thread-local variable.
19283
#
19384
# @return [Object] the current value
@@ -206,8 +97,6 @@ def value
20697
end
20798
end
20899

209-
# @!macro [attach] thread_local_var_method_set
210-
#
211100
# Sets the current thread's copy of this thread-local variable to the specified value.
212101
#
213102
# @param [Object] value the value to set
@@ -226,8 +115,34 @@ def value=(value)
226115
value
227116
end
228117

229-
# @!macro [attach] thread_local_var_method_bind
230-
#
118+
protected
119+
120+
# @!visibility private
121+
def self.threadlocal_finalizer(index)
122+
proc do
123+
LOCK.synchronize do
124+
FREE.push(index)
125+
# The cost of GC'ing a TLV is linear in the number of threads using TLVs
126+
# But that is natural! More threads means more storage is used per TLV
127+
# So naturally more CPU time is required to free more storage
128+
ARRAYS.each_value do |array|
129+
array[index] = nil
130+
end
131+
end
132+
end
133+
end
134+
135+
# @!visibility private
136+
def self.thread_finalizer(array)
137+
proc do
138+
LOCK.synchronize do
139+
# The thread which used this thread-local array is now gone
140+
# So don't hold onto a reference to the array (thus blocking GC)
141+
ARRAYS.delete(array.object_id)
142+
end
143+
end
144+
end
145+
231146
# Bind the given value to thread local storage during
232147
# execution of the given block.
233148
#
@@ -246,56 +161,4 @@ def bind(value, &block)
246161
end
247162
end
248163
end
249-
250-
if Concurrent.on_jruby?
251-
252-
# @!visibility private
253-
# @!macro internal_implementation_note
254-
class JavaThreadLocalVar < AbstractThreadLocalVar
255-
256-
protected
257-
258-
# @!visibility private
259-
def allocate_storage
260-
@var = java.lang.ThreadLocal.new
261-
end
262-
263-
# @!visibility private
264-
def get
265-
@var.get
266-
end
267-
268-
# @!visibility private
269-
def set(value)
270-
@var.set(value)
271-
end
272-
end
273-
end
274-
275-
# @!visibility private
276-
# @!macro internal_implementation_note
277-
ThreadLocalVarImplementation = case
278-
when Concurrent.on_jruby?
279-
JavaThreadLocalVar
280-
else
281-
RubyThreadLocalVar
282-
end
283-
private_constant :ThreadLocalVarImplementation
284-
285-
# @!macro thread_local_var
286-
class ThreadLocalVar < ThreadLocalVarImplementation
287-
288-
# @!method initialize(default = nil)
289-
# @!macro thread_local_var_method_initialize
290-
291-
# @!method value
292-
# @!macro thread_local_var_method_get
293-
294-
# @!method value=(value)
295-
# @!macro thread_local_var_method_set
296-
297-
# @!method bind(value, &block)
298-
# @!macro thread_local_var_method_bind
299-
300-
end
301164
end

spec/concurrent/atomic/thread_local_var_spec.rb

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,6 @@ module Concurrent
2727
expect(t1.value).to eq 14
2828
expect(t2.value).to eq 14
2929
end
30-
31-
if Concurrent.on_jruby?
32-
it 'extends JavaThreadLocalVar' do
33-
expect(subject.class.ancestors).to include(Concurrent::JavaThreadLocalVar)
34-
end
35-
else
36-
it 'extends ThreadLocalNewStorage' do
37-
expect(subject.class.ancestors).to include(Concurrent::RubyThreadLocalVar)
38-
end
39-
end
4030
end
4131

4232
context '#value' do

0 commit comments

Comments
 (0)