concurrent-ruby 0.8.0 → 0.9.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -2
  3. data/README.md +103 -54
  4. data/lib/concurrent.rb +34 -14
  5. data/lib/concurrent/async.rb +164 -50
  6. data/lib/concurrent/atom.rb +171 -0
  7. data/lib/concurrent/atomic/atomic_boolean.rb +57 -107
  8. data/lib/concurrent/atomic/atomic_fixnum.rb +73 -101
  9. data/lib/concurrent/atomic/atomic_reference.rb +49 -0
  10. data/lib/concurrent/atomic/condition.rb +23 -12
  11. data/lib/concurrent/atomic/count_down_latch.rb +23 -21
  12. data/lib/concurrent/atomic/cyclic_barrier.rb +47 -47
  13. data/lib/concurrent/atomic/event.rb +33 -42
  14. data/lib/concurrent/atomic/read_write_lock.rb +252 -0
  15. data/lib/concurrent/atomic/semaphore.rb +64 -89
  16. data/lib/concurrent/atomic/thread_local_var.rb +130 -58
  17. data/lib/concurrent/atomic/thread_local_var/weak_key_map.rb +236 -0
  18. data/lib/concurrent/atomic_reference/direct_update.rb +3 -0
  19. data/lib/concurrent/atomic_reference/jruby.rb +6 -3
  20. data/lib/concurrent/atomic_reference/mutex_atomic.rb +10 -32
  21. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +3 -0
  22. data/lib/concurrent/atomic_reference/rbx.rb +4 -1
  23. data/lib/concurrent/atomic_reference/ruby.rb +6 -3
  24. data/lib/concurrent/atomics.rb +74 -4
  25. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +115 -0
  26. data/lib/concurrent/collection/copy_on_write_observer_set.rb +119 -0
  27. data/lib/concurrent/collection/priority_queue.rb +300 -245
  28. data/lib/concurrent/concern/deprecation.rb +27 -0
  29. data/lib/concurrent/concern/dereferenceable.rb +88 -0
  30. data/lib/concurrent/concern/logging.rb +25 -0
  31. data/lib/concurrent/concern/obligation.rb +228 -0
  32. data/lib/concurrent/concern/observable.rb +85 -0
  33. data/lib/concurrent/configuration.rb +226 -112
  34. data/lib/concurrent/dataflow.rb +2 -3
  35. data/lib/concurrent/delay.rb +141 -50
  36. data/lib/concurrent/edge.rb +30 -0
  37. data/lib/concurrent/errors.rb +10 -0
  38. data/lib/concurrent/exchanger.rb +25 -1
  39. data/lib/concurrent/executor/cached_thread_pool.rb +46 -33
  40. data/lib/concurrent/executor/executor.rb +46 -299
  41. data/lib/concurrent/executor/executor_service.rb +521 -0
  42. data/lib/concurrent/executor/fixed_thread_pool.rb +206 -26
  43. data/lib/concurrent/executor/immediate_executor.rb +9 -9
  44. data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
  45. data/lib/concurrent/executor/java_cached_thread_pool.rb +18 -16
  46. data/lib/concurrent/executor/java_fixed_thread_pool.rb +11 -18
  47. data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
  48. data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
  49. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +9 -18
  50. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +10 -21
  51. data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
  52. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
  53. data/lib/concurrent/executor/safe_task_executor.rb +5 -4
  54. data/lib/concurrent/executor/serialized_execution.rb +22 -18
  55. data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
  56. data/lib/concurrent/executor/single_thread_executor.rb +32 -21
  57. data/lib/concurrent/executor/thread_pool_executor.rb +72 -60
  58. data/lib/concurrent/executor/timer_set.rb +96 -84
  59. data/lib/concurrent/executors.rb +1 -1
  60. data/lib/concurrent/future.rb +70 -38
  61. data/lib/concurrent/immutable_struct.rb +89 -0
  62. data/lib/concurrent/ivar.rb +152 -60
  63. data/lib/concurrent/lazy_register.rb +40 -20
  64. data/lib/concurrent/maybe.rb +226 -0
  65. data/lib/concurrent/mutable_struct.rb +227 -0
  66. data/lib/concurrent/mvar.rb +44 -43
  67. data/lib/concurrent/promise.rb +208 -134
  68. data/lib/concurrent/scheduled_task.rb +339 -43
  69. data/lib/concurrent/settable_struct.rb +127 -0
  70. data/lib/concurrent/synchronization.rb +17 -0
  71. data/lib/concurrent/synchronization/abstract_object.rb +163 -0
  72. data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
  73. data/lib/concurrent/synchronization/condition.rb +53 -0
  74. data/lib/concurrent/synchronization/java_object.rb +35 -0
  75. data/lib/concurrent/synchronization/lock.rb +32 -0
  76. data/lib/concurrent/synchronization/monitor_object.rb +24 -0
  77. data/lib/concurrent/synchronization/mutex_object.rb +43 -0
  78. data/lib/concurrent/synchronization/object.rb +78 -0
  79. data/lib/concurrent/synchronization/rbx_object.rb +75 -0
  80. data/lib/concurrent/timer_task.rb +87 -100
  81. data/lib/concurrent/tvar.rb +42 -38
  82. data/lib/concurrent/utilities.rb +3 -1
  83. data/lib/concurrent/utility/at_exit.rb +97 -0
  84. data/lib/concurrent/utility/engine.rb +40 -0
  85. data/lib/concurrent/utility/monotonic_time.rb +59 -0
  86. data/lib/concurrent/utility/native_extension_loader.rb +56 -0
  87. data/lib/concurrent/utility/processor_counter.rb +156 -0
  88. data/lib/concurrent/utility/timeout.rb +18 -14
  89. data/lib/concurrent/utility/timer.rb +11 -6
  90. data/lib/concurrent/version.rb +2 -1
  91. data/lib/concurrent_ruby.rb +1 -0
  92. metadata +47 -83
  93. data/lib/concurrent/actor.rb +0 -103
  94. data/lib/concurrent/actor/behaviour.rb +0 -70
  95. data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
  96. data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
  97. data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
  98. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
  99. data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
  100. data/lib/concurrent/actor/behaviour/linking.rb +0 -45
  101. data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
  102. data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
  103. data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
  104. data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
  105. data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
  106. data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
  107. data/lib/concurrent/actor/behaviour/termination.rb +0 -54
  108. data/lib/concurrent/actor/context.rb +0 -154
  109. data/lib/concurrent/actor/core.rb +0 -217
  110. data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
  111. data/lib/concurrent/actor/envelope.rb +0 -41
  112. data/lib/concurrent/actor/errors.rb +0 -27
  113. data/lib/concurrent/actor/internal_delegations.rb +0 -49
  114. data/lib/concurrent/actor/public_delegations.rb +0 -40
  115. data/lib/concurrent/actor/reference.rb +0 -81
  116. data/lib/concurrent/actor/root.rb +0 -37
  117. data/lib/concurrent/actor/type_check.rb +0 -48
  118. data/lib/concurrent/actor/utils.rb +0 -10
  119. data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
  120. data/lib/concurrent/actor/utils/balancer.rb +0 -42
  121. data/lib/concurrent/actor/utils/broadcast.rb +0 -52
  122. data/lib/concurrent/actor/utils/pool.rb +0 -59
  123. data/lib/concurrent/actress.rb +0 -3
  124. data/lib/concurrent/agent.rb +0 -209
  125. data/lib/concurrent/atomic.rb +0 -92
  126. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
  127. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
  128. data/lib/concurrent/atomic/synchronization.rb +0 -51
  129. data/lib/concurrent/channel/buffered_channel.rb +0 -85
  130. data/lib/concurrent/channel/channel.rb +0 -41
  131. data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
  132. data/lib/concurrent/channel/waitable_list.rb +0 -40
  133. data/lib/concurrent/channels.rb +0 -5
  134. data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
  135. data/lib/concurrent/collection/ring_buffer.rb +0 -59
  136. data/lib/concurrent/collections.rb +0 -3
  137. data/lib/concurrent/dereferenceable.rb +0 -108
  138. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
  139. data/lib/concurrent/logging.rb +0 -20
  140. data/lib/concurrent/obligation.rb +0 -171
  141. data/lib/concurrent/observable.rb +0 -73
  142. data/lib/concurrent/options_parser.rb +0 -52
  143. data/lib/concurrent/utility/processor_count.rb +0 -152
  144. data/lib/extension_helper.rb +0 -37
@@ -0,0 +1,27 @@
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
14
+ klass = if Class === self
15
+ self
16
+ else
17
+ self.class
18
+ end
19
+ log WARN, klass.to_s, format("[DEPRECATED] %s\ncalled on: %s", message, caller_line)
20
+ end
21
+
22
+ def deprecated_method(old_name, new_name)
23
+ deprecated "`#{old_name}` is deprecated and it'll removed in next release, use `#{new_name}` instead", 3
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,88 @@
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
+
13
+ # Return the value this object represents after applying the options specified
14
+ # by the `#set_deref_options` method.
15
+ #
16
+ # @return [Object] the current value of the object
17
+ def value
18
+ mutex.synchronize { apply_deref_options(@value) }
19
+ end
20
+ alias_method :deref, :value
21
+
22
+ protected
23
+
24
+ # Set the internal value of this object
25
+ #
26
+ # @param [Object] value the new value
27
+ def value=(value)
28
+ mutex.synchronize{ @value = value }
29
+ end
30
+
31
+ # A mutex lock used for synchronizing thread-safe operations. Methods defined
32
+ # by `Dereferenceable` are synchronized using the `Mutex` returned from this
33
+ # method. Operations performed by the including class that operate on the
34
+ # `@value` instance variable should be locked with this `Mutex`.
35
+ #
36
+ # @return [Mutex] the synchronization object
37
+ def mutex
38
+ @mutex
39
+ end
40
+
41
+ # Initializes the internal `Mutex`.
42
+ #
43
+ # @note This method *must* be called from within the constructor of the including class.
44
+ #
45
+ # @see #mutex
46
+ def init_mutex(mutex = Mutex.new)
47
+ @mutex = mutex
48
+ end
49
+
50
+ # @!macro [attach] dereferenceable_set_deref_options
51
+ # Set the options which define the operations #value performs before
52
+ # returning data to the caller (dereferencing).
53
+ #
54
+ # @note Most classes that include this module will call `#set_deref_options`
55
+ # from within the constructor, thus allowing these options to be set at
56
+ # object creation.
57
+ #
58
+ # @param [Hash] opts the options defining dereference behavior.
59
+ # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
60
+ # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
61
+ # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing
62
+ # the internal value and returning the value returned from the proc
63
+ def set_deref_options(opts = {})
64
+ mutex.synchronize{ ns_set_deref_options(opts) }
65
+ end
66
+
67
+ # @!macro dereferenceable_set_deref_options
68
+ # @!visibility private
69
+ def ns_set_deref_options(opts)
70
+ @dup_on_deref = opts[:dup_on_deref] || opts[:dup]
71
+ @freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze]
72
+ @copy_on_deref = opts[:copy_on_deref] || opts[:copy]
73
+ @do_nothing_on_deref = !(@dup_on_deref || @freeze_on_deref || @copy_on_deref)
74
+ nil
75
+ end
76
+
77
+ # @!visibility private
78
+ def apply_deref_options(value)
79
+ return nil if value.nil?
80
+ return value if @do_nothing_on_deref
81
+ value = @copy_on_deref.call(value) if @copy_on_deref
82
+ value = value.dup if @dup_on_deref
83
+ value = value.freeze if @freeze_on_deref
84
+ value
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,25 @@
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 {Configuration#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
+ (@logger || Concurrent.global_logger).call level, progname, message, &block
19
+ rescue => error
20
+ $stderr.puts "`Concurrent.configuration.logger` failed to log #{[level, progname, message, block]}\n" +
21
+ "#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,228 @@
1
+ require 'thread'
2
+ require 'timeout'
3
+
4
+ require 'concurrent/atomic/event'
5
+ require 'concurrent/concern/dereferenceable'
6
+ require 'concurrent/concern/deprecation'
7
+
8
+ module Concurrent
9
+ module Concern
10
+
11
+ module Obligation
12
+ include Concern::Dereferenceable
13
+ include Concern::Deprecation
14
+
15
+ # Has the obligation been fulfilled?
16
+ #
17
+ # @return [Boolean]
18
+ def fulfilled?
19
+ state == :fulfilled
20
+ end
21
+ alias_method :realized?, :fulfilled?
22
+
23
+ # Has the obligation been rejected?
24
+ #
25
+ # @return [Boolean]
26
+ def rejected?
27
+ state == :rejected
28
+ end
29
+
30
+ # Is obligation completion still pending?
31
+ #
32
+ # @return [Boolean]
33
+ def pending?
34
+ state == :pending
35
+ end
36
+
37
+ # Is the obligation still unscheduled?
38
+ #
39
+ # @return [Boolean]
40
+ def unscheduled?
41
+ state == :unscheduled
42
+ end
43
+
44
+ # Has the obligation completed processing?
45
+ #
46
+ # @return [Boolean]
47
+ def complete?
48
+ [:fulfilled, :rejected].include? state
49
+ end
50
+
51
+ # Has the obligation completed processing?
52
+ #
53
+ # @return [Boolean]
54
+ #
55
+ # @deprecated
56
+ def completed?
57
+ deprecated_method 'completed?', 'complete?'
58
+ complete?
59
+ end
60
+
61
+ # Is the obligation still awaiting completion of processing?
62
+ #
63
+ # @return [Boolean]
64
+ def incomplete?
65
+ ! complete?
66
+ end
67
+
68
+ # The current value of the obligation. Will be `nil` while the state is
69
+ # pending or the operation has been rejected.
70
+ #
71
+ # @param [Numeric] timeout the maximum time in seconds to wait.
72
+ # @return [Object] see Dereferenceable#deref
73
+ def value(timeout = nil)
74
+ wait timeout
75
+ deref
76
+ end
77
+
78
+ # Wait until obligation is complete or the timeout has been reached.
79
+ #
80
+ # @param [Numeric] timeout the maximum time in seconds to wait.
81
+ # @return [Obligation] self
82
+ def wait(timeout = nil)
83
+ event.wait(timeout) if timeout != 0 && incomplete?
84
+ self
85
+ end
86
+
87
+ # Wait until obligation is complete or the timeout is reached. Will re-raise
88
+ # any exceptions raised during processing (but will not raise an exception
89
+ # on timeout).
90
+ #
91
+ # @param [Numeric] timeout the maximum time in seconds to wait.
92
+ # @return [Obligation] self
93
+ # @raise [Exception] raises the reason when rejected
94
+ def wait!(timeout = nil)
95
+ wait(timeout).tap { raise self if rejected? }
96
+ end
97
+ alias_method :no_error!, :wait!
98
+
99
+ # The current value of the obligation. Will be `nil` while the state is
100
+ # pending or the operation has been rejected. Will re-raise any exceptions
101
+ # raised during processing (but will not raise an exception on timeout).
102
+ #
103
+ # @param [Numeric] timeout the maximum time in seconds to wait.
104
+ # @return [Object] see Dereferenceable#deref
105
+ # @raise [Exception] raises the reason when rejected
106
+ def value!(timeout = nil)
107
+ wait(timeout)
108
+ if rejected?
109
+ raise self
110
+ else
111
+ deref
112
+ end
113
+ end
114
+
115
+ # The current state of the obligation.
116
+ #
117
+ # @return [Symbol] the current state
118
+ def state
119
+ mutex.synchronize { @state }
120
+ end
121
+
122
+ # If an exception was raised during processing this will return the
123
+ # exception object. Will return `nil` when the state is pending or if
124
+ # the obligation has been successfully fulfilled.
125
+ #
126
+ # @return [Exception] the exception raised during processing or `nil`
127
+ def reason
128
+ mutex.synchronize { @reason }
129
+ end
130
+
131
+ # @example allows Obligation to be risen
132
+ # rejected_ivar = Ivar.new.fail
133
+ # raise rejected_ivar
134
+ def exception(*args)
135
+ raise 'obligation is not rejected' unless rejected?
136
+ reason.exception(*args)
137
+ end
138
+
139
+ protected
140
+
141
+ # @!visibility private
142
+ def get_arguments_from(opts = {})
143
+ [*opts.fetch(:args, [])]
144
+ end
145
+
146
+ # @!visibility private
147
+ def init_obligation(*args)
148
+ init_mutex(*args)
149
+ @event = Event.new
150
+ end
151
+
152
+ # @!visibility private
153
+ def event
154
+ @event
155
+ end
156
+
157
+ # @!visibility private
158
+ def set_state(success, value, reason)
159
+ if success
160
+ @value = value
161
+ @state = :fulfilled
162
+ else
163
+ @reason = reason
164
+ @state = :rejected
165
+ end
166
+ end
167
+
168
+ # @!visibility private
169
+ def state=(value)
170
+ mutex.synchronize { ns_set_state(value) }
171
+ end
172
+
173
+ # Atomic compare and set operation
174
+ # State is set to `next_state` only if `current state == expected_current`.
175
+ #
176
+ # @param [Symbol] next_state
177
+ # @param [Symbol] expected_current
178
+ #
179
+ # @return [Boolean] true is state is changed, false otherwise
180
+ #
181
+ # @!visibility private
182
+ def compare_and_set_state(next_state, *expected_current)
183
+ mutex.synchronize do
184
+ if expected_current.include? @state
185
+ @state = next_state
186
+ true
187
+ else
188
+ false
189
+ end
190
+ end
191
+ end
192
+
193
+ # Executes the block within mutex if current state is included in expected_states
194
+ #
195
+ # @return block value if executed, false otherwise
196
+ #
197
+ # @!visibility private
198
+ def if_state(*expected_states)
199
+ mutex.synchronize do
200
+ raise ArgumentError.new('no block given') unless block_given?
201
+
202
+ if expected_states.include? @state
203
+ yield
204
+ else
205
+ false
206
+ end
207
+ end
208
+ end
209
+
210
+ protected
211
+
212
+ # Am I in the current state?
213
+ #
214
+ # @param [Symbol] expected The state to check against
215
+ # @return [Boolean] true if in the expected state else false
216
+ #
217
+ # @!visibility private
218
+ def ns_check_state?(expected)
219
+ @state == expected
220
+ end
221
+
222
+ # @!visibility private
223
+ def ns_set_state(value)
224
+ @state = value
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,85 @@
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: CopyOnWriteObserverSet and
25
+ # 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
+ # @return [Object] the added observer
53
+ def add_observer(*args, &block)
54
+ observers.add_observer(*args, &block)
55
+ end
56
+
57
+ # as #add_observer but it can be used for chaining
58
+ # @return [Observable] self
59
+ def with_observer(*args, &block)
60
+ add_observer(*args, &block)
61
+ self
62
+ end
63
+
64
+ # @return [Object] the deleted observer
65
+ def delete_observer(*args)
66
+ observers.delete_observer(*args)
67
+ end
68
+
69
+ # @return [Observable] self
70
+ def delete_observers
71
+ observers.delete_observers
72
+ self
73
+ end
74
+
75
+ # @return [Integer] the observers count
76
+ def count_observers
77
+ observers.count_observers
78
+ end
79
+
80
+ protected
81
+
82
+ attr_accessor :observers
83
+ end
84
+ end
85
+ end