File: atomic_markable_reference.rb

package info (click to toggle)
ruby-concurrent 1.0.5-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 4,200 kB
  • sloc: ruby: 27,502; java: 6,085; ansic: 282; sh: 82; makefile: 4
file content (184 lines) | stat: -rw-r--r-- 6,856 bytes parent folder | download
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
184
module Concurrent
  module Edge

    # @!macro [attach] atomic_markable_reference
    #
    #   An atomic reference which maintains an object reference along with a mark bit
    #   that can be updated atomically.
    #
    #   @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicMarkableReference.html
    #     java.util.concurrent.atomic.AtomicMarkableReference
    class AtomicMarkableReference < ::Concurrent::Synchronization::Object

      private(*attr_atomic(:reference))

      # @!macro [attach] atomic_markable_reference_method_initialize
      def initialize(value = nil, mark = false)
        super()
        self.reference = ImmutableArray[value, mark]
      end

      # @!macro [attach] atomic_markable_reference_method_compare_and_set
      #
      #   Atomically sets the value and mark to the given updated value and
      #   mark given both:
      #     - the current value == the expected value &&
      #     - the current mark == the expected mark
      #
      #   @param [Object] expected_val the expected value
      #   @param [Object] new_val the new value
      #   @param [Boolean] expected_mark the expected mark
      #   @param [Boolean] new_mark the new mark
      #
      #   @return [Boolean] `true` if successful. A `false` return indicates
      #   that the actual value was not equal to the expected value or the
      #   actual mark was not equal to the expected mark
      def compare_and_set(expected_val, new_val, expected_mark, new_mark)
        # Memoize a valid reference to the current AtomicReference for
        # later comparison.
        current = reference
        curr_val, curr_mark = current

        # Ensure that that the expected marks match.
        return false unless expected_mark == curr_mark

        if expected_val.is_a? Numeric
          # If the object is a numeric, we need to ensure we are comparing
          # the numerical values
          return false unless expected_val == curr_val
        else
          # Otherwise, we need to ensure we are comparing the object identity.
          # Theoretically, this could be incorrect if a user monkey-patched
          # `Object#equal?`, but they should know that they are playing with
          # fire at that point.
          return false unless expected_val.equal? curr_val
        end

        prospect = ImmutableArray[new_val, new_mark]

        compare_and_set_reference current, prospect
      end
      alias_method :compare_and_swap, :compare_and_set

      # @!macro [attach] atomic_markable_reference_method_get
      #
      #   Gets the current reference and marked values.
      #
      #   @return [ImmutableArray] the current reference and marked values
      def get
        reference
      end

      # @!macro [attach] atomic_markable_reference_method_value
      #
      #   Gets the current value of the reference
      #
      #   @return [Object] the current value of the reference
      def value
        reference[0]
      end

      # @!macro [attach] atomic_markable_reference_method_mark
      #
      #   Gets the current marked value
      #
      #   @return [Boolean] the current marked value
      def mark
        reference[1]
      end
      alias_method :marked?, :mark

      # @!macro [attach] atomic_markable_reference_method_set
      #
      #   _Unconditionally_ sets to the given value of both the reference and
      #   the mark.
      #
      #   @param [Object] new_val the new value
      #   @param [Boolean] new_mark the new mark
      #
      #   @return [ImmutableArray] both the new value and the new mark
      def set(new_val, new_mark)
        self.reference = ImmutableArray[new_val, new_mark]
      end

      # @!macro [attach] atomic_markable_reference_method_update
      #
      # Pass the current value and marked state to the given block, replacing it
      # with the block's results. May retry if the value changes during the
      # block's execution.
      #
      # @yield [Object] Calculate a new value and marked state for the atomic
      #   reference using given (old) value and (old) marked
      # @yieldparam [Object] old_val the starting value of the atomic reference
      # @yieldparam [Boolean] old_mark the starting state of marked
      #
      # @return [ImmutableArray] the new value and new mark
      def update
        loop do
          old_val, old_mark = reference
          new_val, new_mark = yield old_val, old_mark

          if compare_and_set old_val, new_val, old_mark, new_mark
            return ImmutableArray[new_val, new_mark]
          end
        end
      end

      # @!macro [attach] atomic_markable_reference_method_try_update!
      #
      # Pass the current value to the given block, replacing it
      # with the block's result. Raise an exception if the update
      # fails.
      #
      # @yield [Object] Calculate a new value and marked state for the atomic
      #   reference using given (old) value and (old) marked
      # @yieldparam [Object] old_val the starting value of the atomic reference
      # @yieldparam [Boolean] old_mark the starting state of marked
      #
      # @return [ImmutableArray] the new value and marked state
      #
      # @raise [Concurrent::ConcurrentUpdateError] if the update fails
      def try_update!
        old_val, old_mark = reference
        new_val, new_mark = yield old_val, old_mark

        unless compare_and_set old_val, new_val, old_mark, new_mark
          fail ::Concurrent::ConcurrentUpdateError,
               'AtomicMarkableReference: Update failed due to race condition.',
               'Note: If you would like to guarantee an update, please use ' \
               'the `AtomicMarkableReference#update` method.'
        end

        ImmutableArray[new_val, new_mark]
      end

      # @!macro [attach] atomic_markable_reference_method_try_update
      #
      # Pass the current value to the given block, replacing it with the
      # block's result. Simply return nil if update fails.
      #
      # @yield [Object] Calculate a new value and marked state for the atomic
      #   reference using given (old) value and (old) marked
      # @yieldparam [Object] old_val the starting value of the atomic reference
      # @yieldparam [Boolean] old_mark the starting state of marked
      #
      # @return [ImmutableArray] the new value and marked state, or nil if
      # the update failed
      def try_update
        old_val, old_mark = reference
        new_val, new_mark = yield old_val, old_mark

        return unless compare_and_set old_val, new_val, old_mark, new_mark

        ImmutableArray[new_val, new_mark]
      end

      # Internal/private ImmutableArray for representing pairs
      class ImmutableArray < ::Array
        def self.new(*args)
          super(*args).freeze
        end
      end
    end
  end
end