concurrent-ruby 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +478 -0
  3. data/Gemfile +41 -0
  4. data/LICENSE.md +23 -0
  5. data/README.md +381 -0
  6. data/Rakefile +327 -0
  7. data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
  10. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
  11. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
  12. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +159 -0
  13. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
  14. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
  15. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
  16. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
  17. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
  18. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
  19. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
  20. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
  21. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
  22. data/lib/concurrent-ruby.rb +1 -0
  23. data/lib/concurrent.rb +134 -0
  24. data/lib/concurrent/agent.rb +587 -0
  25. data/lib/concurrent/array.rb +66 -0
  26. data/lib/concurrent/async.rb +459 -0
  27. data/lib/concurrent/atom.rb +222 -0
  28. data/lib/concurrent/atomic/abstract_thread_local_var.rb +66 -0
  29. data/lib/concurrent/atomic/atomic_boolean.rb +126 -0
  30. data/lib/concurrent/atomic/atomic_fixnum.rb +143 -0
  31. data/lib/concurrent/atomic/atomic_markable_reference.rb +164 -0
  32. data/lib/concurrent/atomic/atomic_reference.rb +204 -0
  33. data/lib/concurrent/atomic/count_down_latch.rb +100 -0
  34. data/lib/concurrent/atomic/cyclic_barrier.rb +128 -0
  35. data/lib/concurrent/atomic/event.rb +109 -0
  36. data/lib/concurrent/atomic/java_count_down_latch.rb +42 -0
  37. data/lib/concurrent/atomic/java_thread_local_var.rb +37 -0
  38. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
  39. data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
  40. data/lib/concurrent/atomic/mutex_count_down_latch.rb +44 -0
  41. data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
  42. data/lib/concurrent/atomic/read_write_lock.rb +254 -0
  43. data/lib/concurrent/atomic/reentrant_read_write_lock.rb +379 -0
  44. data/lib/concurrent/atomic/ruby_thread_local_var.rb +161 -0
  45. data/lib/concurrent/atomic/semaphore.rb +145 -0
  46. data/lib/concurrent/atomic/thread_local_var.rb +104 -0
  47. data/lib/concurrent/atomic_reference/mutex_atomic.rb +56 -0
  48. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
  49. data/lib/concurrent/atomics.rb +10 -0
  50. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
  51. data/lib/concurrent/collection/copy_on_write_observer_set.rb +111 -0
  52. data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  53. data/lib/concurrent/collection/lock_free_stack.rb +158 -0
  54. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  55. data/lib/concurrent/collection/map/mri_map_backend.rb +66 -0
  56. data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
  57. data/lib/concurrent/collection/map/synchronized_map_backend.rb +82 -0
  58. data/lib/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  59. data/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb +150 -0
  60. data/lib/concurrent/concern/deprecation.rb +34 -0
  61. data/lib/concurrent/concern/dereferenceable.rb +73 -0
  62. data/lib/concurrent/concern/logging.rb +32 -0
  63. data/lib/concurrent/concern/obligation.rb +220 -0
  64. data/lib/concurrent/concern/observable.rb +110 -0
  65. data/lib/concurrent/concurrent_ruby.jar +0 -0
  66. data/lib/concurrent/configuration.rb +184 -0
  67. data/lib/concurrent/constants.rb +8 -0
  68. data/lib/concurrent/dataflow.rb +81 -0
  69. data/lib/concurrent/delay.rb +199 -0
  70. data/lib/concurrent/errors.rb +69 -0
  71. data/lib/concurrent/exchanger.rb +352 -0
  72. data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
  73. data/lib/concurrent/executor/cached_thread_pool.rb +62 -0
  74. data/lib/concurrent/executor/executor_service.rb +185 -0
  75. data/lib/concurrent/executor/fixed_thread_pool.rb +206 -0
  76. data/lib/concurrent/executor/immediate_executor.rb +66 -0
  77. data/lib/concurrent/executor/indirect_immediate_executor.rb +44 -0
  78. data/lib/concurrent/executor/java_executor_service.rb +91 -0
  79. data/lib/concurrent/executor/java_single_thread_executor.rb +29 -0
  80. data/lib/concurrent/executor/java_thread_pool_executor.rb +123 -0
  81. data/lib/concurrent/executor/ruby_executor_service.rb +78 -0
  82. data/lib/concurrent/executor/ruby_single_thread_executor.rb +22 -0
  83. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +362 -0
  84. data/lib/concurrent/executor/safe_task_executor.rb +35 -0
  85. data/lib/concurrent/executor/serial_executor_service.rb +34 -0
  86. data/lib/concurrent/executor/serialized_execution.rb +107 -0
  87. data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
  88. data/lib/concurrent/executor/simple_executor_service.rb +100 -0
  89. data/lib/concurrent/executor/single_thread_executor.rb +56 -0
  90. data/lib/concurrent/executor/thread_pool_executor.rb +87 -0
  91. data/lib/concurrent/executor/timer_set.rb +173 -0
  92. data/lib/concurrent/executors.rb +20 -0
  93. data/lib/concurrent/future.rb +141 -0
  94. data/lib/concurrent/hash.rb +59 -0
  95. data/lib/concurrent/immutable_struct.rb +93 -0
  96. data/lib/concurrent/ivar.rb +207 -0
  97. data/lib/concurrent/map.rb +337 -0
  98. data/lib/concurrent/maybe.rb +229 -0
  99. data/lib/concurrent/mutable_struct.rb +229 -0
  100. data/lib/concurrent/mvar.rb +242 -0
  101. data/lib/concurrent/options.rb +42 -0
  102. data/lib/concurrent/promise.rb +579 -0
  103. data/lib/concurrent/promises.rb +2167 -0
  104. data/lib/concurrent/re_include.rb +58 -0
  105. data/lib/concurrent/scheduled_task.rb +318 -0
  106. data/lib/concurrent/set.rb +66 -0
  107. data/lib/concurrent/settable_struct.rb +129 -0
  108. data/lib/concurrent/synchronization.rb +30 -0
  109. data/lib/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  110. data/lib/concurrent/synchronization/abstract_object.rb +24 -0
  111. data/lib/concurrent/synchronization/abstract_struct.rb +160 -0
  112. data/lib/concurrent/synchronization/condition.rb +60 -0
  113. data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  114. data/lib/concurrent/synchronization/jruby_object.rb +45 -0
  115. data/lib/concurrent/synchronization/lock.rb +36 -0
  116. data/lib/concurrent/synchronization/lockable_object.rb +74 -0
  117. data/lib/concurrent/synchronization/mri_object.rb +44 -0
  118. data/lib/concurrent/synchronization/mutex_lockable_object.rb +76 -0
  119. data/lib/concurrent/synchronization/object.rb +183 -0
  120. data/lib/concurrent/synchronization/rbx_lockable_object.rb +65 -0
  121. data/lib/concurrent/synchronization/rbx_object.rb +49 -0
  122. data/lib/concurrent/synchronization/truffleruby_object.rb +47 -0
  123. data/lib/concurrent/synchronization/volatile.rb +36 -0
  124. data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  125. data/lib/concurrent/thread_safe/util.rb +16 -0
  126. data/lib/concurrent/thread_safe/util/adder.rb +74 -0
  127. data/lib/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  128. data/lib/concurrent/thread_safe/util/data_structures.rb +63 -0
  129. data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  130. data/lib/concurrent/thread_safe/util/striped64.rb +246 -0
  131. data/lib/concurrent/thread_safe/util/volatile.rb +75 -0
  132. data/lib/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  133. data/lib/concurrent/timer_task.rb +334 -0
  134. data/lib/concurrent/tuple.rb +86 -0
  135. data/lib/concurrent/tvar.rb +258 -0
  136. data/lib/concurrent/utility/at_exit.rb +97 -0
  137. data/lib/concurrent/utility/engine.rb +56 -0
  138. data/lib/concurrent/utility/monotonic_time.rb +58 -0
  139. data/lib/concurrent/utility/native_extension_loader.rb +79 -0
  140. data/lib/concurrent/utility/native_integer.rb +53 -0
  141. data/lib/concurrent/utility/processor_counter.rb +158 -0
  142. data/lib/concurrent/version.rb +3 -0
  143. metadata +193 -0
@@ -0,0 +1,34 @@
1
+ require 'concurrent/concern/logging'
2
+
3
+ module Concurrent
4
+ module Concern
5
+
6
+ # @!visibility private
7
+ # @!macro internal_implementation_note
8
+ module Deprecation
9
+ # TODO require additional parameter: a version. Display when it'll be removed based on that. Error if not removed.
10
+ include Concern::Logging
11
+
12
+ def deprecated(message, strip = 2)
13
+ caller_line = caller(strip).first if strip > 0
14
+ klass = if Module === self
15
+ self
16
+ else
17
+ self.class
18
+ end
19
+ message = if strip > 0
20
+ format("[DEPRECATED] %s\ncalled on: %s", message, caller_line)
21
+ else
22
+ format('[DEPRECATED] %s', message)
23
+ end
24
+ log WARN, klass.to_s, message
25
+ end
26
+
27
+ def deprecated_method(old_name, new_name)
28
+ deprecated "`#{old_name}` is deprecated and it'll removed in next release, use `#{new_name}` instead", 3
29
+ end
30
+
31
+ extend self
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,73 @@
1
+ module Concurrent
2
+ module Concern
3
+
4
+ # Object references in Ruby are mutable. This can lead to serious problems when
5
+ # the `#value` of a concurrent object is a mutable reference. Which is always the
6
+ # case unless the value is a `Fixnum`, `Symbol`, or similar "primitive" data type.
7
+ # Most classes in this library that expose a `#value` getter method do so using the
8
+ # `Dereferenceable` mixin module.
9
+ #
10
+ # @!macro copy_options
11
+ module Dereferenceable
12
+ # NOTE: This module is going away in 2.0. In the mean time we need it to
13
+ # play nicely with the synchronization layer. This means that the
14
+ # including class SHOULD be synchronized and it MUST implement a
15
+ # `#synchronize` method. Not doing so will lead to runtime errors.
16
+
17
+ # Return the value this object represents after applying the options specified
18
+ # by the `#set_deref_options` method.
19
+ #
20
+ # @return [Object] the current value of the object
21
+ def value
22
+ synchronize { apply_deref_options(@value) }
23
+ end
24
+ alias_method :deref, :value
25
+
26
+ protected
27
+
28
+ # Set the internal value of this object
29
+ #
30
+ # @param [Object] value the new value
31
+ def value=(value)
32
+ synchronize{ @value = value }
33
+ end
34
+
35
+ # @!macro dereferenceable_set_deref_options
36
+ # Set the options which define the operations #value performs before
37
+ # returning data to the caller (dereferencing).
38
+ #
39
+ # @note Most classes that include this module will call `#set_deref_options`
40
+ # from within the constructor, thus allowing these options to be set at
41
+ # object creation.
42
+ #
43
+ # @param [Hash] opts the options defining dereference behavior.
44
+ # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
45
+ # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
46
+ # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing
47
+ # the internal value and returning the value returned from the proc
48
+ def set_deref_options(opts = {})
49
+ synchronize{ ns_set_deref_options(opts) }
50
+ end
51
+
52
+ # @!macro dereferenceable_set_deref_options
53
+ # @!visibility private
54
+ def ns_set_deref_options(opts)
55
+ @dup_on_deref = opts[:dup_on_deref] || opts[:dup]
56
+ @freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze]
57
+ @copy_on_deref = opts[:copy_on_deref] || opts[:copy]
58
+ @do_nothing_on_deref = !(@dup_on_deref || @freeze_on_deref || @copy_on_deref)
59
+ nil
60
+ end
61
+
62
+ # @!visibility private
63
+ def apply_deref_options(value)
64
+ return nil if value.nil?
65
+ return value if @do_nothing_on_deref
66
+ value = @copy_on_deref.call(value) if @copy_on_deref
67
+ value = value.dup if @dup_on_deref
68
+ value = value.freeze if @freeze_on_deref
69
+ value
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,32 @@
1
+ require 'logger'
2
+
3
+ module Concurrent
4
+ module Concern
5
+
6
+ # Include where logging is needed
7
+ #
8
+ # @!visibility private
9
+ module Logging
10
+ include Logger::Severity
11
+
12
+ # Logs through {Concurrent.global_logger}, it can be overridden by setting @logger
13
+ # @param [Integer] level one of Logger::Severity constants
14
+ # @param [String] progname e.g. a path of an Actor
15
+ # @param [String, nil] message when nil block is used to generate the message
16
+ # @yieldreturn [String] a message
17
+ def log(level, progname, message = nil, &block)
18
+ #NOTE: Cannot require 'concurrent/configuration' above due to circular references.
19
+ # Assume that the gem has been initialized if we've gotten this far.
20
+ logger = if defined?(@logger) && @logger
21
+ @logger
22
+ else
23
+ Concurrent.global_logger
24
+ end
25
+ logger.call level, progname, message, &block
26
+ rescue => error
27
+ $stderr.puts "`Concurrent.configuration.logger` failed to log #{[level, progname, message, block]}\n" +
28
+ "#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,220 @@
1
+ require 'thread'
2
+ require 'timeout'
3
+
4
+ require 'concurrent/atomic/event'
5
+ require 'concurrent/concern/dereferenceable'
6
+
7
+ module Concurrent
8
+ module Concern
9
+
10
+ module Obligation
11
+ include Concern::Dereferenceable
12
+ # NOTE: The Dereferenceable module is going away in 2.0. In the mean time
13
+ # we need it to place nicely with the synchronization layer. This means
14
+ # that the including class SHOULD be synchronized and it MUST implement a
15
+ # `#synchronize` method. Not doing so will lead to runtime errors.
16
+
17
+ # Has the obligation been fulfilled?
18
+ #
19
+ # @return [Boolean]
20
+ def fulfilled?
21
+ state == :fulfilled
22
+ end
23
+ alias_method :realized?, :fulfilled?
24
+
25
+ # Has the obligation been rejected?
26
+ #
27
+ # @return [Boolean]
28
+ def rejected?
29
+ state == :rejected
30
+ end
31
+
32
+ # Is obligation completion still pending?
33
+ #
34
+ # @return [Boolean]
35
+ def pending?
36
+ state == :pending
37
+ end
38
+
39
+ # Is the obligation still unscheduled?
40
+ #
41
+ # @return [Boolean]
42
+ def unscheduled?
43
+ state == :unscheduled
44
+ end
45
+
46
+ # Has the obligation completed processing?
47
+ #
48
+ # @return [Boolean]
49
+ def complete?
50
+ [:fulfilled, :rejected].include? state
51
+ end
52
+
53
+ # Is the obligation still awaiting completion of processing?
54
+ #
55
+ # @return [Boolean]
56
+ def incomplete?
57
+ ! complete?
58
+ end
59
+
60
+ # The current value of the obligation. Will be `nil` while the state is
61
+ # pending or the operation has been rejected.
62
+ #
63
+ # @param [Numeric] timeout the maximum time in seconds to wait.
64
+ # @return [Object] see Dereferenceable#deref
65
+ def value(timeout = nil)
66
+ wait timeout
67
+ deref
68
+ end
69
+
70
+ # Wait until obligation is complete or the timeout has been reached.
71
+ #
72
+ # @param [Numeric] timeout the maximum time in seconds to wait.
73
+ # @return [Obligation] self
74
+ def wait(timeout = nil)
75
+ event.wait(timeout) if timeout != 0 && incomplete?
76
+ self
77
+ end
78
+
79
+ # Wait until obligation is complete or the timeout is reached. Will re-raise
80
+ # any exceptions raised during processing (but will not raise an exception
81
+ # on timeout).
82
+ #
83
+ # @param [Numeric] timeout the maximum time in seconds to wait.
84
+ # @return [Obligation] self
85
+ # @raise [Exception] raises the reason when rejected
86
+ def wait!(timeout = nil)
87
+ wait(timeout).tap { raise self if rejected? }
88
+ end
89
+ alias_method :no_error!, :wait!
90
+
91
+ # The current value of the obligation. Will be `nil` while the state is
92
+ # pending or the operation has been rejected. Will re-raise any exceptions
93
+ # raised during processing (but will not raise an exception on timeout).
94
+ #
95
+ # @param [Numeric] timeout the maximum time in seconds to wait.
96
+ # @return [Object] see Dereferenceable#deref
97
+ # @raise [Exception] raises the reason when rejected
98
+ def value!(timeout = nil)
99
+ wait(timeout)
100
+ if rejected?
101
+ raise self
102
+ else
103
+ deref
104
+ end
105
+ end
106
+
107
+ # The current state of the obligation.
108
+ #
109
+ # @return [Symbol] the current state
110
+ def state
111
+ synchronize { @state }
112
+ end
113
+
114
+ # If an exception was raised during processing this will return the
115
+ # exception object. Will return `nil` when the state is pending or if
116
+ # the obligation has been successfully fulfilled.
117
+ #
118
+ # @return [Exception] the exception raised during processing or `nil`
119
+ def reason
120
+ synchronize { @reason }
121
+ end
122
+
123
+ # @example allows Obligation to be risen
124
+ # rejected_ivar = Ivar.new.fail
125
+ # raise rejected_ivar
126
+ def exception(*args)
127
+ raise 'obligation is not rejected' unless rejected?
128
+ reason.exception(*args)
129
+ end
130
+
131
+ protected
132
+
133
+ # @!visibility private
134
+ def get_arguments_from(opts = {})
135
+ [*opts.fetch(:args, [])]
136
+ end
137
+
138
+ # @!visibility private
139
+ def init_obligation
140
+ @event = Event.new
141
+ @value = @reason = nil
142
+ end
143
+
144
+ # @!visibility private
145
+ def event
146
+ @event
147
+ end
148
+
149
+ # @!visibility private
150
+ def set_state(success, value, reason)
151
+ if success
152
+ @value = value
153
+ @state = :fulfilled
154
+ else
155
+ @reason = reason
156
+ @state = :rejected
157
+ end
158
+ end
159
+
160
+ # @!visibility private
161
+ def state=(value)
162
+ synchronize { ns_set_state(value) }
163
+ end
164
+
165
+ # Atomic compare and set operation
166
+ # State is set to `next_state` only if `current state == expected_current`.
167
+ #
168
+ # @param [Symbol] next_state
169
+ # @param [Symbol] expected_current
170
+ #
171
+ # @return [Boolean] true is state is changed, false otherwise
172
+ #
173
+ # @!visibility private
174
+ def compare_and_set_state(next_state, *expected_current)
175
+ synchronize do
176
+ if expected_current.include? @state
177
+ @state = next_state
178
+ true
179
+ else
180
+ false
181
+ end
182
+ end
183
+ end
184
+
185
+ # Executes the block within mutex if current state is included in expected_states
186
+ #
187
+ # @return block value if executed, false otherwise
188
+ #
189
+ # @!visibility private
190
+ def if_state(*expected_states)
191
+ synchronize do
192
+ raise ArgumentError.new('no block given') unless block_given?
193
+
194
+ if expected_states.include? @state
195
+ yield
196
+ else
197
+ false
198
+ end
199
+ end
200
+ end
201
+
202
+ protected
203
+
204
+ # Am I in the current state?
205
+ #
206
+ # @param [Symbol] expected The state to check against
207
+ # @return [Boolean] true if in the expected state else false
208
+ #
209
+ # @!visibility private
210
+ def ns_check_state?(expected)
211
+ @state == expected
212
+ end
213
+
214
+ # @!visibility private
215
+ def ns_set_state(value)
216
+ @state = value
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,110 @@
1
+ require 'concurrent/collection/copy_on_notify_observer_set'
2
+ require 'concurrent/collection/copy_on_write_observer_set'
3
+
4
+ module Concurrent
5
+ module Concern
6
+
7
+ # The [observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) is one
8
+ # of the most useful design patterns.
9
+ #
10
+ # The workflow is very simple:
11
+ # - an `observer` can register itself to a `subject` via a callback
12
+ # - many `observers` can be registered to the same `subject`
13
+ # - the `subject` notifies all registered observers when its status changes
14
+ # - an `observer` can deregister itself when is no more interested to receive
15
+ # event notifications
16
+ #
17
+ # In a single threaded environment the whole pattern is very easy: the
18
+ # `subject` can use a simple data structure to manage all its subscribed
19
+ # `observer`s and every `observer` can react directly to every event without
20
+ # caring about synchronization.
21
+ #
22
+ # In a multi threaded environment things are more complex. The `subject` must
23
+ # synchronize the access to its data structure and to do so currently we're
24
+ # using two specialized ObserverSet: {Concurrent::Concern::CopyOnWriteObserverSet}
25
+ # and {Concurrent::Concern::CopyOnNotifyObserverSet}.
26
+ #
27
+ # When implementing and `observer` there's a very important rule to remember:
28
+ # **there are no guarantees about the thread that will execute the callback**
29
+ #
30
+ # Let's take this example
31
+ # ```
32
+ # class Observer
33
+ # def initialize
34
+ # @count = 0
35
+ # end
36
+ #
37
+ # def update
38
+ # @count += 1
39
+ # end
40
+ # end
41
+ #
42
+ # obs = Observer.new
43
+ # [obj1, obj2, obj3, obj4].each { |o| o.add_observer(obs) }
44
+ # # execute [obj1, obj2, obj3, obj4]
45
+ # ```
46
+ #
47
+ # `obs` is wrong because the variable `@count` can be accessed by different
48
+ # threads at the same time, so it should be synchronized (using either a Mutex
49
+ # or an AtomicFixum)
50
+ module Observable
51
+
52
+ # @!macro observable_add_observer
53
+ #
54
+ # Adds an observer to this set. If a block is passed, the observer will be
55
+ # created by this method and no other params should be passed.
56
+ #
57
+ # @param [Object] observer the observer to add
58
+ # @param [Symbol] func the function to call on the observer during notification.
59
+ # Default is :update
60
+ # @return [Object] the added observer
61
+ def add_observer(observer = nil, func = :update, &block)
62
+ observers.add_observer(observer, func, &block)
63
+ end
64
+
65
+ # As `#add_observer` but can be used for chaining.
66
+ #
67
+ # @param [Object] observer the observer to add
68
+ # @param [Symbol] func the function to call on the observer during notification.
69
+ # @return [Observable] self
70
+ def with_observer(observer = nil, func = :update, &block)
71
+ add_observer(observer, func, &block)
72
+ self
73
+ end
74
+
75
+ # @!macro observable_delete_observer
76
+ #
77
+ # Remove `observer` as an observer on this object so that it will no
78
+ # longer receive notifications.
79
+ #
80
+ # @param [Object] observer the observer to remove
81
+ # @return [Object] the deleted observer
82
+ def delete_observer(observer)
83
+ observers.delete_observer(observer)
84
+ end
85
+
86
+ # @!macro observable_delete_observers
87
+ #
88
+ # Remove all observers associated with this object.
89
+ #
90
+ # @return [Observable] self
91
+ def delete_observers
92
+ observers.delete_observers
93
+ self
94
+ end
95
+
96
+ # @!macro observable_count_observers
97
+ #
98
+ # Return the number of observers associated with this object.
99
+ #
100
+ # @return [Integer] the observers count
101
+ def count_observers
102
+ observers.count_observers
103
+ end
104
+
105
+ protected
106
+
107
+ attr_accessor :observers
108
+ end
109
+ end
110
+ end