File tree Expand file tree Collapse file tree 4 files changed +99
-9
lines changed
lib/concurrent-ruby/concurrent Expand file tree Collapse file tree 4 files changed +99
-9
lines changed Original file line number Diff line number Diff line change @@ -19,20 +19,27 @@ module Concurrent
19
19
#
20
20
# @see http://ruby-doc.org/stdlib-2.4.0/libdoc/set/rdoc/Set.html Ruby standard library `Set`
21
21
22
-
23
22
# @!macro internal_implementation_note
24
23
SetImplementation = case
25
24
when Concurrent . on_cruby?
26
- # Because MRI never runs code in parallel, the existing
27
- # non-thread-safe structures should usually work fine.
28
- ::Set
25
+ # The CRuby implementation of Set is written in Ruby itself and is
26
+ # not thread safe for certain methods.
27
+ require 'monitor'
28
+ require 'concurrent/thread_safe/util/data_structures'
29
+
30
+ class CRubySet < ::Set
31
+ end
32
+
33
+ ThreadSafe ::Util . make_synchronized_on_cruby CRubySet
34
+ CRubySet
29
35
30
36
when Concurrent . on_jruby?
31
37
require 'jruby/synchronized'
32
38
33
39
class JRubySet < ::Set
34
40
include JRuby ::Synchronized
35
41
end
42
+
36
43
JRubySet
37
44
38
45
when Concurrent . on_rbx?
@@ -41,7 +48,8 @@ class JRubySet < ::Set
41
48
42
49
class RbxSet < ::Set
43
50
end
44
- ThreadSafe ::Util . make_synchronized_on_rbx Concurrent ::RbxSet
51
+
52
+ ThreadSafe ::Util . make_synchronized_on_rbx RbxSet
45
53
RbxSet
46
54
47
55
when Concurrent . on_truffleruby?
@@ -50,7 +58,7 @@ class RbxSet < ::Set
50
58
class TruffleRubySet < ::Set
51
59
end
52
60
53
- ThreadSafe ::Util . make_synchronized_on_truffleruby Concurrent :: TruffleRubySet
61
+ ThreadSafe ::Util . make_synchronized_on_truffleruby TruffleRubySet
54
62
TruffleRubySet
55
63
56
64
else
Original file line number Diff line number Diff line change @@ -12,12 +12,37 @@ def self.synchronized(object, &block)
12
12
module Concurrent
13
13
module ThreadSafe
14
14
module Util
15
+ def self . make_synchronized_on_cruby ( klass )
16
+ klass . class_eval do
17
+ def initialize ( *args , &block )
18
+ @_monitor = Monitor . new
19
+ super
20
+ end
21
+
22
+ def initialize_copy ( other )
23
+ # make sure a copy is not sharing a monitor with the original object!
24
+ @_monitor = Monitor . new
25
+ super
26
+ end
27
+ end
28
+
29
+ klass . superclass . instance_methods ( false ) . each do |method |
30
+ klass . class_eval <<-RUBY , __FILE__ , __LINE__ + 1
31
+ def #{ method } (*args)
32
+ monitor = @_monitor
33
+ monitor or raise("BUG: Internal monitor was not properly initialized. Please report this to the concurrent-ruby developers.")
34
+ monitor.synchronize { super }
35
+ end
36
+ RUBY
37
+ end
38
+ end
39
+
15
40
def self . make_synchronized_on_rbx ( klass )
16
41
klass . class_eval do
17
42
private
18
43
19
44
def _mon_initialize
20
- @_monitor = Monitor . new unless @_monitor # avoid double initialisation
45
+ @_monitor || = Monitor . new # avoid double initialisation
21
46
end
22
47
23
48
def self . new ( *args )
Original file line number Diff line number Diff line change 1
1
module Concurrent
2
- VERSION = '1.1.8'
2
+ VERSION = '1.1.8'
3
3
end
Original file line number Diff line number Diff line change @@ -42,7 +42,7 @@ module Concurrent
42
42
end
43
43
44
44
context 'concurrency' do
45
- it do
45
+ it '#add and #delete' do
46
46
( 1 ..Concurrent ::ThreadSafe ::Test ::THREADS ) . map do |i |
47
47
in_thread do
48
48
1000 . times do
@@ -55,6 +55,63 @@ module Concurrent
55
55
end . map ( &:join )
56
56
expect ( set ) . to be_empty
57
57
end
58
+
59
+ it 'force context switch' do
60
+ barrier = Concurrent ::CyclicBarrier . new ( 2 )
61
+
62
+ # methods like include? or delete? are implemented for CRuby in Ruby itself
63
+ # @see https://github.com/ruby/ruby/blob/master/lib/set.rb
64
+ set . clear
65
+
66
+ # add a single element
67
+ set . add ( 1 )
68
+
69
+ # This thread should start and `Set#reject!` in CRuby should cache a value of `0` for size
70
+ thread_reject = in_thread do
71
+ # we expect this to return nil since nothing should have changed.
72
+ expect ( set . reject! do |v |
73
+ barrier . wait
74
+ v == 1 # only delete the 1 value
75
+ end ) . to eq set
76
+ end
77
+
78
+ thread_add = in_thread do
79
+ barrier . wait
80
+ expect ( set . add? ( 1 ) ) . to eq set
81
+ end
82
+
83
+ join_with [ thread_reject , thread_add ]
84
+ end
85
+
86
+ it '#each' do
87
+ threads = [ ]
88
+ ( "a" .."z" ) . inject ( set , &:<< ) # setup a non-empty set
89
+
90
+ threads << in_thread do
91
+ 2000 . times do
92
+ size = nil
93
+ set . each do |member |
94
+ if size . nil?
95
+ size = set . length
96
+ else
97
+ expect ( set . length ) . to eq ( size )
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ threads += ( 1 ..19 ) . map do |i |
104
+ in_thread do
105
+ v = i * 1000
106
+ 10 . times do
107
+ 200 . times { |j | set << ( v +j ) }
108
+ 200 . times { |j | set . delete ( v +j ) }
109
+ end
110
+ end
111
+ end
112
+
113
+ threads . map ( &:join )
114
+ end
58
115
end
59
116
end
60
117
end
You can’t perform that action at this time.
0 commit comments