Skip to content

Commit 7d33d7e

Browse files
committed
Fixing ThreadLocalVar to use 'ref' gem
ThreadLocalVar needs to be required manually.
1 parent da60120 commit 7d33d7e

File tree

4 files changed

+53
-52
lines changed

4 files changed

+53
-52
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ gemspec
55
group :development do
66
gem 'rake', '~> 10.3.2'
77
gem 'rake-compiler', '~> 0.9.2'
8+
gem 'ref', '~> 1.0.5'
89
end
910

1011
group :testing do

lib/concurrent/atomic/thread_local_var.rb

Lines changed: 35 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,71 +2,60 @@
22

33
module Concurrent
44

5-
module ThreadLocalRubyStorage
5+
class AbstractThreadLocalVar
66

7-
protected
7+
module ThreadLocalRubyStorage
88

9-
def allocate_storage
10-
@storage = Atomic.new Hash.new
11-
end
9+
protected
1210

13-
def get
14-
@storage.get[Thread.current.object_id]
15-
end
11+
begin
12+
require 'ref'
13+
rescue LoadError
14+
raise LoadError,
15+
'ThreadLocalVar requires ref gem installed on MRI to avoid memory leaks. It\'s not concurrent-ruby dependency.'
16+
end
1617

17-
def set(value, &block)
18-
key = Thread.current.object_id
18+
def allocate_storage
19+
@storage = Ref::WeakKeyMap.new
20+
end
1921

20-
@storage.update do |s|
21-
s.merge(key => value)
22+
def get
23+
@storage[Thread.current]
2224
end
2325

24-
if block_given?
25-
begin
26-
block.call
27-
ensure
28-
@storage.update do |s|
29-
s.clone.tap { |h| h.delete key }
30-
end
31-
end
26+
def set(value, &block)
27+
key = Thread.current
3228

33-
else
34-
unless ThreadLocalRubyStorage.i_know_it_may_leak_values?
35-
warn "it may leak values if used without block\n#{caller[0]}"
29+
@storage[key] = value
30+
31+
if block_given?
32+
begin
33+
block.call
34+
ensure
35+
@storage.delete key
36+
end
3637
end
3738
end
3839
end
3940

40-
def self.i_know_it_may_leak_values!
41-
@leak_acknowledged = true
42-
end
43-
44-
def self.i_know_it_may_leak_values?
45-
@leak_acknowledged
46-
end
47-
48-
end
41+
module ThreadLocalJavaStorage
4942

50-
module ThreadLocalJavaStorage
43+
protected
5144

52-
protected
45+
def allocate_storage
46+
@var = java.lang.ThreadLocal.new
47+
end
5348

54-
def allocate_storage
55-
@var = java.lang.ThreadLocal.new
56-
end
49+
def get
50+
@var.get
51+
end
5752

58-
def get
59-
@var.get
60-
end
53+
def set(value)
54+
@var.set(value)
55+
end
6156

62-
def set(value)
63-
@var.set(value)
6457
end
6558

66-
end
67-
68-
class AbstractThreadLocalVar
69-
7059
NIL_SENTINEL = Object.new
7160

7261
def initialize(default = nil)
@@ -86,7 +75,6 @@ def value
8675
end
8776
end
8877

89-
# may leak the value, #bind is preferred
9078
def value=(value)
9179
bind value
9280
end

lib/concurrent/atomics.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@
77
require 'concurrent/atomic/cyclic_barrier'
88
require 'concurrent/atomic/count_down_latch'
99
require 'concurrent/atomic/event'
10-
require 'concurrent/atomic/thread_local_var'
1110
require 'concurrent/atomic/synchronization'

spec/concurrent/atomic/thread_local_var_spec.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
module Concurrent
55

6-
ThreadLocalRubyStorage.i_know_it_may_leak_values!
6+
require 'concurrent/atomic/thread_local_var'
77

88
describe ThreadLocalVar do
99

@@ -31,11 +31,11 @@ module Concurrent
3131

3232
if jruby?
3333
it 'uses ThreadLocalJavaStorage' do
34-
expect(subject.class.ancestors).to include(Concurrent::ThreadLocalJavaStorage)
34+
expect(subject.class.ancestors).to include(Concurrent::AbstractThreadLocalVar::ThreadLocalJavaStorage)
3535
end
3636
else
3737
it 'uses ThreadLocalNewStorage' do
38-
expect(subject.class.ancestors).to include(Concurrent::ThreadLocalRubyStorage)
38+
expect(subject.class.ancestors).to include(Concurrent::AbstractThreadLocalVar::ThreadLocalRubyStorage)
3939
end
4040
end
4141
end
@@ -48,7 +48,20 @@ module Concurrent
4848
Thread.new { var.bind(i) { var.value } }
4949
end.each(&:join)
5050
var.value = 0
51-
expect(var.instance_variable_get(:@storage).get.size).to be == 1
51+
expect(var.instance_variable_get(:@storage).keys.size).to be == 1
52+
end
53+
54+
it 'does not leave values behind when bind is not used' do
55+
var = ThreadLocalVar.new(0)
56+
100.times.map do |i|
57+
Thread.new { var.value = i; var.value }
58+
end.each(&:join)
59+
var.value = 0
60+
sleep 0.1
61+
GC.start
62+
sleep 0.1
63+
GC.start
64+
expect(var.instance_variable_get(:@storage).keys.size).to be == 1
5265
end
5366
end
5467
end

0 commit comments

Comments
 (0)