File: object.rb

package info (click to toggle)
ruby-concurrent 1.1.6%2Bdfsg-5
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 30,284 kB
  • sloc: ruby: 30,875; java: 6,117; javascript: 1,114; ansic: 288; makefile: 10; sh: 6
file content (183 lines) | stat: -rw-r--r-- 7,018 bytes parent folder | download | duplicates (3)
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