1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
|
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_rbx?
RbxObject
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
# TODO make it a module if possible
# @!method self.attr_volatile(*names)
# Creates methods for reading and writing (as `attr_accessor` does) to a instance variable with
# volatile (Java) semantic. The instance variable should be accessed only through generated methods.
#
# @param [::Array<Symbol>] names of the instance variables to be volatile
# @return [::Array<Symbol>] names of defined method names
# Has to be called by children.
def initialize
super
__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
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?)
end
# For testing purposes, quite slow. Injects assert code to new method which will raise if class instance contains
# any instance variables with CamelCase names and isn't {.safe_initialization?}.
# @raise when offend found
# @return [true]
def self.ensure_safe_initialization_when_final_fields_are_present
Object.class_eval do
def self.new(*args, &block)
object = super(*args, &block)
ensure
has_final_field = object.instance_variables.any? { |v| v.to_s =~ /^@[A-Z]/ }
if has_final_field && !safe_initialization?
raise "there was an instance of #{object.class} with final field but not marked with safe_initialization!"
end
end
end
true
end
# Creates methods for reading and writing to a instance variable with
# volatile (Java) semantic as {.attr_volatile} does.
# The instance variable should be accessed oly through generated methods.
# This method generates following methods: `value`, `value=(new_value) #=> new_value`,
# `swap_value(new_value) #=> old_value`,
# `compare_and_set_value(expected, value) #=> true || false`, `update_value(&block)`.
# @param [::Array<Symbol>] names of the instance variables to be volatile with CAS.
# @return [::Array<Symbol>] names of defined method names.
# @!macro attr_atomic
# @!method $1
# @return [Object] The $1.
# @!method $1=(new_$1)
# Set the $1.
# @return [Object] new_$1.
# @!method swap_$1(new_$1)
# Set the $1 to new_$1 and return the old $1.
# @return [Object] old $1
# @!method compare_and_set_$1(expected_$1, new_$1)
# Sets the $1 to new_$1 if the current $1 is expected_$1
# @return [true, false]
# @!method update_$1(&block)
# Updates the $1 using the block.
# @yield [Object] Calculate a new $1 using given (old) $1
# @yieldparam [Object] old $1
# @return [Object] new $1
def self.attr_atomic(*names)
@__atomic_fields__ ||= []
@__atomic_fields__ += names
safe_initialization!
define_initialize_atomic_fields
names.each do |name|
ivar = :"@Atomic#{name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }}"
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}
#{ivar}.get
end
def #{name}=(value)
#{ivar}.set value
end
def swap_#{name}(value)
#{ivar}.swap value
end
def compare_and_set_#{name}(expected, value)
#{ivar}.compare_and_set expected, value
end
def update_#{name}(&block)
#{ivar}.update(&block)
end
RUBY
end
names.flat_map { |n| [n, :"#{n}=", :"swap_#{n}", :"compare_and_set_#{n}", :"update_#{n}"] }
end
# @param [true, false] inherited should inherited volatile with CAS fields be returned?
# @return [::Array<Symbol>] Returns defined volatile with CAS fields on this class.
def self.atomic_attributes(inherited = true)
@__atomic_fields__ ||= []
((superclass.atomic_attributes if superclass.respond_to?(:atomic_attributes) && inherited) || []) + @__atomic_fields__
end
# @return [true, false] is the attribute with name atomic?
def self.atomic_attribute?(name)
atomic_attributes.include? name
end
private
def self.define_initialize_atomic_fields
assignments = @__atomic_fields__.map do |name|
"@Atomic#{name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }} = Concurrent::AtomicReference.new(nil)"
end.join("\n")
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def __initialize_atomic_fields__
super
#{assignments}
end
RUBY
end
private_class_method :define_initialize_atomic_fields
def __initialize_atomic_fields__
end
end
end
end
|