File tree Expand file tree Collapse file tree 4 files changed +53
-52
lines changed Expand file tree Collapse file tree 4 files changed +53
-52
lines changed Original file line number Diff line number Diff line change 5
5
group :development do
6
6
gem 'rake' , '~> 10.3.2'
7
7
gem 'rake-compiler' , '~> 0.9.2'
8
+ gem 'ref' , '~> 1.0.5'
8
9
end
9
10
10
11
group :testing do
Original file line number Diff line number Diff line change 2
2
3
3
module Concurrent
4
4
5
- module ThreadLocalRubyStorage
5
+ class AbstractThreadLocalVar
6
6
7
- protected
7
+ module ThreadLocalRubyStorage
8
8
9
- def allocate_storage
10
- @storage = Atomic . new Hash . new
11
- end
9
+ protected
12
10
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
16
17
17
- def set ( value , &block )
18
- key = Thread . current . object_id
18
+ def allocate_storage
19
+ @storage = Ref ::WeakKeyMap . new
20
+ end
19
21
20
- @storage . update do | s |
21
- s . merge ( key => value )
22
+ def get
23
+ @storage [ Thread . current ]
22
24
end
23
25
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
32
28
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
36
37
end
37
38
end
38
39
end
39
40
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
49
42
50
- module ThreadLocalJavaStorage
43
+ protected
51
44
52
- protected
45
+ def allocate_storage
46
+ @var = java . lang . ThreadLocal . new
47
+ end
53
48
54
- def allocate_storage
55
- @var = java . lang . ThreadLocal . new
56
- end
49
+ def get
50
+ @var . get
51
+ end
57
52
58
- def get
59
- @var . get
60
- end
53
+ def set ( value )
54
+ @var . set ( value )
55
+ end
61
56
62
- def set ( value )
63
- @var . set ( value )
64
57
end
65
58
66
- end
67
-
68
- class AbstractThreadLocalVar
69
-
70
59
NIL_SENTINEL = Object . new
71
60
72
61
def initialize ( default = nil )
@@ -86,7 +75,6 @@ def value
86
75
end
87
76
end
88
77
89
- # may leak the value, #bind is preferred
90
78
def value = ( value )
91
79
bind value
92
80
end
Original file line number Diff line number Diff line change 7
7
require 'concurrent/atomic/cyclic_barrier'
8
8
require 'concurrent/atomic/count_down_latch'
9
9
require 'concurrent/atomic/event'
10
- require 'concurrent/atomic/thread_local_var'
11
10
require 'concurrent/atomic/synchronization'
Original file line number Diff line number Diff line change 3
3
4
4
module Concurrent
5
5
6
- ThreadLocalRubyStorage . i_know_it_may_leak_values!
6
+ require 'concurrent/atomic/thread_local_var'
7
7
8
8
describe ThreadLocalVar do
9
9
@@ -31,11 +31,11 @@ module Concurrent
31
31
32
32
if jruby?
33
33
it 'uses ThreadLocalJavaStorage' do
34
- expect ( subject . class . ancestors ) . to include ( Concurrent ::ThreadLocalJavaStorage )
34
+ expect ( subject . class . ancestors ) . to include ( Concurrent ::AbstractThreadLocalVar :: ThreadLocalJavaStorage )
35
35
end
36
36
else
37
37
it 'uses ThreadLocalNewStorage' do
38
- expect ( subject . class . ancestors ) . to include ( Concurrent ::ThreadLocalRubyStorage )
38
+ expect ( subject . class . ancestors ) . to include ( Concurrent ::AbstractThreadLocalVar :: ThreadLocalRubyStorage )
39
39
end
40
40
end
41
41
end
@@ -48,7 +48,20 @@ module Concurrent
48
48
Thread . new { var . bind ( i ) { var . value } }
49
49
end . each ( &:join )
50
50
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
52
65
end
53
66
end
54
67
end
You can’t perform that action at this time.
0 commit comments