Skip to content

Commit 9c93a47

Browse files
committed
Merge remote-tracking branch 'upstream/pr/424' into synchronization
* upstream/pr/424: (5 commits) correct example to use volatile method ...
2 parents d50dfa5 + a4a9a76 commit 9c93a47

File tree

7 files changed

+213
-115
lines changed

7 files changed

+213
-115
lines changed

ext/com/concurrent_ruby/ext/SynchronizationLibrary.java

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.jruby.RubyClass;
77
import org.jruby.RubyModule;
88
import org.jruby.RubyObject;
9+
import org.jruby.RubyBasicObject;
910
import org.jruby.anno.JRubyClass;
1011
import org.jruby.anno.JRubyMethod;
1112
import org.jruby.runtime.ObjectAllocator;
@@ -47,6 +48,9 @@ public void load(Ruby runtime, boolean wrap) throws IOException {
4748
defineModule("Concurrent").
4849
defineModuleUnder("Synchronization");
4950

51+
RubyModule jrubyAttrVolatileModule = synchronizationModule.defineModuleUnder("JRubyAttrVolatile");
52+
jrubyAttrVolatileModule.defineAnnotatedMethods(JRubyAttrVolatile.class);
53+
5054
defineClass(runtime, synchronizationModule, "AbstractObject", "JRubyObject",
5155
JRubyObject.class, JRUBY_OBJECT_ALLOCATOR);
5256

@@ -81,21 +85,16 @@ private RubyClass defineClass(Ruby runtime, RubyModule namespace, String parentN
8185
// SynchronizedVariableAccessor wraps with synchronized block, StampedVariableAccessor uses fullFence or
8286
// volatilePut
8387

84-
@JRubyClass(name = "JRubyObject", parent = "AbstractObject")
85-
public static class JRubyObject extends RubyObject {
86-
private static volatile ThreadContext threadContext = null;
87-
88-
public JRubyObject(Ruby runtime, RubyClass metaClass) {
89-
super(runtime, metaClass);
90-
}
88+
// module JRubyAttrVolatile
89+
public static class JRubyAttrVolatile {
9190

92-
@JRubyMethod
93-
public IRubyObject initialize(ThreadContext context) {
94-
return this;
95-
}
91+
// volatile threadContext is used as a memory barrier per the JVM memory model happens-before semantic
92+
// on volatile fields. any volatile field could have been used but using the thread context is an
93+
// attempt to avoid code elimination.
94+
private static volatile ThreadContext threadContext = null;
9695

9796
@JRubyMethod(name = "full_memory_barrier", visibility = Visibility.PRIVATE)
98-
public IRubyObject fullMemoryBarrier(ThreadContext context) {
97+
public static IRubyObject fullMemoryBarrier(ThreadContext context, IRubyObject self) {
9998
// Prevent reordering of ivar writes with publication of this instance
10099
if (UnsafeHolder.U == null || !UnsafeHolder.SUPPORTS_FENCES) {
101100
// Assuming that following volatile read and write is not eliminated it simulates fullFence.
@@ -109,35 +108,43 @@ public IRubyObject fullMemoryBarrier(ThreadContext context) {
109108
}
110109

111110
@JRubyMethod(name = "instance_variable_get_volatile", visibility = Visibility.PROTECTED)
112-
public IRubyObject instanceVariableGetVolatile(ThreadContext context, IRubyObject name) {
111+
public static IRubyObject instanceVariableGetVolatile(ThreadContext context, IRubyObject self, IRubyObject name) {
113112
// Ensure we ses latest value with loadFence
114113
if (UnsafeHolder.U == null || !UnsafeHolder.SUPPORTS_FENCES) {
115114
// piggybacking on volatile read, simulating loadFence
116115
final ThreadContext oldContext = threadContext;
117-
return instance_variable_get(context, name);
116+
return ((RubyBasicObject)self).instance_variable_get(context, name);
118117
} else {
119118
UnsafeHolder.loadFence();
120-
return instance_variable_get(context, name);
119+
return ((RubyBasicObject)self).instance_variable_get(context, name);
121120
}
122121
}
123122

124123
@JRubyMethod(name = "instance_variable_set_volatile", visibility = Visibility.PROTECTED)
125-
public IRubyObject InstanceVariableSetVolatile(ThreadContext context, IRubyObject name, IRubyObject value) {
124+
public static IRubyObject InstanceVariableSetVolatile(ThreadContext context, IRubyObject self, IRubyObject name, IRubyObject value) {
126125
// Ensure we make last update visible
127126
if (UnsafeHolder.U == null || !UnsafeHolder.SUPPORTS_FENCES) {
128127
// piggybacking on volatile write, simulating storeFence
129-
final IRubyObject result = instance_variable_set(name, value);
128+
final IRubyObject result = ((RubyBasicObject)self).instance_variable_set(name, value);
130129
threadContext = context;
131130
return result;
132131
} else {
133132
// JRuby uses StampedVariableAccessor which calls fullFence
134133
// so no additional steps needed.
135134
// See https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/runtime/ivars/StampedVariableAccessor.java#L151-L159
136-
return instance_variable_set(name, value);
135+
return ((RubyBasicObject)self).instance_variable_set(name, value);
137136
}
138137
}
139138
}
140139

140+
@JRubyClass(name = "JRubyObject", parent = "AbstractObject")
141+
public static class JRubyObject extends RubyObject {
142+
143+
public JRubyObject(Ruby runtime, RubyClass metaClass) {
144+
super(runtime, metaClass);
145+
}
146+
}
147+
141148
@JRubyClass(name = "Object", parent = "JRubyObject")
142149
public static class Object extends JRubyObject {
143150

lib/concurrent/synchronization.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require 'concurrent/synchronization/jruby_object'
88
require 'concurrent/synchronization/rbx_object'
99
require 'concurrent/synchronization/object'
10+
require 'concurrent/synchronization/volatile'
1011

1112
require 'concurrent/synchronization/abstract_lockable_object'
1213
require 'concurrent/synchronization/mri_lockable_object'

lib/concurrent/synchronization/jruby_object.rb

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,41 @@ module Synchronization
33

44
if Concurrent.on_jruby?
55

6-
# @!visibility private
7-
# @!macro internal_implementation_note
8-
class JRubyObject < AbstractObject
6+
module JRubyAttrVolatile
7+
def self.included(base)
8+
base.extend(ClassMethods)
9+
end
910

10-
def self.attr_volatile(*names)
11-
names.each do |name|
11+
module ClassMethods
12+
def attr_volatile(*names)
13+
names.each do |name|
1214

13-
ivar = :"@volatile_#{name}"
15+
ivar = :"@volatile_#{name}"
1416

15-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
16-
def #{name}
17-
instance_variable_get_volatile(:#{ivar})
18-
end
17+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
18+
def #{name}
19+
instance_variable_get_volatile(:#{ivar})
20+
end
1921
20-
def #{name}=(value)
21-
instance_variable_set_volatile(:#{ivar}, value)
22-
end
23-
RUBY
22+
def #{name}=(value)
23+
instance_variable_set_volatile(:#{ivar}, value)
24+
end
25+
RUBY
2426

27+
end
28+
names.map { |n| [n, :"#{n}="] }.flatten
2529
end
26-
names.map { |n| [n, :"#{n}="] }.flatten
2730
end
31+
end
2832

33+
# @!visibility private
34+
# @!macro internal_implementation_note
35+
class JRubyObject < AbstractObject
36+
include JRubyAttrVolatile
37+
38+
def initialize
39+
# nothing to do
40+
end
2941
end
3042
end
3143
end
Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,43 @@
11
module Concurrent
22
module Synchronization
33

4-
# @!visibility private
5-
# @!macro internal_implementation_note
6-
class MriObject < AbstractObject
4+
module MriAttrVolatile
5+
def self.included(base)
6+
base.extend(ClassMethods)
7+
end
78

8-
def initialize
9-
# nothing to do
9+
module ClassMethods
10+
def attr_volatile(*names)
11+
names.each do |name|
12+
ivar = :"@volatile_#{name}"
13+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
14+
def #{name}
15+
#{ivar}
16+
end
17+
18+
def #{name}=(value)
19+
#{ivar} = value
20+
end
21+
RUBY
22+
end
23+
names.map { |n| [n, :"#{n}="] }.flatten
24+
end
1025
end
1126

1227
def full_memory_barrier
1328
# relying on undocumented behavior of CRuby, GVL acquire has lock which ensures visibility of ivars
1429
# https://github.com/ruby/ruby/blob/ruby_2_2/thread_pthread.c#L204-L211
1530
end
31+
end
1632

17-
def self.attr_volatile(*names)
18-
names.each do |name|
19-
ivar = :"@volatile_#{name}"
20-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
21-
def #{name}
22-
#{ivar}
23-
end
33+
# @!visibility private
34+
# @!macro internal_implementation_note
35+
class MriObject < AbstractObject
36+
include MriAttrVolatile
2437

25-
def #{name}=(value)
26-
#{ivar} = value
27-
end
28-
RUBY
29-
end
30-
names.map { |n| [n, :"#{n}="] }.flatten
38+
def initialize
39+
# nothing to do
3140
end
3241
end
33-
3442
end
3543
end
Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,45 @@
11
module Concurrent
22
module Synchronization
33

4-
# @!visibility private
5-
# @!macro internal_implementation_note
6-
class RbxObject < AbstractObject
7-
def initialize
8-
# nothing to do
4+
module RbxAttrVolatile
5+
def self.included(base)
6+
base.extend(ClassMethods)
7+
end
8+
9+
module ClassMethods
10+
def attr_volatile(*names)
11+
names.each do |name|
12+
ivar = :"@volatile_#{name}"
13+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
14+
def #{name}
15+
Rubinius.memory_barrier
16+
#{ivar}
17+
end
18+
19+
def #{name}=(value)
20+
#{ivar} = value
21+
Rubinius.memory_barrier
22+
end
23+
RUBY
24+
end
25+
names.map { |n| [n, :"#{n}="] }.flatten
26+
end
927
end
1028

1129
def full_memory_barrier
1230
# Rubinius instance variables are not volatile so we need to insert barrier
1331
Rubinius.memory_barrier
1432
end
33+
end
1534

16-
def self.attr_volatile *names
17-
names.each do |name|
18-
ivar = :"@volatile_#{name}"
19-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
20-
def #{name}
21-
Rubinius.memory_barrier
22-
#{ivar}
23-
end
35+
# @!visibility private
36+
# @!macro internal_implementation_note
37+
class RbxObject < AbstractObject
38+
include RbxAttrVolatile
2439

25-
def #{name}=(value)
26-
#{ivar} = value
27-
Rubinius.memory_barrier
28-
end
29-
RUBY
30-
end
31-
names.map { |n| [n, :"#{n}="] }.flatten
40+
def initialize
41+
# nothing to do
3242
end
3343
end
34-
3544
end
3645
end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
module Concurrent
2+
module Synchronization
3+
4+
# Volatile adds the attr_volatile class method when included.
5+
#
6+
# @example
7+
# class Foo
8+
# include Concurrent::Synchronization::Volatile
9+
#
10+
# attr_volatile :bar
11+
#
12+
# def initialize
13+
# self.bar = 1
14+
# end
15+
# end
16+
#
17+
# foo = Foo.new
18+
# foo.bar
19+
# => 1
20+
# foo.bar = 2
21+
# => 2
22+
23+
Volatile = case
24+
when Concurrent.on_cruby?
25+
MriAttrVolatile
26+
when Concurrent.on_jruby?
27+
JRubyAttrVolatile
28+
when Concurrent.on_rbx?
29+
RbxAttrVolatile
30+
else
31+
warn 'Possibly unsupported Ruby implementation'
32+
MriAttrVolatile
33+
end
34+
end
35+
end

0 commit comments

Comments
 (0)