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
@@ -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