11package com .concurrent_ruby .ext ;
22
33import java .io .IOException ;
4- import java .util .concurrent .atomic .AtomicBoolean ;
54
65import org .jruby .Ruby ;
76import org .jruby .RubyClass ;
1413import org .jruby .runtime .load .Library ;
1514import org .jruby .runtime .Block ;
1615import org .jruby .runtime .Visibility ;
17- import org .jruby .RubyBoolean ;
18- import org .jruby .RubyNil ;
1916import org .jruby .runtime .ThreadContext ;
2017import org .jruby .util .unsafe .UnsafeHolder ;
2118
2219public class SynchronizationLibrary implements Library {
2320
24- private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator () {
21+ private static final ObjectAllocator JRUBY_OBJECT_ALLOCATOR = new ObjectAllocator () {
2522 public IRubyObject allocate (Ruby runtime , RubyClass klazz ) {
26- return new JavaObject (runtime , klazz );
23+ return new JRubyObject (runtime , klazz );
24+ }
25+ };
26+
27+ private static final ObjectAllocator OBJECT_ALLOCATOR = new ObjectAllocator () {
28+ public IRubyObject allocate (Ruby runtime , RubyClass klazz ) {
29+ return new Object (runtime , klazz );
30+ }
31+ };
32+
33+ private static final ObjectAllocator ABSTRACT_LOCKABLE_OBJECT_ALLOCATOR = new ObjectAllocator () {
34+ public IRubyObject allocate (Ruby runtime , RubyClass klazz ) {
35+ return new AbstractLockableObject (runtime , klazz );
36+ }
37+ };
38+
39+ private static final ObjectAllocator JRUBY_LOCKABLE_OBJECT_ALLOCATOR = new ObjectAllocator () {
40+ public IRubyObject allocate (Ruby runtime , RubyClass klazz ) {
41+ return new JRubyLockableObject (runtime , klazz );
2742 }
2843 };
2944
3045 public void load (Ruby runtime , boolean wrap ) throws IOException {
3146 RubyModule synchronizationModule = runtime .
3247 defineModule ("Concurrent" ).
3348 defineModuleUnder ("Synchronization" );
34- RubyClass parentClass = synchronizationModule .getClass ("AbstractObject" );
3549
36- if (parentClass == null )
37- throw runtime .newRuntimeError ("Concurrent::Synchronization::AbstractObject is missing" );
50+ defineClass (runtime , synchronizationModule , "AbstractObject" , "JRubyObject" ,
51+ JRubyObject .class , JRUBY_OBJECT_ALLOCATOR );
52+
53+ defineClass (runtime , synchronizationModule , "JRubyObject" , "Object" ,
54+ Object .class , OBJECT_ALLOCATOR );
55+
56+ defineClass (runtime , synchronizationModule , "Object" , "AbstractLockableObject" ,
57+ AbstractLockableObject .class , ABSTRACT_LOCKABLE_OBJECT_ALLOCATOR );
58+
59+ defineClass (runtime , synchronizationModule , "AbstractLockableObject" , "JRubyLockableObject" ,
60+ JRubyLockableObject .class , JRUBY_LOCKABLE_OBJECT_ALLOCATOR );
61+ }
62+
63+ private RubyClass defineClass (Ruby runtime , RubyModule namespace , String parentName , String name ,
64+ Class javaImplementation , ObjectAllocator allocator ) {
65+ final RubyClass parentClass = namespace .getClass (parentName );
3866
39- RubyClass synchronizedObjectJavaClass =
40- synchronizationModule .defineClassUnder ("JavaObject" , parentClass , JRUBYREFERENCE_ALLOCATOR );
67+ if (parentClass == null ) {
68+ System .out .println ("not found " + parentName );
69+ throw runtime .newRuntimeError (namespace .toString () + "::" + parentName + " is missing" );
70+ }
4171
42- synchronizedObjectJavaClass .defineAnnotatedMethods (JavaObject .class );
72+ final RubyClass newClass = namespace .defineClassUnder (name , parentClass , allocator );
73+ newClass .defineAnnotatedMethods (javaImplementation );
74+ return newClass ;
4375 }
4476
45- @ JRubyClass (name = "JavaObject" , parent = "AbstractObject" )
46- public static class JavaObject extends RubyObject {
77+ // Facts:
78+ // - all ivar reads are without any synchronisation of fences see
79+ // https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/runtime/ivars/VariableAccessor.java#L110-110
80+ // - writes depend on UnsafeHolder.U, null -> SynchronizedVariableAccessor, !null -> StampedVariableAccessor
81+ // SynchronizedVariableAccessor wraps with synchronized block, StampedVariableAccessor uses fullFence or
82+ // volatilePut
4783
48- public static final long AN_VOLATILE_FIELD_OFFSET =
49- UnsafeHolder . fieldOffset ( JavaObject . class , "anVolatileField" );
50- private volatile int anVolatileField = 0 ;
84+ @ JRubyClass ( name = "JRubyObject" , parent = "AbstractObject" )
85+ public static class JRubyObject extends RubyObject {
86+ private static volatile ThreadContext threadContext = null ;
5187
52- public JavaObject (Ruby runtime , RubyClass metaClass ) {
88+ public JRubyObject (Ruby runtime , RubyClass metaClass ) {
5389 super (runtime , metaClass );
5490 }
5591
@@ -58,6 +94,73 @@ public IRubyObject initialize(ThreadContext context) {
5894 return this ;
5995 }
6096
97+ @ JRubyMethod (name = "full_memory_barrier" , visibility = Visibility .PRIVATE )
98+ public IRubyObject fullMemoryBarrier (ThreadContext context ) {
99+ // Prevent reordering of ivar writes with publication of this instance
100+ if (UnsafeHolder .U == null || !UnsafeHolder .SUPPORTS_FENCES ) {
101+ // Assuming that following volatile read and write is not eliminated it simulates fullFence.
102+ // If it's eliminated it'll cause problems only on non-x86 platforms.
103+ final ThreadContext oldContext = threadContext ;
104+ threadContext = context ;
105+ } else {
106+ UnsafeHolder .fullFence ();
107+ }
108+ return context .nil ;
109+ }
110+
111+ @ JRubyMethod (name = "instance_variable_get_volatile" , visibility = Visibility .PROTECTED )
112+ public IRubyObject instanceVariableGetVolatile (ThreadContext context , IRubyObject name ) {
113+ // Ensure we ses latest value with loadFence
114+ if (UnsafeHolder .U == null || !UnsafeHolder .SUPPORTS_FENCES ) {
115+ // piggybacking on volatile read, simulating loadFence
116+ final ThreadContext oldContext = threadContext ;
117+ return instance_variable_get (context , name );
118+ } else {
119+ UnsafeHolder .loadFence ();
120+ return instance_variable_get (context , name );
121+ }
122+ }
123+
124+ @ JRubyMethod (name = "instance_variable_set_volatile" , visibility = Visibility .PROTECTED )
125+ public IRubyObject InstanceVariableSetVolatile (ThreadContext context , IRubyObject name , IRubyObject value ) {
126+ // Ensure we make last update visible
127+ if (UnsafeHolder .U == null || !UnsafeHolder .SUPPORTS_FENCES ) {
128+ // piggybacking on volatile write, simulating storeFence
129+ final IRubyObject result = instance_variable_set (name , value );
130+ threadContext = context ;
131+ return result ;
132+ } else {
133+ // JRuby uses StampedVariableAccessor which calls fullFence
134+ // so no additional steps needed.
135+ // 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 );
137+ }
138+ }
139+ }
140+
141+ @ JRubyClass (name = "Object" , parent = "JRubyObject" )
142+ public static class Object extends JRubyObject {
143+
144+ public Object (Ruby runtime , RubyClass metaClass ) {
145+ super (runtime , metaClass );
146+ }
147+ }
148+
149+ @ JRubyClass (name = "AbstractLockableObject" , parent = "Object" )
150+ public static class AbstractLockableObject extends Object {
151+
152+ public AbstractLockableObject (Ruby runtime , RubyClass metaClass ) {
153+ super (runtime , metaClass );
154+ }
155+ }
156+
157+ @ JRubyClass (name = "JRubyLockableObject" , parent = "AbstractLockableObject" )
158+ public static class JRubyLockableObject extends JRubyObject {
159+
160+ public JRubyLockableObject (Ruby runtime , RubyClass metaClass ) {
161+ super (runtime , metaClass );
162+ }
163+
61164 @ JRubyMethod (name = "synchronize" , visibility = Visibility .PROTECTED )
62165 public IRubyObject rubySynchronize (ThreadContext context , Block block ) {
63166 synchronized (this ) {
@@ -108,58 +211,5 @@ public IRubyObject nsBroadcast(ThreadContext context) {
108211 notifyAll ();
109212 return this ;
110213 }
111-
112- @ JRubyMethod (name = "ensure_ivar_visibility!" , visibility = Visibility .PROTECTED )
113- public IRubyObject ensureIvarVisibilityBang (ThreadContext context ) {
114- if (UnsafeHolder .U == null ) {
115- // We are screwed
116- throw new UnsupportedOperationException ();
117- } else if (UnsafeHolder .SUPPORTS_FENCES )
118- // We have to prevent ivar writes to reordered with storing of the final instance reference
119- // Therefore wee need a fullFence to prevent reordering in both directions.
120- UnsafeHolder .fullFence ();
121- else {
122- // Assumption that this is not eliminated, if false it will break non x86 platforms.
123- UnsafeHolder .U .putIntVolatile (this , AN_VOLATILE_FIELD_OFFSET , 1 );
124- UnsafeHolder .U .getIntVolatile (this , AN_VOLATILE_FIELD_OFFSET );
125- }
126- return context .nil ;
127- }
128-
129- @ JRubyMethod (name = "instance_variable_get_volatile" , visibility = Visibility .PROTECTED )
130- public IRubyObject instanceVariableGetVolatile (ThreadContext context , IRubyObject name ) {
131- if (UnsafeHolder .U == null ) {
132- // TODO: Possibly dangerous, there may be a deadlock on the this
133- synchronized (this ) {
134- return instance_variable_get (context , name );
135- }
136- } else if (UnsafeHolder .SUPPORTS_FENCES ) {
137- // ensure we see latest value
138- UnsafeHolder .loadFence ();
139- return instance_variable_get (context , name );
140- } else {
141- UnsafeHolder .U .getIntVolatile (this , AN_VOLATILE_FIELD_OFFSET );
142- return instance_variable_get (context , name );
143- }
144- }
145-
146- @ JRubyMethod (name = "instance_variable_set_volatile" , visibility = Visibility .PROTECTED )
147- public IRubyObject InstanceVariableSetVolatile (ThreadContext context , IRubyObject name , IRubyObject value ) {
148- if (UnsafeHolder .U == null ) {
149- // TODO: Possibly dangerous, there may be a deadlock on the this
150- synchronized (this ) {
151- return instance_variable_set (name , value );
152- }
153- } else if (UnsafeHolder .SUPPORTS_FENCES ) {
154- final IRubyObject result = instance_variable_set (name , value );
155- // ensure we make latest value visible
156- UnsafeHolder .storeFence ();
157- return result ;
158- } else {
159- final IRubyObject result = instance_variable_set (name , value );
160- UnsafeHolder .U .putIntVolatile (this , AN_VOLATILE_FIELD_OFFSET , 1 );
161- return result ;
162- }
163- }
164214 }
165215}
0 commit comments