concurrent-ruby 0.8.0 → 0.9.0.pre2

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 (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
@@ -3,6 +3,9 @@ require 'concurrent/atomic_reference/concurrent_update_error'
3
3
  module Concurrent
4
4
 
5
5
  # Define update methods that use direct paths
6
+ #
7
+ # @!visibility private
8
+ # @!macro internal_implementation_note
6
9
  module AtomicDirectUpdate
7
10
 
8
11
  # @!macro [attach] atomic_reference_method_update
@@ -1,12 +1,15 @@
1
- require_relative '../../extension_helper'
1
+ require 'concurrent/utility/native_extension_loader'
2
2
 
3
- if defined?(Concurrent::JavaAtomic)
3
+ if defined?(Concurrent::JavaAtomicReference)
4
4
  require 'concurrent/atomic_reference/direct_update'
5
5
 
6
6
  module Concurrent
7
7
 
8
8
  # @!macro atomic_reference
9
- class JavaAtomic
9
+ #
10
+ # @!visibility private
11
+ # @!macro internal_implementation_note
12
+ class JavaAtomicReference
10
13
  include Concurrent::AtomicDirectUpdate
11
14
  end
12
15
  end
@@ -5,45 +5,32 @@ require 'concurrent/atomic_reference/numeric_cas_wrapper'
5
5
  module Concurrent
6
6
 
7
7
  # @!macro atomic_reference
8
- class MutexAtomic
8
+ #
9
+ # @!visibility private
10
+ # @!macro internal_implementation_note
11
+ class MutexAtomicReference
9
12
  include Concurrent::AtomicDirectUpdate
10
13
  include Concurrent::AtomicNumericCompareAndSetWrapper
11
14
 
12
- # @!macro [attach] atomic_reference_method_initialize
15
+ # @!macro atomic_reference_method_initialize
13
16
  def initialize(value = nil)
14
17
  @mutex = Mutex.new
15
18
  @value = value
16
19
  end
17
20
 
18
- # @!macro [attach] atomic_reference_method_get
19
- #
20
- # Gets the current value.
21
- #
22
- # @return [Object] the current value
21
+ # @!macro atomic_reference_method_get
23
22
  def get
24
23
  @mutex.synchronize { @value }
25
24
  end
26
25
  alias_method :value, :get
27
26
 
28
- # @!macro [attach] atomic_reference_method_set
29
- #
30
- # Sets to the given value.
31
- #
32
- # @param [Object] new_value the new value
33
- #
34
- # @return [Object] the new value
27
+ # @!macro atomic_reference_method_set
35
28
  def set(new_value)
36
29
  @mutex.synchronize { @value = new_value }
37
30
  end
38
31
  alias_method :value=, :set
39
32
 
40
- # @!macro [attach] atomic_reference_method_get_and_set
41
- #
42
- # Atomically sets to the given value and returns the old value.
43
- #
44
- # @param [Object] new_value the new value
45
- #
46
- # @return [Object] the old value
33
+ # @!macro atomic_reference_method_get_and_set
47
34
  def get_and_set(new_value)
48
35
  @mutex.synchronize do
49
36
  old_value = @value
@@ -53,17 +40,8 @@ module Concurrent
53
40
  end
54
41
  alias_method :swap, :get_and_set
55
42
 
56
- # @!macro [attach] atomic_reference_method_compare_and_set
57
- #
58
- # Atomically sets the value to the given updated value if
59
- # the current value == the expected value.
60
- #
61
- # @param [Object] old_value the expected value
62
- # @param [Object] new_value the new value
63
- #
64
- # @return [Boolean] `true` if successful. A `false` return indicates
65
- # that the actual value was not equal to the expected value.
66
- def _compare_and_set(old_value, new_value) #:nodoc:
43
+ # @!macro atomic_reference_method_compare_and_set
44
+ def _compare_and_set(old_value, new_value)
67
45
  return false unless @mutex.try_lock
68
46
  begin
69
47
  return false unless @value.equal? old_value
@@ -1,6 +1,9 @@
1
1
  module Concurrent
2
2
 
3
3
  # Special "compare and set" handling of numeric values.
4
+ #
5
+ # @!visibility private
6
+ # @!macro internal_implementation_note
4
7
  module AtomicNumericCompareAndSetWrapper
5
8
 
6
9
  # @!macro atomic_reference_method_compare_and_set
@@ -7,7 +7,10 @@ module Concurrent
7
7
  #
8
8
  # @note Extends `Rubinius::AtomicReference` version adding aliases
9
9
  # and numeric logic.
10
- class RbxAtomic < Rubinius::AtomicReference
10
+ #
11
+ # @!visibility private
12
+ # @!macro internal_implementation_note
13
+ class RbxAtomicReference < Rubinius::AtomicReference
11
14
  alias _compare_and_set compare_and_set
12
15
  include Concurrent::AtomicDirectUpdate
13
16
  include Concurrent::AtomicNumericCompareAndSetWrapper
@@ -1,12 +1,15 @@
1
- if defined? Concurrent::CAtomic
2
- require_relative '../../extension_helper'
1
+ if defined? Concurrent::CAtomicReference
2
+ require 'concurrent/utility/native_extension_loader'
3
3
  require 'concurrent/atomic_reference/direct_update'
4
4
  require 'concurrent/atomic_reference/numeric_cas_wrapper'
5
5
 
6
6
  module Concurrent
7
7
 
8
8
  # @!macro atomic_reference
9
- class CAtomic
9
+ #
10
+ # @!visibility private
11
+ # @!macro internal_implementation_note
12
+ class CAtomicReference
10
13
  include Concurrent::AtomicDirectUpdate
11
14
  include Concurrent::AtomicNumericCompareAndSetWrapper
12
15
 
@@ -1,12 +1,82 @@
1
- require 'concurrent/atomic'
1
+ # @!macro [new] atomic_reference
2
+ #
3
+ # An object reference that may be updated atomically.
4
+ #
5
+ # Testing with ruby 2.1.2
6
+ #
7
+ # *** Sequential updates ***
8
+ # user system total real
9
+ # no lock 0.000000 0.000000 0.000000 ( 0.005502)
10
+ # mutex 0.030000 0.000000 0.030000 ( 0.025158)
11
+ # MutexAtomicReference 0.100000 0.000000 0.100000 ( 0.103096)
12
+ # CAtomicReference 0.040000 0.000000 0.040000 ( 0.034012)
13
+ #
14
+ # *** Parallel updates ***
15
+ # user system total real
16
+ # no lock 0.010000 0.000000 0.010000 ( 0.009387)
17
+ # mutex 0.030000 0.010000 0.040000 ( 0.032545)
18
+ # MutexAtomicReference 0.830000 2.280000 3.110000 ( 2.146622)
19
+ # CAtomicReference 0.040000 0.000000 0.040000 ( 0.038332)
20
+ #
21
+ # Testing with jruby 1.9.3
22
+ #
23
+ # *** Sequential updates ***
24
+ # user system total real
25
+ # no lock 0.170000 0.000000 0.170000 ( 0.051000)
26
+ # mutex 0.370000 0.010000 0.380000 ( 0.121000)
27
+ # MutexAtomicReference 1.530000 0.020000 1.550000 ( 0.471000)
28
+ # JavaAtomicReference 0.370000 0.010000 0.380000 ( 0.112000)
29
+ #
30
+ # *** Parallel updates ***
31
+ # user system total real
32
+ # no lock 0.390000 0.000000 0.390000 ( 0.105000)
33
+ # mutex 0.480000 0.040000 0.520000 ( 0.145000)
34
+ # MutexAtomicReference 1.600000 0.180000 1.780000 ( 0.511000)
35
+ # JavaAtomicReference 0.460000 0.010000 0.470000 ( 0.131000)
36
+ #
37
+ # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html
38
+ # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
39
+ #
40
+ # @!method initialize
41
+ # @!macro [new] atomic_reference_method_initialize
42
+ # @param [Object] value The initial value.
43
+ #
44
+ # @!method get
45
+ # @!macro [new] atomic_reference_method_get
46
+ # Gets the current value.
47
+ # @return [Object] the current value
48
+ #
49
+ # @!method set
50
+ # @!macro [new] atomic_reference_method_set
51
+ # Sets to the given value.
52
+ # @param [Object] new_value the new value
53
+ # @return [Object] the new value
54
+ #
55
+ # @!method get_and_set
56
+ # @!macro [new] atomic_reference_method_get_and_set
57
+ # Atomically sets to the given value and returns the old value.
58
+ # @param [Object] new_value the new value
59
+ # @return [Object] the old value
60
+ #
61
+ # @!method compare_and_set
62
+ # @!macro [new] atomic_reference_method_compare_and_set
63
+ #
64
+ # Atomically sets the value to the given updated value if
65
+ # the current value == the expected value.
66
+ #
67
+ # @param [Object] old_value the expected value
68
+ # @param [Object] new_value the new value
69
+ #
70
+ # @return [Boolean] `true` if successful. A `false` return indicates
71
+ # that the actual value was not equal to the expected value.
72
+
73
+ require 'concurrent/atomic/atomic_reference'
2
74
  require 'concurrent/atomic/atomic_boolean'
3
75
  require 'concurrent/atomic/atomic_fixnum'
4
76
  require 'concurrent/atomic/condition'
5
- require 'concurrent/atomic/copy_on_notify_observer_set'
6
- require 'concurrent/atomic/copy_on_write_observer_set'
7
77
  require 'concurrent/atomic/cyclic_barrier'
8
78
  require 'concurrent/atomic/count_down_latch'
9
79
  require 'concurrent/atomic/event'
10
- require 'concurrent/atomic/synchronization'
80
+ require 'concurrent/atomic/read_write_lock'
11
81
  require 'concurrent/atomic/semaphore'
12
82
  require 'concurrent/atomic/thread_local_var'
@@ -0,0 +1,115 @@
1
+ require 'concurrent/synchronization'
2
+
3
+ module Concurrent
4
+ module Collection
5
+
6
+ # A thread safe observer set implemented using copy-on-read approach:
7
+ # observers are added and removed from a thread safe collection; every time
8
+ # a notification is required the internal data structure is copied to
9
+ # prevent concurrency issues
10
+ #
11
+ # @api private
12
+ class CopyOnNotifyObserverSet < Synchronization::Object
13
+
14
+ def initialize
15
+ super()
16
+ synchronize { ns_initialize }
17
+ end
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)
27
+ if observer.nil? && block.nil?
28
+ raise ArgumentError, 'should pass observer as a first argument or block'
29
+ elsif observer && block
30
+ raise ArgumentError.new('cannot provide both an observer and a block')
31
+ end
32
+
33
+ if block
34
+ observer = block
35
+ func = :call
36
+ end
37
+
38
+ synchronize do
39
+ @observers[observer] = func
40
+ observer
41
+ end
42
+ end
43
+
44
+ # @param [Object] observer the observer to remove
45
+ # @return [Object] the deleted observer
46
+ def delete_observer(observer)
47
+ synchronize do
48
+ @observers.delete(observer)
49
+ observer
50
+ end
51
+ end
52
+
53
+ # Deletes all observers
54
+ # @return [CopyOnWriteObserverSet] self
55
+ def delete_observers
56
+ synchronize do
57
+ @observers.clear
58
+ self
59
+ end
60
+ end
61
+
62
+ # @return [Integer] the observers count
63
+ def count_observers
64
+ synchronize { @observers.count }
65
+ end
66
+
67
+ # Notifies all registered observers with optional args
68
+ # @param [Object] args arguments to be passed to each observer
69
+ # @return [CopyOnWriteObserverSet] self
70
+ def notify_observers(*args, &block)
71
+ observers = duplicate_observers
72
+ notify_to(observers, *args, &block)
73
+ self
74
+ end
75
+
76
+ # Notifies all registered observers with optional args and deletes them.
77
+ #
78
+ # @param [Object] args arguments to be passed to each observer
79
+ # @return [CopyOnWriteObserverSet] self
80
+ def notify_and_delete_observers(*args, &block)
81
+ observers = duplicate_and_clear_observers
82
+ notify_to(observers, *args, &block)
83
+ self
84
+ end
85
+
86
+ protected
87
+
88
+ def ns_initialize
89
+ @observers = {}
90
+ end
91
+
92
+ private
93
+
94
+ def duplicate_and_clear_observers
95
+ synchronize do
96
+ observers = @observers.dup
97
+ @observers.clear
98
+ observers
99
+ end
100
+ end
101
+
102
+ def duplicate_observers
103
+ synchronize { observers = @observers.dup }
104
+ end
105
+
106
+ def notify_to(observers, *args)
107
+ raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty?
108
+ observers.each do |observer, function|
109
+ args = yield if block_given?
110
+ observer.send(function, *args)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,119 @@
1
+ require 'concurrent/synchronization'
2
+
3
+ module Concurrent
4
+ module Collection
5
+
6
+ # A thread safe observer set implemented using copy-on-write approach:
7
+ # every time an observer is added or removed the whole internal data structure is
8
+ # duplicated and replaced with a new one.
9
+ #
10
+ # @api private
11
+ class CopyOnWriteObserverSet < Synchronization::Object
12
+
13
+ def initialize
14
+ super()
15
+ synchronize { ns_initialize }
16
+ end
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)
26
+ if observer.nil? && block.nil?
27
+ raise ArgumentError, 'should pass observer as a first argument or block'
28
+ elsif observer && block
29
+ raise ArgumentError.new('cannot provide both an observer and a block')
30
+ end
31
+
32
+ if block
33
+ observer = block
34
+ func = :call
35
+ end
36
+
37
+ synchronize do
38
+ new_observers = @observers.dup
39
+ new_observers[observer] = func
40
+ @observers = new_observers
41
+ observer
42
+ end
43
+ end
44
+
45
+ # @param [Object] observer the observer to remove
46
+ # @return [Object] the deleted observer
47
+ def delete_observer(observer)
48
+ synchronize do
49
+ new_observers = @observers.dup
50
+ new_observers.delete(observer)
51
+ @observers = new_observers
52
+ observer
53
+ end
54
+ end
55
+
56
+ # Deletes all observers
57
+ # @return [CopyOnWriteObserverSet] self
58
+ def delete_observers
59
+ self.observers = {}
60
+ self
61
+ end
62
+
63
+ # @return [Integer] the observers count
64
+ def count_observers
65
+ observers.count
66
+ end
67
+
68
+ # Notifies all registered observers with optional args
69
+ # @param [Object] args arguments to be passed to each observer
70
+ # @return [CopyOnWriteObserverSet] self
71
+ def notify_observers(*args, &block)
72
+ notify_to(observers, *args, &block)
73
+ self
74
+ end
75
+
76
+ # Notifies all registered observers with optional args and deletes them.
77
+ #
78
+ # @param [Object] args arguments to be passed to each observer
79
+ # @return [CopyOnWriteObserverSet] self
80
+ def notify_and_delete_observers(*args, &block)
81
+ old = clear_observers_and_return_old
82
+ notify_to(old, *args, &block)
83
+ self
84
+ end
85
+
86
+ protected
87
+
88
+ def ns_initialize
89
+ @observers = {}
90
+ end
91
+
92
+ private
93
+
94
+ def notify_to(observers, *args)
95
+ raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty?
96
+ observers.each do |observer, function|
97
+ args = yield if block_given?
98
+ observer.send(function, *args)
99
+ end
100
+ end
101
+
102
+ def observers
103
+ synchronize { @observers }
104
+ end
105
+
106
+ def observers=(new_set)
107
+ synchronize { @observers = new_set }
108
+ end
109
+
110
+ def clear_observers_and_return_old
111
+ synchronize do
112
+ old_observers = @observers
113
+ @observers = {}
114
+ old_observers
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end