diff --git a/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java b/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java index bfcc0d078..f0c75ee40 100644 --- a/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +++ b/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java @@ -55,12 +55,6 @@ private static boolean supportsFences() { } } - private static final ObjectAllocator JRUBY_OBJECT_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new JRubyObject(runtime, klazz); - } - }; - private static final ObjectAllocator OBJECT_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klazz) { return new Object(runtime, klazz); @@ -87,10 +81,7 @@ public void load(Ruby runtime, boolean wrap) throws IOException { RubyModule jrubyAttrVolatileModule = synchronizationModule.defineModuleUnder("JRubyAttrVolatile"); jrubyAttrVolatileModule.defineAnnotatedMethods(JRubyAttrVolatile.class); - defineClass(runtime, synchronizationModule, "AbstractObject", "JRubyObject", - JRubyObject.class, JRUBY_OBJECT_ALLOCATOR); - - defineClass(runtime, synchronizationModule, "JRubyObject", "Object", + defineClass(runtime, synchronizationModule, "AbstractObject", "Object", Object.class, OBJECT_ALLOCATOR); defineClass(runtime, synchronizationModule, "Object", "AbstractLockableObject", @@ -143,8 +134,8 @@ public static class JRubyAttrVolatile { // attempt to avoid code elimination. private static volatile int volatileField; - @JRubyMethod(name = "full_memory_barrier", visibility = Visibility.PUBLIC) - public static IRubyObject fullMemoryBarrier(ThreadContext context, IRubyObject self) { + @JRubyMethod(name = "full_memory_barrier", visibility = Visibility.PUBLIC, module = true) + public static IRubyObject fullMemoryBarrier(ThreadContext context, IRubyObject module) { // Prevent reordering of ivar writes with publication of this instance if (!FULL_FENCE) { // Assuming that following volatile read and write is not eliminated it simulates fullFence. @@ -158,9 +149,10 @@ public static IRubyObject fullMemoryBarrier(ThreadContext context, IRubyObject s return context.nil; } - @JRubyMethod(name = "instance_variable_get_volatile", visibility = Visibility.PUBLIC) + @JRubyMethod(name = "instance_variable_get_volatile", visibility = Visibility.PUBLIC, module = true) public static IRubyObject instanceVariableGetVolatile( ThreadContext context, + IRubyObject module, IRubyObject self, IRubyObject name) { // Ensure we ses latest value with loadFence @@ -174,9 +166,10 @@ public static IRubyObject instanceVariableGetVolatile( } } - @JRubyMethod(name = "instance_variable_set_volatile", visibility = Visibility.PUBLIC) + @JRubyMethod(name = "instance_variable_set_volatile", visibility = Visibility.PUBLIC, module = true) public static IRubyObject InstanceVariableSetVolatile( ThreadContext context, + IRubyObject module, IRubyObject self, IRubyObject name, IRubyObject value) { @@ -195,16 +188,8 @@ public static IRubyObject InstanceVariableSetVolatile( } } - @JRubyClass(name = "JRubyObject", parent = "AbstractObject") - public static class JRubyObject extends RubyObject { - - public JRubyObject(Ruby runtime, RubyClass metaClass) { - super(runtime, metaClass); - } - } - - @JRubyClass(name = "Object", parent = "JRubyObject") - public static class Object extends JRubyObject { + @JRubyClass(name = "Object", parent = "AbstractObject") + public static class Object extends RubyObject { public Object(Ruby runtime, RubyClass metaClass) { super(runtime, metaClass); @@ -220,7 +205,7 @@ public AbstractLockableObject(Ruby runtime, RubyClass metaClass) { } @JRubyClass(name = "JRubyLockableObject", parent = "AbstractLockableObject") - public static class JRubyLockableObject extends JRubyObject { + public static class JRubyLockableObject extends AbstractLockableObject { public JRubyLockableObject(Ruby runtime, RubyClass metaClass) { super(runtime, metaClass); diff --git a/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb b/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb index 0b0373dc2..f775691a2 100644 --- a/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +++ b/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb @@ -1,5 +1,6 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + require 'concurrent/atomic/mutex_atomic_boolean' -require 'concurrent/synchronization' module Concurrent @@ -79,10 +80,10 @@ module Concurrent # @!visibility private # @!macro internal_implementation_note AtomicBooleanImplementation = case - when defined?(JavaAtomicBoolean) - JavaAtomicBoolean - when defined?(CAtomicBoolean) + when Concurrent.on_cruby? && Concurrent.c_extensions_loaded? CAtomicBoolean + when Concurrent.on_jruby? + JavaAtomicBoolean else MutexAtomicBoolean end diff --git a/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb b/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb index c67166d83..26cd05d86 100644 --- a/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +++ b/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb @@ -1,5 +1,6 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + require 'concurrent/atomic/mutex_atomic_fixnum' -require 'concurrent/synchronization' module Concurrent @@ -96,10 +97,10 @@ module Concurrent # @!visibility private # @!macro internal_implementation_note AtomicFixnumImplementation = case - when defined?(JavaAtomicFixnum) - JavaAtomicFixnum - when defined?(CAtomicFixnum) + when Concurrent.on_cruby? && Concurrent.c_extensions_loaded? CAtomicFixnum + when Concurrent.on_jruby? + JavaAtomicFixnum else MutexAtomicFixnum end diff --git a/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb b/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb index 46e276c4e..c85defffe 100644 --- a/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +++ b/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb @@ -1,5 +1,5 @@ -require 'concurrent/synchronization' -require 'concurrent/utility/engine' +require 'concurrent/utility/native_extension_loader' # load native parts first + require 'concurrent/atomic_reference/numeric_cas_wrapper' # Shim for TruffleRuby::AtomicReference diff --git a/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb b/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb index cb5b35a56..3c119bc32 100644 --- a/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +++ b/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb @@ -1,4 +1,5 @@ if Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' module Concurrent diff --git a/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb b/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb index a033de4ca..015996b06 100644 --- a/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +++ b/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb @@ -1,16 +1,18 @@ -require 'concurrent/synchronization' +require 'concurrent/synchronization/safe_initialization' module Concurrent # @!macro atomic_boolean # @!visibility private # @!macro internal_implementation_note - class MutexAtomicBoolean < Synchronization::LockableObject + class MutexAtomicBoolean + extend Concurrent::Synchronization::SafeInitialization # @!macro atomic_boolean_method_initialize def initialize(initial = false) super() - synchronize { ns_initialize(initial) } + @Lock = ::Mutex.new + @value = !!initial end # @!macro atomic_boolean_method_value_get @@ -46,8 +48,12 @@ def make_false protected # @!visibility private - def ns_initialize(initial) - @value = !!initial + def synchronize + if @Lock.owned? + yield + else + @Lock.synchronize { yield } + end end private diff --git a/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb b/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb index 77b91d2db..0ca395579 100644 --- a/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +++ b/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb @@ -1,4 +1,4 @@ -require 'concurrent/synchronization' +require 'concurrent/synchronization/safe_initialization' require 'concurrent/utility/native_integer' module Concurrent @@ -6,12 +6,14 @@ module Concurrent # @!macro atomic_fixnum # @!visibility private # @!macro internal_implementation_note - class MutexAtomicFixnum < Synchronization::LockableObject + class MutexAtomicFixnum + extend Concurrent::Synchronization::SafeInitialization # @!macro atomic_fixnum_method_initialize def initialize(initial = 0) super() - synchronize { ns_initialize(initial) } + @Lock = ::Mutex.new + ns_set(initial) end # @!macro atomic_fixnum_method_value_get @@ -60,8 +62,12 @@ def update protected # @!visibility private - def ns_initialize(initial) - ns_set(initial) + def synchronize + if @Lock.owned? + yield + else + @Lock.synchronize { yield } + end end private diff --git a/lib/concurrent-ruby/concurrent/atomic/semaphore.rb b/lib/concurrent-ruby/concurrent/atomic/semaphore.rb index 084eca2ee..3bf45ff6a 100644 --- a/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +++ b/lib/concurrent-ruby/concurrent/atomic/semaphore.rb @@ -94,8 +94,8 @@ module Concurrent # @!visibility private # @!macro internal_implementation_note - SemaphoreImplementation = case - when defined?(JavaSemaphore) + SemaphoreImplementation = if Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' JavaSemaphore else MutexSemaphore diff --git a/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb b/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb index d092aedd5..e4cfc0a20 100644 --- a/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +++ b/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb @@ -1,8 +1,11 @@ +require 'concurrent/synchronization/safe_initialization' + module Concurrent # @!visibility private # @!macro internal_implementation_note - class MutexAtomicReference < Synchronization::LockableObject + class MutexAtomicReference + extend Concurrent::Synchronization::SafeInitialization include AtomicDirectUpdate include AtomicNumericCompareAndSetWrapper alias_method :compare_and_swap, :compare_and_set @@ -10,7 +13,8 @@ class MutexAtomicReference < Synchronization::LockableObject # @!macro atomic_reference_method_initialize def initialize(value = nil) super() - synchronize { ns_initialize(value) } + @Lock = ::Mutex.new + @value = value end # @!macro atomic_reference_method_get @@ -49,8 +53,13 @@ def _compare_and_set(old_value, new_value) protected - def ns_initialize(value) - @value = value + # @!visibility private + def synchronize + if @Lock.owned? + yield + else + @Lock.synchronize { yield } + end end end end diff --git a/lib/concurrent-ruby/concurrent/exchanger.rb b/lib/concurrent-ruby/concurrent/exchanger.rb index 5a99550b3..a5405d252 100644 --- a/lib/concurrent-ruby/concurrent/exchanger.rb +++ b/lib/concurrent-ruby/concurrent/exchanger.rb @@ -289,6 +289,7 @@ def do_exchange(value, timeout) end if Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' # @!macro internal_implementation_note # @!visibility private diff --git a/lib/concurrent-ruby/concurrent/map.rb b/lib/concurrent-ruby/concurrent/map.rb index 328af19cc..fb431efde 100644 --- a/lib/concurrent-ruby/concurrent/map.rb +++ b/lib/concurrent-ruby/concurrent/map.rb @@ -10,6 +10,7 @@ module Collection # @!visibility private MapImplementation = case when Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' # noinspection RubyResolve JRubyMapBackend when Concurrent.on_cruby? diff --git a/lib/concurrent-ruby/concurrent/synchronization.rb b/lib/concurrent-ruby/concurrent/synchronization.rb index 20b606c83..13cc8859b 100644 --- a/lib/concurrent-ruby/concurrent/synchronization.rb +++ b/lib/concurrent-ruby/concurrent/synchronization.rb @@ -1,12 +1,5 @@ require 'concurrent/utility/engine' -require 'concurrent/synchronization/abstract_object' -require 'concurrent/utility/native_extension_loader' # load native parts first -Concurrent.load_native_extensions - -require 'concurrent/synchronization/mri_object' -require 'concurrent/synchronization/jruby_object' -require 'concurrent/synchronization/truffleruby_object' require 'concurrent/synchronization/object' require 'concurrent/synchronization/volatile' diff --git a/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb b/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb index bc1260336..6a38a0227 100644 --- a/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +++ b/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb @@ -1,3 +1,5 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + module Concurrent module Synchronization diff --git a/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb b/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb index 532388b2b..7cd2decf9 100644 --- a/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +++ b/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb @@ -4,10 +4,8 @@ module Synchronization # @!visibility private # @!macro internal_implementation_note class AbstractObject - - # @abstract has to be implemented based on Ruby runtime def initialize - raise NotImplementedError + # nothing to do end # @!visibility private diff --git a/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb b/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb new file mode 100644 index 000000000..139e08d85 --- /dev/null +++ b/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb @@ -0,0 +1,29 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +module Concurrent + module Synchronization + case + when Concurrent.on_cruby? + def self.full_memory_barrier + # relying on undocumented behavior of CRuby, GVL acquire has lock which ensures visibility of ivars + # https://github.com/ruby/ruby/blob/ruby_2_2/thread_pthread.c#L204-L211 + end + + when Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' + def self.full_memory_barrier + JRubyAttrVolatile.full_memory_barrier + end + + when Concurrent.on_truffleruby? + def self.full_memory_barrier + TruffleRuby.full_memory_barrier + end + + else + warn 'Possibly unsupported Ruby implementation' + def self.full_memory_barrier + end + end + end +end diff --git a/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb b/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb index 359a032b7..76930461b 100644 --- a/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +++ b/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb @@ -1,7 +1,9 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + module Concurrent module Synchronization - if Concurrent.on_jruby? && Concurrent.java_extensions_loaded? + if Concurrent.on_jruby? # @!visibility private # @!macro internal_implementation_note diff --git a/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb b/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb deleted file mode 100644 index da91ac50b..000000000 --- a/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +++ /dev/null @@ -1,45 +0,0 @@ -module Concurrent - module Synchronization - - if Concurrent.on_jruby? && Concurrent.java_extensions_loaded? - - # @!visibility private - module JRubyAttrVolatile - def self.included(base) - base.extend(ClassMethods) - end - - module ClassMethods - def attr_volatile(*names) - names.each do |name| - - ivar = :"@volatile_#{name}" - - class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{name} - instance_variable_get_volatile(:#{ivar}) - end - - def #{name}=(value) - instance_variable_set_volatile(:#{ivar}, value) - end - RUBY - - end - names.map { |n| [n, :"#{n}="] }.flatten - end - end - end - - # @!visibility private - # @!macro internal_implementation_note - class JRubyObject < AbstractObject - include JRubyAttrVolatile - - def initialize - # nothing to do - end - end - end - end -end diff --git a/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb b/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb deleted file mode 100644 index 4b1d6c295..000000000 --- a/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +++ /dev/null @@ -1,44 +0,0 @@ -module Concurrent - module Synchronization - - # @!visibility private - module MriAttrVolatile - def self.included(base) - base.extend(ClassMethods) - end - - module ClassMethods - def attr_volatile(*names) - names.each do |name| - ivar = :"@volatile_#{name}" - class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{name} - #{ivar} - end - - def #{name}=(value) - #{ivar} = value - end - RUBY - end - names.map { |n| [n, :"#{n}="] }.flatten - end - end - - def full_memory_barrier - # relying on undocumented behavior of CRuby, GVL acquire has lock which ensures visibility of ivars - # https://github.com/ruby/ruby/blob/ruby_2_2/thread_pthread.c#L204-L211 - end - end - - # @!visibility private - # @!macro internal_implementation_note - class MriObject < AbstractObject - include MriAttrVolatile - - def initialize - # nothing to do - end - end - end -end diff --git a/lib/concurrent-ruby/concurrent/synchronization/object.rb b/lib/concurrent-ruby/concurrent/synchronization/object.rb index db6f1c976..c70ac88e3 100644 --- a/lib/concurrent-ruby/concurrent/synchronization/object.rb +++ b/lib/concurrent-ruby/concurrent/synchronization/object.rb @@ -1,26 +1,19 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +require 'concurrent/synchronization/safe_initialization' +require 'concurrent/synchronization/volatile' +require 'concurrent/atomic/atomic_reference' + module Concurrent module Synchronization - # @!visibility private - # @!macro internal_implementation_note - ObjectImplementation = case - when Concurrent.on_cruby? - MriObject - when Concurrent.on_jruby? - JRubyObject - when Concurrent.on_truffleruby? - TruffleRubyObject - else - warn 'Possibly unsupported Ruby implementation' - MriObject - end - private_constant :ObjectImplementation - # Abstract object providing final, volatile, ans CAS extensions to build other concurrent abstractions. # - final instance variables see {Object.safe_initialization!} # - volatile instance variables see {Object.attr_volatile} # - volatile instance variables see {Object.attr_atomic} - class Object < ObjectImplementation + class Object < AbstractObject + include Volatile + # TODO make it a module if possible # @!method self.attr_volatile(*names) @@ -36,36 +29,12 @@ def initialize __initialize_atomic_fields__ end - # By calling this method on a class, it and all its children are marked to be constructed safely. Meaning that - # all writes (ivar initializations) are made visible to all readers of newly constructed object. It ensures - # same behaviour as Java's final fields. - # @example - # class AClass < Concurrent::Synchronization::Object - # safe_initialization! - # - # def initialize - # @AFinalValue = 'value' # published safely, does not have to be synchronized - # end - # end - # @return [true] def self.safe_initialization! - # define only once, and not again in children - return if safe_initialization? - - # @!visibility private - def self.new(*args, &block) - object = super(*args, &block) - ensure - object.full_memory_barrier if object - end - - @safe_initialization = true + extend SafeInitialization unless safe_initialization? end - # @return [true, false] if this class is safely initialized. def self.safe_initialization? - @safe_initialization = false unless defined? @safe_initialization - @safe_initialization || (superclass.respond_to?(:safe_initialization?) && superclass.safe_initialization?) + self.singleton_class < SafeInitialization end # For testing purposes, quite slow. Injects assert code to new method which will raise if class instance contains diff --git a/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb b/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb new file mode 100644 index 000000000..f785e3522 --- /dev/null +++ b/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb @@ -0,0 +1,36 @@ +require 'concurrent/synchronization/full_memory_barrier' + +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + # + # By extending this module, a class and all its children are marked to be constructed safely. Meaning that + # all writes (ivar initializations) are made visible to all readers of newly constructed object. It ensures + # same behaviour as Java's final fields. + # + # Due to using Kernel#extend, the module is not included again if already present in the ancestors, + # which avoids extra overhead. + # + # @example + # class AClass < Concurrent::Synchronization::Object + # extend Concurrent::Synchronization::SafeInitialization + # + # def initialize + # @AFinalValue = 'value' # published safely, #foo will never return nil + # end + # + # def foo + # @AFinalValue + # end + # end + module SafeInitialization + def new(*args, &block) + super(*args, &block) + ensure + Concurrent::Synchronization.full_memory_barrier + end + end + end +end diff --git a/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb b/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb deleted file mode 100644 index 3919c76d2..000000000 --- a/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +++ /dev/null @@ -1,47 +0,0 @@ -module Concurrent - module Synchronization - - # @!visibility private - module TruffleRubyAttrVolatile - def self.included(base) - base.extend(ClassMethods) - end - - module ClassMethods - def attr_volatile(*names) - names.each do |name| - ivar = :"@volatile_#{name}" - - class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{name} - full_memory_barrier - #{ivar} - end - - def #{name}=(value) - #{ivar} = value - full_memory_barrier - end - RUBY - end - - names.map { |n| [n, :"#{n}="] }.flatten - end - end - - def full_memory_barrier - TruffleRuby.full_memory_barrier - end - end - - # @!visibility private - # @!macro internal_implementation_note - class TruffleRubyObject < AbstractObject - include TruffleRubyAttrVolatile - - def initialize - # nothing to do - end - end - end -end diff --git a/lib/concurrent-ruby/concurrent/synchronization/volatile.rb b/lib/concurrent-ruby/concurrent/synchronization/volatile.rb index 47f6a64ee..13fc14851 100644 --- a/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +++ b/lib/concurrent-ruby/concurrent/synchronization/volatile.rb @@ -1,3 +1,6 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first +require 'concurrent/synchronization/full_memory_barrier' + module Concurrent module Synchronization @@ -20,15 +23,77 @@ module Synchronization # foo.bar = 2 # => 2 - Volatile = case - when Concurrent.on_cruby? - MriAttrVolatile - when Concurrent.on_jruby? - JRubyAttrVolatile - when Concurrent.on_truffleruby? - TruffleRubyAttrVolatile - else - MriAttrVolatile - end + module Volatile + def self.included(base) + base.extend(ClassMethods) + end + + def full_memory_barrier + Synchronization.full_memory_barrier + end + + module ClassMethods + if Concurrent.on_cruby? + def attr_volatile(*names) + names.each do |name| + ivar = :"@volatile_#{name}" + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + #{ivar} + end + + def #{name}=(value) + #{ivar} = value + end + RUBY + end + names.map { |n| [n, :"#{n}="] }.flatten + end + + elsif Concurrent.on_jruby? + def attr_volatile(*names) + names.each do |name| + ivar = :"@volatile_#{name}" + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + ::Concurrent::Synchronization::JRubyAttrVolatile.instance_variable_get_volatile(self, :#{ivar}) + end + + def #{name}=(value) + ::Concurrent::Synchronization::JRubyAttrVolatile.instance_variable_set_volatile(self, :#{ivar}, value) + end + RUBY + + end + names.map { |n| [n, :"#{n}="] }.flatten + end + + else + warn 'Possibly unsupported Ruby implementation' unless Concurrent.on_truffleruby? + + def attr_volatile(*names) + names.each do |name| + ivar = :"@volatile_#{name}" + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + ::Concurrent::Synchronization.full_memory_barrier + #{ivar} + end + + def #{name}=(value) + #{ivar} = value + ::Concurrent::Synchronization.full_memory_barrier + end + RUBY + end + + names.map { |n| [n, :"#{n}="] }.flatten + end + end + end + + end end end diff --git a/lib/concurrent-ruby/concurrent/utility/engine.rb b/lib/concurrent-ruby/concurrent/utility/engine.rb index 89dce5b0c..43b2ba5ce 100644 --- a/lib/concurrent-ruby/concurrent/utility/engine.rb +++ b/lib/concurrent-ruby/concurrent/utility/engine.rb @@ -3,14 +3,14 @@ module Utility # @!visibility private module EngineDetector - def on_jruby? - RUBY_ENGINE == 'jruby' - end - def on_cruby? RUBY_ENGINE == 'ruby' end + def on_jruby? + RUBY_ENGINE == 'jruby' + end + def on_truffleruby? RUBY_ENGINE == 'truffleruby' end diff --git a/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb b/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb index a944bd729..02568ae87 100644 --- a/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +++ b/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb @@ -1,9 +1,9 @@ require 'concurrent/utility/engine' +# Synchronization::AbstractObject must be defined before loading the extension +require 'concurrent/synchronization/abstract_object' module Concurrent - module Utility - # @!visibility private module NativeExtensionLoader @@ -15,15 +15,7 @@ def c_extensions_loaded? defined?(@c_extensions_loaded) && @c_extensions_loaded end - def java_extensions_loaded? - defined?(@java_extensions_loaded) && @java_extensions_loaded - end - def load_native_extensions - unless defined? Synchronization::AbstractObject - raise 'native_extension_loader loaded before Synchronization::AbstractObject' - end - if Concurrent.on_cruby? && !c_extensions_loaded? ['concurrent/concurrent_ruby_ext', "concurrent/#{RUBY_VERSION[0..2]}/concurrent_ruby_ext" @@ -54,6 +46,10 @@ def set_c_extensions_loaded @c_extensions_loaded = true end + def java_extensions_loaded? + defined?(@java_extensions_loaded) && @java_extensions_loaded + end + def set_java_extensions_loaded @java_extensions_loaded = true end @@ -77,3 +73,4 @@ def try_load_c_extension(path) extend Utility::NativeExtensionLoader end +Concurrent.load_native_extensions diff --git a/spec/concurrent/atomic/atomic_boolean_spec.rb b/spec/concurrent/atomic/atomic_boolean_spec.rb index f839d50d3..817685b57 100644 --- a/spec/concurrent/atomic/atomic_boolean_spec.rb +++ b/spec/concurrent/atomic/atomic_boolean_spec.rb @@ -140,15 +140,13 @@ module Concurrent end end - if defined? Concurrent::CAtomicBoolean - + if Concurrent.allow_c_extensions? RSpec.describe CAtomicBoolean do it_should_behave_like :atomic_boolean end end if Concurrent.on_jruby? - RSpec.describe JavaAtomicBoolean do it_should_behave_like :atomic_boolean end @@ -165,7 +163,7 @@ module Concurrent it 'inherits from JavaAtomicBoolean' do expect(AtomicBoolean.ancestors).to include(JavaAtomicBoolean) end - elsif defined? Concurrent::CAtomicBoolean + elsif Concurrent.allow_c_extensions? it 'inherits from CAtomicBoolean' do expect(AtomicBoolean.ancestors).to include(CAtomicBoolean) end diff --git a/spec/concurrent/atomic/atomic_fixnum_spec.rb b/spec/concurrent/atomic/atomic_fixnum_spec.rb index b68086425..b450f78f0 100644 --- a/spec/concurrent/atomic/atomic_fixnum_spec.rb +++ b/spec/concurrent/atomic/atomic_fixnum_spec.rb @@ -202,15 +202,13 @@ module Concurrent end end - if defined? Concurrent::CAtomicFixnum - + if Concurrent.allow_c_extensions? RSpec.describe CAtomicFixnum do it_should_behave_like :atomic_fixnum end end if Concurrent.on_jruby? - RSpec.describe JavaAtomicFixnum do it_should_behave_like :atomic_fixnum end @@ -227,7 +225,7 @@ module Concurrent it 'inherits from JavaAtomicFixnum' do expect(AtomicFixnum.ancestors).to include(JavaAtomicFixnum) end - elsif defined? Concurrent::CAtomicFixnum + elsif Concurrent.allow_c_extensions? it 'inherits from CAtomicFixnum' do expect(AtomicFixnum.ancestors).to include(CAtomicFixnum) end diff --git a/spec/concurrent/atomic/atomic_reference_spec.rb b/spec/concurrent/atomic/atomic_reference_spec.rb index 37f95e10a..a2f3a4a14 100644 --- a/spec/concurrent/atomic/atomic_reference_spec.rb +++ b/spec/concurrent/atomic/atomic_reference_spec.rb @@ -161,17 +161,17 @@ module Concurrent it_should_behave_like :atomic_reference end - if defined? Concurrent::CAtomicReference + if Concurrent.allow_c_extensions? RSpec.describe CAtomicReference do it_should_behave_like :atomic_reference end end - if defined? Concurrent::JavaAtomicReference + if Concurrent.on_jruby? RSpec.describe JavaAtomicReference do it_should_behave_like :atomic_reference end end - if defined? Concurrent::TruffleRubyAtomicReference + if Concurrent.on_truffleruby? RSpec.describe TruffleRubyAtomicReference do it_should_behave_like :atomic_reference end diff --git a/spec/concurrent/synchronization_spec.rb b/spec/concurrent/synchronization_spec.rb index 4af7e795d..3ddccf8e8 100644 --- a/spec/concurrent/synchronization_spec.rb +++ b/spec/concurrent/synchronization_spec.rb @@ -1,4 +1,5 @@ require 'timeout' +require 'concurrent/synchronization' module Concurrent @@ -52,22 +53,22 @@ class ADClass < ACClass end it 'does not ensure visibility when not needed' do - expect_any_instance_of(AAClass).not_to receive(:full_memory_barrier) + expect(Concurrent::Synchronization).not_to receive(:full_memory_barrier) AAClass.new end it "does ensure visibility when specified" do - expect_any_instance_of(ABClass).to receive(:full_memory_barrier) + expect(Concurrent::Synchronization).to receive(:full_memory_barrier).exactly(:once) ABClass.new end it "does ensure visibility when specified in a parent" do - expect_any_instance_of(ACClass).to receive(:full_memory_barrier) + expect(Concurrent::Synchronization).to receive(:full_memory_barrier).exactly(:once) ACClass.new end it "does ensure visibility once when specified in child again" do - expect_any_instance_of(ADClass).to receive(:full_memory_barrier) + expect(Concurrent::Synchronization).to receive(:full_memory_barrier).exactly(:once) ADClass.new end @@ -180,6 +181,7 @@ def ns_initialize end specify 'final field always visible' do + require 'concurrent/atomic/count_down_latch' store = BClass.new 'asd' done = CountDownLatch.new in_thread do