concurrent-ruby 1.0.0.pre1 → 1.0.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/README.md +16 -18
  4. data/lib/concurrent.rb +3 -3
  5. data/lib/concurrent/agent.rb +583 -0
  6. data/lib/concurrent/array.rb +1 -0
  7. data/lib/concurrent/async.rb +236 -111
  8. data/lib/concurrent/atom.rb +101 -46
  9. data/lib/concurrent/atomic/atomic_boolean.rb +2 -0
  10. data/lib/concurrent/atomic/atomic_fixnum.rb +2 -0
  11. data/lib/concurrent/atomic/cyclic_barrier.rb +1 -1
  12. data/lib/concurrent/atomic/event.rb +1 -1
  13. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +1 -1
  14. data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +1 -1
  15. data/lib/concurrent/atomic/mutex_count_down_latch.rb +1 -1
  16. data/lib/concurrent/atomic/mutex_semaphore.rb +2 -2
  17. data/lib/concurrent/atomic/read_write_lock.rb +5 -4
  18. data/lib/concurrent/atomic/reentrant_read_write_lock.rb +3 -1
  19. data/lib/concurrent/atomic/thread_local_var.rb +2 -0
  20. data/lib/concurrent/atomic_reference/mutex_atomic.rb +1 -1
  21. data/lib/concurrent/atomics.rb +6 -4
  22. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +7 -15
  23. data/lib/concurrent/collection/copy_on_write_observer_set.rb +7 -15
  24. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +5 -0
  25. data/lib/concurrent/concern/observable.rb +38 -13
  26. data/lib/concurrent/configuration.rb +5 -4
  27. data/lib/concurrent/delay.rb +9 -8
  28. data/lib/concurrent/exchanger.rb +2 -0
  29. data/lib/concurrent/executor/abstract_executor_service.rb +2 -2
  30. data/lib/concurrent/executor/java_single_thread_executor.rb +0 -1
  31. data/lib/concurrent/executor/ruby_executor_service.rb +10 -4
  32. data/lib/concurrent/executor/ruby_single_thread_executor.rb +10 -68
  33. data/lib/concurrent/executor/safe_task_executor.rb +7 -8
  34. data/lib/concurrent/executor/serialized_execution.rb +4 -4
  35. data/lib/concurrent/executor/single_thread_executor.rb +20 -10
  36. data/lib/concurrent/executor/timer_set.rb +4 -2
  37. data/lib/concurrent/executors.rb +0 -1
  38. data/lib/concurrent/future.rb +3 -2
  39. data/lib/concurrent/hash.rb +1 -1
  40. data/lib/concurrent/immutable_struct.rb +5 -1
  41. data/lib/concurrent/ivar.rb +1 -1
  42. data/lib/concurrent/mutable_struct.rb +7 -6
  43. data/lib/concurrent/{executor/executor.rb → options.rb} +4 -3
  44. data/lib/concurrent/promise.rb +3 -2
  45. data/lib/concurrent/scheduled_task.rb +3 -2
  46. data/lib/concurrent/settable_struct.rb +5 -4
  47. data/lib/concurrent/synchronization.rb +11 -3
  48. data/lib/concurrent/synchronization/abstract_lockable_object.rb +117 -0
  49. data/lib/concurrent/synchronization/abstract_object.rb +16 -129
  50. data/lib/concurrent/synchronization/abstract_struct.rb +2 -3
  51. data/lib/concurrent/synchronization/condition.rb +6 -4
  52. data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  53. data/lib/concurrent/synchronization/{java_object.rb → jruby_object.rb} +5 -3
  54. data/lib/concurrent/synchronization/lock.rb +3 -2
  55. data/lib/concurrent/synchronization/lockable_object.rb +59 -0
  56. data/lib/concurrent/synchronization/mri_lockable_object.rb +71 -0
  57. data/lib/concurrent/synchronization/mri_object.rb +35 -0
  58. data/lib/concurrent/synchronization/object.rb +111 -39
  59. data/lib/concurrent/synchronization/rbx_lockable_object.rb +64 -0
  60. data/lib/concurrent/synchronization/rbx_object.rb +17 -68
  61. data/lib/concurrent/thread_safe/util.rb +0 -9
  62. data/lib/concurrent/thread_safe/util/adder.rb +3 -0
  63. data/lib/concurrent/thread_safe/util/array_hash_rbx.rb +3 -1
  64. data/lib/concurrent/thread_safe/util/cheap_lockable.rb +3 -0
  65. data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +1 -0
  66. data/lib/concurrent/thread_safe/util/striped64.rb +6 -1
  67. data/lib/concurrent/thread_safe/util/volatile.rb +2 -0
  68. data/lib/concurrent/thread_safe/util/xor_shift_random.rb +2 -0
  69. data/lib/concurrent/tvar.rb +36 -0
  70. data/lib/concurrent/utility/at_exit.rb +1 -1
  71. data/lib/concurrent/utility/monotonic_time.rb +3 -4
  72. data/lib/concurrent/utility/native_extension_loader.rb +1 -1
  73. data/lib/concurrent/version.rb +2 -2
  74. metadata +12 -7
  75. data/lib/concurrent/synchronization/monitor_object.rb +0 -27
  76. data/lib/concurrent/synchronization/mutex_object.rb +0 -43
@@ -1,6 +1,7 @@
1
- require 'concurrent/concern/dereferenceable'
2
1
  require 'concurrent/atomic/atomic_reference'
3
- require 'concurrent/synchronization/object'
2
+ require 'concurrent/collection/copy_on_notify_observer_set'
3
+ require 'concurrent/concern/observable'
4
+ require 'concurrent/synchronization'
4
5
 
5
6
  module Concurrent
6
7
 
@@ -18,11 +19,50 @@ module Concurrent
18
19
  # new value to the result of running the given block if and only if that
19
20
  # value validates.
20
21
  #
21
- # @!macro copy_options
22
+ # ## Example
23
+ #
24
+ # ```
25
+ # def next_fibonacci(set = nil)
26
+ # return [0, 1] if set.nil?
27
+ # set + [set[-2..-1].reduce{|sum,x| sum + x }]
28
+ # end
29
+ #
30
+ # # create an atom with aninitial value
31
+ # atom = Concurrent::Atom.new(next_fibonacci)
32
+ #
33
+ # # send a few update requests
34
+ # 5.times do
35
+ # atom.swap{|set| next_fibonacci(set) }
36
+ # end
37
+ #
38
+ # # get the current value
39
+ # atom.value #=> [0, 1, 1, 2, 3, 5, 8]
40
+ # ```
41
+ #
42
+ # ## Observation
43
+ #
44
+ # Atoms support observers through the {Concurrent::Observable} mixin module.
45
+ # Notification of observers occurs every time the value of the Atom changes.
46
+ # When notified the observer will receive three arguments: `time`, `old_value`,
47
+ # and `new_value`. The `time` argument is the time at which the value change
48
+ # occurred. The `old_value` is the value of the Atom when the change began
49
+ # The `new_value` is the value to which the Atom was set when the change
50
+ # completed. Note that `old_value` and `new_value` may be the same. This is
51
+ # not an error. It simply means that the change operation returned the same
52
+ # value.
53
+ #
54
+ # Unlike in Clojure, `Atom` cannot participate in {Concurrent::TVar} transactions.
55
+ #
56
+ # @!macro thread_safe_variable_comparison
22
57
  #
23
58
  # @see http://clojure.org/atoms Clojure Atoms
59
+ # @see http://clojure.org/state Values and Change - Clojure's approach to Identity and State
24
60
  class Atom < Synchronization::Object
25
- include Concern::Dereferenceable
61
+ include Concern::Observable
62
+
63
+ safe_initialization!
64
+ private *attr_volatile_with_cas(:value)
65
+ public :value
26
66
 
27
67
  # Create a new atom with the given initial value.
28
68
  #
@@ -34,25 +74,19 @@ module Concurrent
34
74
  # is acceptable else return false (preferrably) or raise an exception.
35
75
  #
36
76
  # @!macro deref_options
37
- #
77
+ #
38
78
  # @raise [ArgumentError] if the validator is not a `Proc` (when given)
39
79
  def initialize(value, opts = {})
40
- super()
41
-
42
- @validator = opts.fetch(:validator, ->(v){ true })
43
- raise ArgumentError.new('validator must be a proc') unless @validator.is_a? Proc
44
-
45
- @value = Concurrent::AtomicReference.new(value)
46
- ns_set_deref_options(opts)
47
- ensure_ivar_visibility!
80
+ @Validator = opts.fetch(:validator, -> v { true })
81
+ self.observers = Collection::CopyOnNotifyObserverSet.new
82
+ super(value)
48
83
  end
49
84
 
50
- # The current value of the atom.
85
+ # @!method value
86
+ # The current value of the atom.
51
87
  #
52
- # @return [Object] The current value.
53
- def value
54
- apply_deref_options(@value.value)
55
- end
88
+ # @return [Object] The current value.
89
+
56
90
  alias_method :deref, :value
57
91
 
58
92
  # Atomically swaps the value of atom using the given block. The current
@@ -68,7 +102,7 @@ module Concurrent
68
102
  # the application of the supplied block to a current value, atomically.
69
103
  # However, because the block might be called multiple times, it must be free
70
104
  # of side effects.
71
- #
105
+ #
72
106
  # @note The given block may be called multiple times, and thus should be free
73
107
  # of side effects.
74
108
  #
@@ -87,45 +121,66 @@ module Concurrent
87
121
  def swap(*args)
88
122
  raise ArgumentError.new('no block given') unless block_given?
89
123
 
90
- begin
91
- loop do
92
- old_value = @value.value
124
+ loop do
125
+ old_value = value
126
+ begin
93
127
  new_value = yield(old_value, *args)
94
- return old_value unless @validator.call(new_value)
95
- return new_value if compare_and_set!(old_value, new_value)
128
+ break old_value unless valid?(new_value)
129
+ break new_value if compare_and_set(old_value, new_value)
130
+ rescue
131
+ break old_value
96
132
  end
97
- rescue
98
- return @value.value
99
133
  end
100
134
  end
101
135
 
102
- # @!macro [attach] atom_compare_and_set
103
- # Atomically sets the value of atom to the new value if and only if the
104
- # current value of the atom is identical to the old value and the new
105
- # value successfully validates against the (optional) validator given
106
- # at construction.
136
+ # Atomically sets the value of atom to the new value if and only if the
137
+ # current value of the atom is identical to the old value and the new
138
+ # value successfully validates against the (optional) validator given
139
+ # at construction.
107
140
  #
108
- # @param [Object] old_value The expected current value.
109
- # @param [Object] new_value The intended new value.
141
+ # @param [Object] old_value The expected current value.
142
+ # @param [Object] new_value The intended new value.
110
143
  #
111
- # @return [Boolean] True if the value is changed else false.
144
+ # @return [Boolean] True if the value is changed else false.
112
145
  def compare_and_set(old_value, new_value)
113
- compare_and_set!(old_value, new_value)
114
- rescue
115
- false
146
+ if valid?(new_value) && compare_and_set_value(old_value, new_value)
147
+ observers.notify_observers(Time.now, old_value, new_value)
148
+ true
149
+ else
150
+ false
151
+ end
116
152
  end
117
153
 
118
- private
119
-
120
- # @!macro atom_compare_and_set
121
- # @raise [Exception] if the validator proc raises an exception
122
- # @!visibility private
123
- def compare_and_set!(old_value, new_value)
124
- if @validator.call(new_value) # may raise exception
125
- @value.compare_and_set(old_value, new_value)
154
+ # Atomically sets the value of atom to the new value without regard for the
155
+ # current value so long as the new value successfully validates against the
156
+ # (optional) validator given at construction.
157
+ #
158
+ # @param [Object] new_value The intended new value.
159
+ #
160
+ # @return [Object] The final value of the atom after all operations and
161
+ # validations are complete.
162
+ def reset(new_value)
163
+ old_value = value
164
+ if valid?(new_value)
165
+ self.value = new_value
166
+ observers.notify_observers(Time.now, old_value, new_value)
167
+ new_value
126
168
  else
127
- false
169
+ old_value
128
170
  end
129
171
  end
172
+
173
+ private
174
+
175
+ # Is the new value valid?
176
+ #
177
+ # @param [Object] new_value The intended new value.
178
+ # @return [Boolean] false if the validator function returns false or raises
179
+ # an exception else true
180
+ def valid?(new_value)
181
+ @Validator.call(new_value)
182
+ rescue
183
+ false
184
+ end
130
185
  end
131
186
  end
@@ -94,6 +94,8 @@ module Concurrent
94
94
  # boolean and thread-safe and guaranteed to succeed. Reads and writes may block
95
95
  # briefly but no explicit locking is required.
96
96
  #
97
+ # @!macro thread_safe_variable_comparison
98
+ #
97
99
  # Testing with ruby 2.1.2
98
100
  # Testing with Concurrent::MutexAtomicBoolean...
99
101
  # 2.790000 0.000000 2.790000 ( 2.791454)
@@ -111,6 +111,8 @@ module Concurrent
111
111
  # fixnum and thread-safe and guaranteed to succeed. Reads and writes may block
112
112
  # briefly but no explicit locking is required.
113
113
  #
114
+ # @!macro thread_safe_variable_comparison
115
+ #
114
116
  # Testing with ruby 2.1.2
115
117
  # Testing with Concurrent::MutexAtomicFixnum...
116
118
  # 3.130000 0.000000 3.130000 ( 3.136505)
@@ -4,7 +4,7 @@ module Concurrent
4
4
 
5
5
  # A synchronization aid that allows a set of threads to all wait for each
6
6
  # other to reach a common barrier point.
7
- class CyclicBarrier < Synchronization::Object
7
+ class CyclicBarrier < Synchronization::LockableObject
8
8
 
9
9
  # @!visibility private
10
10
  Generation = Struct.new(:status)
@@ -13,7 +13,7 @@ module Concurrent
13
13
  # `#reset` at any time once it has been set.
14
14
  #
15
15
  # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682655.aspx
16
- class Event < Synchronization::Object
16
+ class Event < Synchronization::LockableObject
17
17
 
18
18
  # Creates a new `Event` in the unset state. Threads calling `#wait` on the
19
19
  # `Event` will block.
@@ -5,7 +5,7 @@ module Concurrent
5
5
  # @!macro atomic_boolean
6
6
  # @!visibility private
7
7
  # @!macro internal_implementation_note
8
- class MutexAtomicBoolean < Synchronization::Object
8
+ class MutexAtomicBoolean < Synchronization::LockableObject
9
9
 
10
10
  # @!macro atomic_boolean_method_initialize
11
11
  def initialize(initial = false)
@@ -5,7 +5,7 @@ module Concurrent
5
5
  # @!macro atomic_fixnum
6
6
  # @!visibility private
7
7
  # @!macro internal_implementation_note
8
- class MutexAtomicFixnum < Synchronization::Object
8
+ class MutexAtomicFixnum < Synchronization::LockableObject
9
9
 
10
10
  # http://stackoverflow.com/questions/535721/ruby-max-integer
11
11
  MIN_VALUE = -(2**(0.size * 8 - 2))
@@ -5,7 +5,7 @@ module Concurrent
5
5
  # @!macro count_down_latch
6
6
  # @!visibility private
7
7
  # @!macro internal_implementation_note
8
- class MutexCountDownLatch < Synchronization::Object
8
+ class MutexCountDownLatch < Synchronization::LockableObject
9
9
 
10
10
  # @!macro count_down_latch_method_initialize
11
11
  def initialize(count = 1)
@@ -5,7 +5,7 @@ module Concurrent
5
5
  # @!macro semaphore
6
6
  # @!visibility private
7
7
  # @!macro internal_implementation_note
8
- class MutexSemaphore < Synchronization::Object
8
+ class MutexSemaphore < Synchronization::LockableObject
9
9
 
10
10
  # @!macro semaphore_method_initialize
11
11
  def initialize(count)
@@ -78,7 +78,7 @@ module Concurrent
78
78
  # @raise [ArgumentError] if `@free` - `@reduction` is less than zero
79
79
  #
80
80
  # @return [nil]
81
- #
81
+ #
82
82
  # @!visibility private
83
83
  def reduce_permits(reduction)
84
84
  unless reduction.is_a?(Fixnum) && reduction >= 0
@@ -41,10 +41,12 @@ module Concurrent
41
41
  # @!visibility private
42
42
  MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1
43
43
 
44
- # Implementation notes:
44
+ safe_initialization!
45
+
46
+ # Implementation notes:
45
47
  # A goal is to make the uncontended path for both readers/writers lock-free
46
48
  # Only if there is reader-writer or writer-writer contention, should locks be used
47
- # Internal state is represented by a single integer ("counter"), and updated
49
+ # Internal state is represented by a single integer ("counter"), and updated
48
50
  # using atomic compare-and-swap operations
49
51
  # When the counter is 0, the lock is free
50
52
  # Each reader increments the counter by 1 when acquiring a read lock
@@ -54,11 +56,10 @@ module Concurrent
54
56
 
55
57
  # Create a new `ReadWriteLock` in the unlocked state.
56
58
  def initialize
59
+ super()
57
60
  @Counter = AtomicFixnum.new(0) # single integer which represents lock state
58
61
  @ReadLock = Synchronization::Lock.new
59
62
  @WriteLock = Synchronization::Lock.new
60
- ensure_ivar_visibility!
61
- super()
62
63
  end
63
64
 
64
65
  # Execute a block operation within a read lock.
@@ -99,13 +99,15 @@ module Concurrent
99
99
  # @!visibility private
100
100
  WRITE_LOCK_MASK = MAX_WRITERS
101
101
 
102
+ safe_initialization!
103
+
102
104
  # Create a new `ReentrantReadWriteLock` in the unlocked state.
103
105
  def initialize
106
+ super()
104
107
  @Counter = AtomicFixnum.new(0) # single integer which represents lock state
105
108
  @ReadQueue = Synchronization::Lock.new # used to queue waiting readers
106
109
  @WriteQueue = Synchronization::Lock.new # used to queue waiting writers
107
110
  @HeldCount = ThreadLocalVar.new(0) # indicates # of R & W locks held by this thread
108
- ensure_ivar_visibility!
109
111
  end
110
112
 
111
113
  # Execute a block operation within a read lock.
@@ -68,6 +68,8 @@ module Concurrent
68
68
  # A `ThreadLocalVar` is a variable where the value is different for each thread.
69
69
  # Each variable may have a default value, but when you modify the variable only
70
70
  # the current thread will ever see that change.
71
+ #
72
+ # @!macro thread_safe_variable_comparison
71
73
  #
72
74
  # @example
73
75
  # v = ThreadLocalVar.new(14)
@@ -8,7 +8,7 @@ module Concurrent
8
8
  #
9
9
  # @!visibility private
10
10
  # @!macro internal_implementation_note
11
- class MutexAtomicReference < Synchronization::Object
11
+ class MutexAtomicReference < Synchronization::LockableObject
12
12
  include Concurrent::AtomicDirectUpdate
13
13
  include Concurrent::AtomicNumericCompareAndSetWrapper
14
14
 
@@ -2,30 +2,32 @@
2
2
  #
3
3
  # An object reference that may be updated atomically.
4
4
  #
5
+ # @!macro thread_safe_variable_comparison
6
+ #
5
7
  # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html
6
8
  # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
7
9
  #
8
10
  # @!method initialize
9
11
  # @!macro [new] atomic_reference_method_initialize
10
12
  # @param [Object] value The initial value.
11
- #
13
+ #
12
14
  # @!method get
13
15
  # @!macro [new] atomic_reference_method_get
14
16
  # Gets the current value.
15
17
  # @return [Object] the current value
16
- #
18
+ #
17
19
  # @!method set
18
20
  # @!macro [new] atomic_reference_method_set
19
21
  # Sets to the given value.
20
22
  # @param [Object] new_value the new value
21
23
  # @return [Object] the new value
22
- #
24
+ #
23
25
  # @!method get_and_set
24
26
  # @!macro [new] atomic_reference_method_get_and_set
25
27
  # Atomically sets to the given value and returns the old value.
26
28
  # @param [Object] new_value the new value
27
29
  # @return [Object] the old value
28
- #
30
+ #
29
31
  # @!method compare_and_set
30
32
  # @!macro [new] atomic_reference_method_compare_and_set
31
33
  #
@@ -7,23 +7,17 @@ module Concurrent
7
7
  # observers are added and removed from a thread safe collection; every time
8
8
  # a notification is required the internal data structure is copied to
9
9
  # prevent concurrency issues
10
- #
10
+ #
11
11
  # @api private
12
- class CopyOnNotifyObserverSet < Synchronization::Object
12
+ class CopyOnNotifyObserverSet < Synchronization::LockableObject
13
13
 
14
14
  def initialize
15
15
  super()
16
16
  synchronize { ns_initialize }
17
17
  end
18
18
 
19
- # Adds an observer to this set. If a block is passed, the observer will be
20
- # created by this method and no other params should be passed
21
- #
22
- # @param [Object] observer the observer to add
23
- # @param [Symbol] func the function to call on the observer during notification.
24
- # Default is :update
25
- # @return [Object] the added observer
26
- def add_observer(observer=nil, func=:update, &block)
19
+ # @!macro observable_add_observer
20
+ def add_observer(observer = nil, func = :update, &block)
27
21
  if observer.nil? && block.nil?
28
22
  raise ArgumentError, 'should pass observer as a first argument or block'
29
23
  elsif observer && block
@@ -41,8 +35,7 @@ module Concurrent
41
35
  end
42
36
  end
43
37
 
44
- # @param [Object] observer the observer to remove
45
- # @return [Object] the deleted observer
38
+ # @!macro observable_delete_observer
46
39
  def delete_observer(observer)
47
40
  synchronize do
48
41
  @observers.delete(observer)
@@ -50,8 +43,7 @@ module Concurrent
50
43
  end
51
44
  end
52
45
 
53
- # Deletes all observers
54
- # @return [CopyOnWriteObserverSet] self
46
+ # @!macro observable_delete_observers
55
47
  def delete_observers
56
48
  synchronize do
57
49
  @observers.clear
@@ -59,7 +51,7 @@ module Concurrent
59
51
  end
60
52
  end
61
53
 
62
- # @return [Integer] the observers count
54
+ # @!macro observable_count_observers
63
55
  def count_observers
64
56
  synchronize { @observers.count }
65
57
  end
@@ -6,23 +6,17 @@ module Concurrent
6
6
  # A thread safe observer set implemented using copy-on-write approach:
7
7
  # every time an observer is added or removed the whole internal data structure is
8
8
  # duplicated and replaced with a new one.
9
- #
9
+ #
10
10
  # @api private
11
- class CopyOnWriteObserverSet < Synchronization::Object
11
+ class CopyOnWriteObserverSet < Synchronization::LockableObject
12
12
 
13
13
  def initialize
14
14
  super()
15
15
  synchronize { ns_initialize }
16
16
  end
17
17
 
18
- # Adds an observer to this set
19
- # If a block is passed, the observer will be created by this method and no
20
- # other params should be passed
21
- # @param [Object] observer the observer to add
22
- # @param [Symbol] func the function to call on the observer during notification.
23
- # Default is :update
24
- # @return [Object] the added observer
25
- def add_observer(observer=nil, func=:update, &block)
18
+ # @!macro observable_add_observer
19
+ def add_observer(observer = nil, func = :update, &block)
26
20
  if observer.nil? && block.nil?
27
21
  raise ArgumentError, 'should pass observer as a first argument or block'
28
22
  elsif observer && block
@@ -42,8 +36,7 @@ module Concurrent
42
36
  end
43
37
  end
44
38
 
45
- # @param [Object] observer the observer to remove
46
- # @return [Object] the deleted observer
39
+ # @!macro observable_delete_observer
47
40
  def delete_observer(observer)
48
41
  synchronize do
49
42
  new_observers = @observers.dup
@@ -53,14 +46,13 @@ module Concurrent
53
46
  end
54
47
  end
55
48
 
56
- # Deletes all observers
57
- # @return [CopyOnWriteObserverSet] self
49
+ # @!macro observable_delete_observers
58
50
  def delete_observers
59
51
  self.observers = {}
60
52
  self
61
53
  end
62
54
 
63
- # @return [Integer] the observers count
55
+ # @!macro observable_count_observers
64
56
  def count_observers
65
57
  observers.count
66
58
  end