Skip to content

Commit 3e0d64d

Browse files
authored
Merge pull request #734 from ivoanjo/fix-array-hash-set-constructors-truffleruby
Fix Array/Hash/Set construction broken on TruffleRuby
2 parents 0ee31d0 + bdcd61f commit 3e0d64d

File tree

7 files changed

+300
-48
lines changed

7 files changed

+300
-48
lines changed

lib/concurrent/array.rb

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
require 'concurrent/thread_safe/util'
33

44
module Concurrent
5-
if Concurrent.on_cruby?
5+
case
6+
when Concurrent.on_cruby?
67

78
# Because MRI never runs code in parallel, the existing
89
# non-thread-safe structures should usually work fine.
@@ -21,26 +22,40 @@ module Concurrent
2122
# may be lost. Use `#concat` instead.
2223
#
2324
# @see http://ruby-doc.org/core-2.2.0/Array.html Ruby standard library `Array`
24-
class Array < ::Array;
25+
class Array < ::Array
2526
end
2627

27-
elsif Concurrent.on_jruby?
28+
when Concurrent.on_jruby?
2829
require 'jruby/synchronized'
2930

3031
# @!macro concurrent_array
3132
class Array < ::Array
3233
include JRuby::Synchronized
3334
end
3435

35-
elsif Concurrent.on_rbx? || Concurrent.on_truffleruby?
36+
when Concurrent.on_rbx?
3637
require 'monitor'
37-
require 'concurrent/thread_safe/util/array_hash_rbx'
38+
require 'concurrent/thread_safe/util/data_structures'
3839

3940
# @!macro concurrent_array
4041
class Array < ::Array
4142
end
4243

4344
ThreadSafe::Util.make_synchronized_on_rbx Concurrent::Array
45+
46+
when Concurrent.on_truffleruby?
47+
require 'concurrent/thread_safe/util/data_structures'
48+
49+
# @!macro concurrent_array
50+
class Array < ::Array
51+
end
52+
53+
ThreadSafe::Util.make_synchronized_on_truffleruby Concurrent::Array
54+
55+
else
56+
warn 'Possibly unsupported Ruby implementation'
57+
class Array < ::Array
58+
end
4459
end
4560
end
4661

lib/concurrent/hash.rb

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
require 'concurrent/thread_safe/util'
33

44
module Concurrent
5-
if Concurrent.on_cruby?
5+
case
6+
when Concurrent.on_cruby?
67

78
# @!macro [attach] concurrent_hash
89
#
@@ -12,25 +13,41 @@ module Concurrent
1213
# which takes the lock repeatedly when reading an item.
1314
#
1415
# @see http://ruby-doc.org/core-2.2.0/Hash.html Ruby standard library `Hash`
15-
class Hash < ::Hash;
16+
class Hash < ::Hash
1617
end
1718

18-
elsif Concurrent.on_jruby?
19+
when Concurrent.on_jruby?
1920
require 'jruby/synchronized'
2021

2122
# @!macro concurrent_hash
2223
class Hash < ::Hash
2324
include JRuby::Synchronized
2425
end
2526

26-
elsif Concurrent.on_rbx? || Concurrent.on_truffleruby?
27+
when Concurrent.on_rbx?
2728
require 'monitor'
28-
require 'concurrent/thread_safe/util/array_hash_rbx'
29+
require 'concurrent/thread_safe/util/data_structures'
2930

3031
# @!macro concurrent_hash
3132
class Hash < ::Hash
3233
end
3334

3435
ThreadSafe::Util.make_synchronized_on_rbx Concurrent::Hash
36+
37+
when Concurrent.on_truffleruby?
38+
require 'concurrent/thread_safe/util/data_structures'
39+
40+
# @!macro concurrent_hash
41+
class Hash < ::Hash
42+
end
43+
44+
ThreadSafe::Util.make_synchronized_on_truffleruby Concurrent::Hash
45+
46+
else
47+
warn 'Possibly unsupported Ruby implementation'
48+
class Hash < ::Hash
49+
end
50+
3551
end
3652
end
53+

lib/concurrent/set.rb

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
require 'set'
44

55
module Concurrent
6-
if Concurrent.on_cruby?
6+
case
7+
when Concurrent.on_cruby?
78

89
# Because MRI never runs code in parallel, the existing
910
# non-thread-safe structures should usually work fine.
@@ -25,23 +26,37 @@ module Concurrent
2526
class Set < ::Set;
2627
end
2728

28-
elsif Concurrent.on_jruby?
29+
when Concurrent.on_jruby?
2930
require 'jruby/synchronized'
3031

3132
# @!macro concurrent_Set
3233
class Set < ::Set
3334
include JRuby::Synchronized
3435
end
3536

36-
elsif Concurrent.on_rbx? || Concurrent.on_truffleruby?
37+
when Concurrent.on_rbx?
3738
require 'monitor'
38-
require 'concurrent/thread_safe/util/array_hash_rbx'
39+
require 'concurrent/thread_safe/util/data_structures'
3940

4041
# @!macro concurrent_Set
4142
class Set < ::Set
4243
end
4344

44-
ThreadSafe::Util.make_synchronized_on_rbx Set
45+
ThreadSafe::Util.make_synchronized_on_rbx Concurrent::Set
46+
47+
when Concurrent.on_truffleruby?
48+
require 'concurrent/thread_safe/util/data_structures'
49+
50+
# @!macro concurrent_array
51+
class Set < ::Set
52+
end
53+
54+
ThreadSafe::Util.make_synchronized_on_truffleruby Concurrent::Set
55+
56+
else
57+
warn 'Possibly unsupported Ruby implementation'
58+
class Set < ::Set
59+
end
4560
end
4661
end
4762

lib/concurrent/thread_safe/util/array_hash_rbx.rb renamed to lib/concurrent/thread_safe/util/data_structures.rb

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ module Util
66
def self.make_synchronized_on_rbx(klass)
77
klass.class_eval do
88
private
9+
910
def _mon_initialize
1011
@_monitor = Monitor.new unless @_monitor # avoid double initialisation
1112
end
1213

13-
def self.new
14-
obj = super
14+
def self.new(*args)
15+
obj = super(*args)
1516
obj.send(:_mon_initialize)
1617
obj
1718
end
@@ -30,12 +31,25 @@ def #{method}(*args)
3031
else
3132
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
3233
def #{method}(*args)
33-
@_monitor.synchronize { super }
34+
monitor = @_monitor
35+
monitor or raise("BUG: Internal monitor was not properly initialized. Please report this to the concurrent-ruby developers.")
36+
monitor.synchronize { super }
3437
end
3538
RUBY
3639
end
3740
end
3841
end
42+
43+
def self.make_synchronized_on_truffleruby(klass)
44+
klass.superclass.instance_methods(false).each do |method|
45+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
46+
def #{method}(*args, &block)
47+
# TODO (pitr-ch 01-Jul-2018): don't use internal TruffleRuby APIs
48+
Truffle::System.synchronized(self) { super(*args, &block) }
49+
end
50+
RUBY
51+
end
52+
end
3953
end
4054
end
4155
end

spec/concurrent/array_spec.rb

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,87 @@ module Concurrent
22
RSpec.describe Array do
33
let!(:ary) { described_class.new }
44

5-
it 'concurrency' do
6-
(1..Concurrent::ThreadSafe::Test::THREADS).map do |i|
7-
in_thread do
8-
1000.times do
9-
ary << i
10-
ary.each { |x| x * 2 }
11-
ary.shift
12-
ary.last
5+
describe '.[]' do
6+
describe 'when initializing with no arguments' do
7+
it do
8+
expect(described_class[]).to be_empty
9+
end
10+
end
11+
12+
describe 'when initializing with arguments' do
13+
it 'creates an array with the given objects' do
14+
expect(described_class[:hello, :world]).to eq [:hello, :world]
15+
end
16+
end
17+
end
18+
19+
describe '.new' do
20+
describe 'when initializing with no arguments' do
21+
it do
22+
expect(described_class.new).to be_empty
23+
end
24+
end
25+
26+
describe 'when initializing with a size argument' do
27+
let(:size) { 3 }
28+
29+
it 'creates an array with size elements set to nil' do
30+
expect(described_class.new(size)).to eq [nil, nil, nil]
31+
end
32+
33+
describe 'when initializing with a default value argument' do
34+
let(:default_value) { :ruby }
35+
36+
it 'creates an array with size elements set to the default value' do
37+
expect(described_class.new(size, default_value)).to eq [:ruby, :ruby, :ruby]
1338
end
1439
end
15-
end.map(&:join)
16-
expect(ary).to be_empty
40+
41+
describe 'when initializing with a block argument' do
42+
let(:block_argument) { proc { |index| :"ruby#{index}" } }
43+
44+
it 'creates an array with size elements set to the default value' do
45+
expect(described_class.new(size, &block_argument)).to eq [:ruby0, :ruby1, :ruby2]
46+
end
47+
end
48+
end
49+
50+
describe 'when initializing with another array as an argument' do
51+
let(:other_array) { [:hello, :world] }
52+
let(:fake_other_array) { double('Fake array', to_ary: other_array) }
53+
54+
it 'creates a new array' do
55+
expect(described_class.new(other_array)).to_not be other_array
56+
end
57+
58+
it 'creates an array with the same contents as the other array' do
59+
expect(described_class.new(other_array)).to eq [:hello, :world]
60+
end
61+
62+
it 'creates an array with the results of calling #to_ary on the other array' do
63+
expect(described_class.new(fake_other_array)).to eq [:hello, :world]
64+
end
65+
end
66+
end
67+
68+
context 'concurrency' do
69+
it do
70+
(1..Concurrent::ThreadSafe::Test::THREADS).map do |i|
71+
in_thread(ary) do |ary|
72+
1000.times do
73+
ary << i
74+
ary.each { |x| x * 2 }
75+
ary.shift
76+
ary.last
77+
end
78+
end
79+
end.map(&:join)
80+
expect(ary).to be_empty
81+
end
1782
end
1883

1984
describe '#slice' do
20-
# This is mostly relevant on Rubinius and Truffle
85+
# This is mostly relevant on Rubinius and TruffleRuby
2186
it 'correctly initializes the monitor' do
2287
ary.concat([0, 1, 2, 3, 4, 5, 6, 7, 8])
2388

0 commit comments

Comments
 (0)