concurrent-ruby 0.7.0.rc0-x86-linux
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.
- checksums.yaml +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +166 -0
- data/ext/concurrent_ruby_ext/atomic_reference.c +78 -0
- data/ext/concurrent_ruby_ext/atomic_reference.h +12 -0
- data/ext/concurrent_ruby_ext/extconf.rb +59 -0
- data/ext/concurrent_ruby_ext/rb_concurrent.c +28 -0
- data/lib/concurrent.rb +45 -0
- data/lib/concurrent/actress.rb +221 -0
- data/lib/concurrent/actress/ad_hoc.rb +20 -0
- data/lib/concurrent/actress/context.rb +98 -0
- data/lib/concurrent/actress/core.rb +228 -0
- data/lib/concurrent/actress/core_delegations.rb +42 -0
- data/lib/concurrent/actress/envelope.rb +41 -0
- data/lib/concurrent/actress/errors.rb +14 -0
- data/lib/concurrent/actress/reference.rb +64 -0
- data/lib/concurrent/actress/type_check.rb +48 -0
- data/lib/concurrent/agent.rb +232 -0
- data/lib/concurrent/async.rb +319 -0
- data/lib/concurrent/atomic.rb +46 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +157 -0
- data/lib/concurrent/atomic/atomic_fixnum.rb +162 -0
- data/lib/concurrent/atomic/condition.rb +67 -0
- data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +118 -0
- data/lib/concurrent/atomic/copy_on_write_observer_set.rb +117 -0
- data/lib/concurrent/atomic/count_down_latch.rb +116 -0
- data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
- data/lib/concurrent/atomic/event.rb +98 -0
- data/lib/concurrent/atomic/thread_local_var.rb +117 -0
- data/lib/concurrent/atomic_reference/concurrent_update_error.rb +7 -0
- data/lib/concurrent/atomic_reference/delegated_update.rb +28 -0
- data/lib/concurrent/atomic_reference/direct_update.rb +28 -0
- data/lib/concurrent/atomic_reference/jruby.rb +8 -0
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +47 -0
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +24 -0
- data/lib/concurrent/atomic_reference/rbx.rb +16 -0
- data/lib/concurrent/atomic_reference/ruby.rb +16 -0
- data/lib/concurrent/atomics.rb +10 -0
- data/lib/concurrent/channel/buffered_channel.rb +85 -0
- data/lib/concurrent/channel/channel.rb +41 -0
- data/lib/concurrent/channel/unbuffered_channel.rb +34 -0
- data/lib/concurrent/channel/waitable_list.rb +40 -0
- data/lib/concurrent/channels.rb +5 -0
- data/lib/concurrent/collection/blocking_ring_buffer.rb +71 -0
- data/lib/concurrent/collection/priority_queue.rb +305 -0
- data/lib/concurrent/collection/ring_buffer.rb +59 -0
- data/lib/concurrent/collections.rb +3 -0
- data/lib/concurrent/configuration.rb +158 -0
- data/lib/concurrent/dataflow.rb +91 -0
- data/lib/concurrent/delay.rb +112 -0
- data/lib/concurrent/dereferenceable.rb +101 -0
- data/lib/concurrent/errors.rb +30 -0
- data/lib/concurrent/exchanger.rb +34 -0
- data/lib/concurrent/executor/cached_thread_pool.rb +44 -0
- data/lib/concurrent/executor/executor.rb +229 -0
- data/lib/concurrent/executor/fixed_thread_pool.rb +33 -0
- data/lib/concurrent/executor/immediate_executor.rb +16 -0
- data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
- data/lib/concurrent/executor/java_fixed_thread_pool.rb +33 -0
- data/lib/concurrent/executor/java_single_thread_executor.rb +21 -0
- data/lib/concurrent/executor/java_thread_pool_executor.rb +187 -0
- data/lib/concurrent/executor/per_thread_executor.rb +24 -0
- data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
- data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +32 -0
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +73 -0
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +286 -0
- data/lib/concurrent/executor/ruby_thread_pool_worker.rb +72 -0
- data/lib/concurrent/executor/safe_task_executor.rb +35 -0
- data/lib/concurrent/executor/serialized_execution.rb +90 -0
- data/lib/concurrent/executor/single_thread_executor.rb +35 -0
- data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
- data/lib/concurrent/executor/timer_set.rb +143 -0
- data/lib/concurrent/executors.rb +9 -0
- data/lib/concurrent/future.rb +124 -0
- data/lib/concurrent/ivar.rb +111 -0
- data/lib/concurrent/logging.rb +17 -0
- data/lib/concurrent/mvar.rb +200 -0
- data/lib/concurrent/obligation.rb +171 -0
- data/lib/concurrent/observable.rb +40 -0
- data/lib/concurrent/options_parser.rb +46 -0
- data/lib/concurrent/promise.rb +169 -0
- data/lib/concurrent/scheduled_task.rb +78 -0
- data/lib/concurrent/supervisor.rb +343 -0
- data/lib/concurrent/timer_task.rb +341 -0
- data/lib/concurrent/tvar.rb +252 -0
- data/lib/concurrent/utilities.rb +3 -0
- data/lib/concurrent/utility/processor_count.rb +150 -0
- data/lib/concurrent/utility/timeout.rb +35 -0
- data/lib/concurrent/utility/timer.rb +21 -0
- data/lib/concurrent/version.rb +3 -0
- data/lib/concurrent_ruby.rb +1 -0
- data/lib/concurrent_ruby_ext.so +0 -0
- data/lib/extension_helper.rb +9 -0
- metadata +140 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'concurrent/atomic_reference/concurrent_update_error'
|
2
|
+
require 'concurrent/atomic_reference/mutex_atomic'
|
3
|
+
|
4
|
+
begin
|
5
|
+
# force fallback impl with FORCE_ATOMIC_FALLBACK=1
|
6
|
+
if /[^0fF]/ =~ ENV['FORCE_ATOMIC_FALLBACK']
|
7
|
+
ruby_engine = 'mutex_atomic'
|
8
|
+
else
|
9
|
+
ruby_engine = defined?(RUBY_ENGINE)? RUBY_ENGINE : 'ruby'
|
10
|
+
end
|
11
|
+
|
12
|
+
require "concurrent/atomic_reference/#{ruby_engine}"
|
13
|
+
rescue LoadError
|
14
|
+
warn 'Compiled extensions not installed, pure Ruby Atomic will be used.'
|
15
|
+
end
|
16
|
+
|
17
|
+
if defined? Concurrent::JavaAtomic
|
18
|
+
|
19
|
+
class Concurrent::Atomic < Concurrent::JavaAtomic
|
20
|
+
end
|
21
|
+
|
22
|
+
elsif defined? Concurrent::CAtomic
|
23
|
+
|
24
|
+
class Concurrent::Atomic < Concurrent::CAtomic
|
25
|
+
end
|
26
|
+
|
27
|
+
elsif defined? Concurrent::RbxAtomic
|
28
|
+
|
29
|
+
class Concurrent::Atomic < Concurrent::RbxAtomic
|
30
|
+
end
|
31
|
+
|
32
|
+
else
|
33
|
+
|
34
|
+
class Concurrent::Atomic < Concurrent::MutexAtomic
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Atomic < Concurrent::Atomic
|
39
|
+
|
40
|
+
ConcurrentUpdateError = Class.new(Concurrent::ConcurrentUpdateError)
|
41
|
+
|
42
|
+
def initialize(*args)
|
43
|
+
warn "[DEPRECATED] Please use Concurrent::Atomic instead."
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# @!macro [attach] atomic_boolean
|
4
|
+
#
|
5
|
+
# A boolean value that can be updated atomically. Reads and writes to an atomic
|
6
|
+
# boolean and thread-safe and guaranteed to succeed. Reads and writes may block
|
7
|
+
# briefly but no explicit locking is required.
|
8
|
+
#
|
9
|
+
# @since 0.6.0
|
10
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicBoolean.html java.util.concurrent.atomic.AtomicBoolean
|
11
|
+
class MutexAtomicBoolean
|
12
|
+
|
13
|
+
# @!macro [attach] atomic_boolean_method_initialize
|
14
|
+
#
|
15
|
+
# Creates a new `AtomicBoolean` with the given initial value.
|
16
|
+
#
|
17
|
+
# @param [Boolean] initial the initial value
|
18
|
+
def initialize(initial = false)
|
19
|
+
@value = !!initial
|
20
|
+
@mutex = Mutex.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!macro [attach] atomic_boolean_method_value
|
24
|
+
#
|
25
|
+
# Retrieves the current `Boolean` value.
|
26
|
+
#
|
27
|
+
# @return [Boolean] the current value
|
28
|
+
def value
|
29
|
+
@mutex.lock
|
30
|
+
@value
|
31
|
+
ensure
|
32
|
+
@mutex.unlock
|
33
|
+
end
|
34
|
+
|
35
|
+
# @!macro [attach] atomic_boolean_method_value_eq
|
36
|
+
#
|
37
|
+
# Explicitly sets the value.
|
38
|
+
#
|
39
|
+
# @param [Boolean] value the new value to be set
|
40
|
+
#
|
41
|
+
# @return [Boolean] the current value
|
42
|
+
def value=(value)
|
43
|
+
@mutex.lock
|
44
|
+
@value = !!value
|
45
|
+
@value
|
46
|
+
ensure
|
47
|
+
@mutex.unlock
|
48
|
+
end
|
49
|
+
|
50
|
+
# @!macro [attach] atomic_boolean_method_is_true
|
51
|
+
#
|
52
|
+
# Is the current value `true`?
|
53
|
+
#
|
54
|
+
# @return [Boolean] true if the current value is `true`, else false
|
55
|
+
def true?
|
56
|
+
@mutex.lock
|
57
|
+
@value
|
58
|
+
ensure
|
59
|
+
@mutex.unlock
|
60
|
+
end
|
61
|
+
|
62
|
+
# @!macro [attach] atomic_boolean_method_is_false
|
63
|
+
#
|
64
|
+
# Is the current value `true`?false
|
65
|
+
#
|
66
|
+
# @return [Boolean] true if the current value is `false`, else false
|
67
|
+
def false?
|
68
|
+
@mutex.lock
|
69
|
+
!@value
|
70
|
+
ensure
|
71
|
+
@mutex.unlock
|
72
|
+
end
|
73
|
+
|
74
|
+
# @!macro [attach] atomic_boolean_method_make_true
|
75
|
+
#
|
76
|
+
# Explicitly sets the value to true.
|
77
|
+
#
|
78
|
+
# @return [Boolean] true is value has changed, otherwise false
|
79
|
+
def make_true
|
80
|
+
@mutex.lock
|
81
|
+
old = @value
|
82
|
+
@value = true
|
83
|
+
!old
|
84
|
+
ensure
|
85
|
+
@mutex.unlock
|
86
|
+
end
|
87
|
+
|
88
|
+
# @!macro [attach] atomic_boolean_method_make_false
|
89
|
+
#
|
90
|
+
# Explicitly sets the value to false.
|
91
|
+
#
|
92
|
+
# @return [Boolean] true is value has changed, otherwise false
|
93
|
+
def make_false
|
94
|
+
@mutex.lock
|
95
|
+
old = @value
|
96
|
+
@value = false
|
97
|
+
old
|
98
|
+
ensure
|
99
|
+
@mutex.unlock
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
if RUBY_PLATFORM == 'java'
|
104
|
+
|
105
|
+
# @!macro atomic_boolean
|
106
|
+
class JavaAtomicBoolean
|
107
|
+
|
108
|
+
# @!macro atomic_boolean_method_initialize
|
109
|
+
#
|
110
|
+
def initialize(initial = false)
|
111
|
+
@atomic = java.util.concurrent.atomic.AtomicBoolean.new(!!initial)
|
112
|
+
end
|
113
|
+
|
114
|
+
# @!macro atomic_boolean_method_value
|
115
|
+
#
|
116
|
+
def value
|
117
|
+
@atomic.get
|
118
|
+
end
|
119
|
+
|
120
|
+
# @!macro atomic_boolean_method_value_eq
|
121
|
+
#
|
122
|
+
def value=(value)
|
123
|
+
@atomic.set(!!value)
|
124
|
+
end
|
125
|
+
|
126
|
+
# @!macro [attach] atomic_boolean_method_is_true
|
127
|
+
def true?
|
128
|
+
@atomic.get
|
129
|
+
end
|
130
|
+
|
131
|
+
# @!macro [attach] atomic_boolean_method_is_false
|
132
|
+
def false?
|
133
|
+
!@atomic.get
|
134
|
+
end
|
135
|
+
|
136
|
+
# @!macro atomic_boolean_method_make_true
|
137
|
+
def make_true
|
138
|
+
@atomic.compareAndSet(false, true)
|
139
|
+
end
|
140
|
+
|
141
|
+
# @!macro atomic_boolean_method_make_false
|
142
|
+
def make_false
|
143
|
+
@atomic.compareAndSet(true, false)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!macro atomic_boolean
|
148
|
+
class AtomicBoolean < JavaAtomicBoolean
|
149
|
+
end
|
150
|
+
|
151
|
+
else
|
152
|
+
|
153
|
+
# @!macro atomic_boolean
|
154
|
+
class AtomicBoolean < MutexAtomicBoolean
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# @!macro [attach] atomic_fixnum
|
4
|
+
#
|
5
|
+
# A numeric value that can be updated atomically. Reads and writes to an atomic
|
6
|
+
# fixnum and thread-safe and guaranteed to succeed. Reads and writes may block
|
7
|
+
# briefly but no explicit locking is required.
|
8
|
+
#
|
9
|
+
# @since 0.5.0
|
10
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicLong.html java.util.concurrent.atomic.AtomicLong
|
11
|
+
class MutexAtomicFixnum
|
12
|
+
|
13
|
+
# @!macro [attach] atomic_fixnum_method_initialize
|
14
|
+
#
|
15
|
+
# Creates a new `AtomicFixnum` with the given initial value.
|
16
|
+
#
|
17
|
+
# @param [Fixnum] init the initial value
|
18
|
+
# @raise [ArgumentError] if the initial value is not a `Fixnum`
|
19
|
+
def initialize(init = 0)
|
20
|
+
raise ArgumentError.new('initial value must be a Fixnum') unless init.is_a?(Fixnum)
|
21
|
+
@value = init
|
22
|
+
@mutex = Mutex.new
|
23
|
+
end
|
24
|
+
|
25
|
+
# @!macro [attach] atomic_fixnum_method_value
|
26
|
+
#
|
27
|
+
# Retrieves the current `Fixnum` value.
|
28
|
+
#
|
29
|
+
# @return [Fixnum] the current value
|
30
|
+
def value
|
31
|
+
@mutex.lock
|
32
|
+
@value
|
33
|
+
ensure
|
34
|
+
@mutex.unlock
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!macro [attach] atomic_fixnum_method_value_eq
|
38
|
+
#
|
39
|
+
# Explicitly sets the value.
|
40
|
+
#
|
41
|
+
# @param [Fixnum] value the new value to be set
|
42
|
+
#
|
43
|
+
# @return [Fixnum] the current value
|
44
|
+
#
|
45
|
+
# @raise [ArgumentError] if the new value is not a `Fixnum`
|
46
|
+
def value=(value)
|
47
|
+
raise ArgumentError.new('value must be a Fixnum') unless value.is_a?(Fixnum)
|
48
|
+
@mutex.lock
|
49
|
+
@value = value
|
50
|
+
ensure
|
51
|
+
@mutex.unlock
|
52
|
+
end
|
53
|
+
|
54
|
+
# @!macro [attach] atomic_fixnum_method_increment
|
55
|
+
#
|
56
|
+
# Increases the current value by 1.
|
57
|
+
#
|
58
|
+
# @return [Fixnum] the current value after incrementation
|
59
|
+
def increment
|
60
|
+
@mutex.lock
|
61
|
+
@value += 1
|
62
|
+
ensure
|
63
|
+
@mutex.unlock
|
64
|
+
end
|
65
|
+
|
66
|
+
alias_method :up, :increment
|
67
|
+
|
68
|
+
# @!macro [attach] atomic_fixnum_method_decrement
|
69
|
+
#
|
70
|
+
# Decreases the current value by 1.
|
71
|
+
#
|
72
|
+
# @return [Fixnum] the current value after decrementation
|
73
|
+
def decrement
|
74
|
+
@mutex.lock
|
75
|
+
@value -= 1
|
76
|
+
ensure
|
77
|
+
@mutex.unlock
|
78
|
+
end
|
79
|
+
|
80
|
+
alias_method :down, :decrement
|
81
|
+
|
82
|
+
# @!macro [attach] atomic_fixnum_method_compare_and_set
|
83
|
+
#
|
84
|
+
# Atomically sets the value to the given updated value if the current
|
85
|
+
# value == the expected value.
|
86
|
+
#
|
87
|
+
# @param [Fixnum] expect the expected value
|
88
|
+
# @param [Fixnum] update the new value
|
89
|
+
#
|
90
|
+
# @return [Boolean] true if the value was updated else false
|
91
|
+
def compare_and_set(expect, update)
|
92
|
+
@mutex.lock
|
93
|
+
if @value == expect
|
94
|
+
@value = update
|
95
|
+
true
|
96
|
+
else
|
97
|
+
false
|
98
|
+
end
|
99
|
+
ensure
|
100
|
+
@mutex.unlock
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
if RUBY_PLATFORM == 'java'
|
105
|
+
|
106
|
+
# @!macro atomic_fixnum
|
107
|
+
class JavaAtomicFixnum
|
108
|
+
|
109
|
+
# @!macro atomic_fixnum_method_initialize
|
110
|
+
#
|
111
|
+
def initialize(init = 0)
|
112
|
+
raise ArgumentError.new('initial value must be a Fixnum') unless init.is_a?(Fixnum)
|
113
|
+
@atomic = java.util.concurrent.atomic.AtomicLong.new(init)
|
114
|
+
end
|
115
|
+
|
116
|
+
# @!macro atomic_fixnum_method_value
|
117
|
+
#
|
118
|
+
def value
|
119
|
+
@atomic.get
|
120
|
+
end
|
121
|
+
|
122
|
+
# @!macro atomic_fixnum_method_value_eq
|
123
|
+
#
|
124
|
+
def value=(value)
|
125
|
+
raise ArgumentError.new('value must be a Fixnum') unless value.is_a?(Fixnum)
|
126
|
+
@atomic.set(value)
|
127
|
+
end
|
128
|
+
|
129
|
+
# @!macro atomic_fixnum_method_increment
|
130
|
+
#
|
131
|
+
def increment
|
132
|
+
@atomic.increment_and_get
|
133
|
+
end
|
134
|
+
|
135
|
+
alias_method :up, :increment
|
136
|
+
|
137
|
+
# @!macro atomic_fixnum_method_decrement
|
138
|
+
#
|
139
|
+
def decrement
|
140
|
+
@atomic.decrement_and_get
|
141
|
+
end
|
142
|
+
|
143
|
+
alias_method :down, :decrement
|
144
|
+
|
145
|
+
# @!macro atomic_fixnum_method_compare_and_set
|
146
|
+
#
|
147
|
+
def compare_and_set(expect, update)
|
148
|
+
@atomic.compare_and_set(expect, update)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# @!macro atomic_fixnum
|
153
|
+
class AtomicFixnum < JavaAtomicFixnum
|
154
|
+
end
|
155
|
+
|
156
|
+
else
|
157
|
+
|
158
|
+
# @!macro atomic_fixnum
|
159
|
+
class AtomicFixnum < MutexAtomicFixnum
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# Condition is a better implementation of standard Ruby ConditionVariable.
|
4
|
+
# The biggest difference is the wait return value: Condition#wait returns
|
5
|
+
# Condition::Result which make possible to know if waiting thread has been woken up
|
6
|
+
# by an another thread (using #signal or #broadcast) or due to timeout.
|
7
|
+
#
|
8
|
+
# Every #wait must be guarded by a locked Mutex or a ThreadError will be risen.
|
9
|
+
# Although it's not mandatory, it's recommended to call also #signal and #broadcast within
|
10
|
+
# the same mutex
|
11
|
+
class Condition
|
12
|
+
|
13
|
+
class Result
|
14
|
+
def initialize(remaining_time)
|
15
|
+
@remaining_time = remaining_time
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :remaining_time
|
19
|
+
|
20
|
+
# @return [Boolean] true if current thread has been waken up by a #signal or a #broadcast call, otherwise false
|
21
|
+
def woken_up?
|
22
|
+
@remaining_time.nil? || @remaining_time > 0
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean] true if current thread has been waken up due to a timeout, otherwise false
|
26
|
+
def timed_out?
|
27
|
+
@remaining_time != nil && @remaining_time <= 0
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :can_wait?, :woken_up?
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@condition = ConditionVariable.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param [Mutex] mutex the locked mutex guarding the wait
|
39
|
+
# @param [Object] timeout nil means no timeout
|
40
|
+
# @return [Result]
|
41
|
+
def wait(mutex, timeout = nil)
|
42
|
+
start_time = Time.now.to_f
|
43
|
+
@condition.wait(mutex, timeout)
|
44
|
+
|
45
|
+
if timeout.nil?
|
46
|
+
Result.new(nil)
|
47
|
+
else
|
48
|
+
Result.new(start_time + timeout - Time.now.to_f)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Wakes up a waiting thread
|
53
|
+
# @return [true]
|
54
|
+
def signal
|
55
|
+
@condition.signal
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
# Wakes up all waiting threads
|
60
|
+
# @return [true]
|
61
|
+
def broadcast
|
62
|
+
@condition.broadcast
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# A thread safe observer set implemented using copy-on-read approach:
|
4
|
+
# observers are added and removed from a thread safe collection; every time
|
5
|
+
# a notification is required the internal data structure is copied to
|
6
|
+
# prevent concurrency issues
|
7
|
+
class CopyOnNotifyObserverSet
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@mutex = Mutex.new
|
11
|
+
@observers = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Adds an observer to this set
|
15
|
+
# If a block is passed, the observer will be created by this method and no other params should be passed
|
16
|
+
# @param [Object] observer the observer to add
|
17
|
+
# @param [Symbol] func the function to call on the observer during notification. Default is :update
|
18
|
+
# @return [Object] the added observer
|
19
|
+
def add_observer(observer=nil, func=:update, &block)
|
20
|
+
if observer.nil? && block.nil?
|
21
|
+
raise ArgumentError, 'should pass observer as a first argument or block'
|
22
|
+
elsif observer && block
|
23
|
+
raise ArgumentError.new('cannot provide both an observer and a block')
|
24
|
+
end
|
25
|
+
|
26
|
+
if block
|
27
|
+
observer = block
|
28
|
+
func = :call
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
@mutex.lock
|
33
|
+
@observers[observer] = func
|
34
|
+
ensure
|
35
|
+
@mutex.unlock
|
36
|
+
end
|
37
|
+
|
38
|
+
observer
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [Object] observer the observer to remove
|
42
|
+
# @return [Object] the deleted observer
|
43
|
+
def delete_observer(observer)
|
44
|
+
@mutex.lock
|
45
|
+
@observers.delete(observer)
|
46
|
+
@mutex.unlock
|
47
|
+
|
48
|
+
observer
|
49
|
+
end
|
50
|
+
|
51
|
+
# Deletes all observers
|
52
|
+
# @return [CopyOnWriteObserverSet] self
|
53
|
+
def delete_observers
|
54
|
+
@mutex.lock
|
55
|
+
@observers.clear
|
56
|
+
@mutex.unlock
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Integer] the observers count
|
62
|
+
def count_observers
|
63
|
+
@mutex.lock
|
64
|
+
result = @observers.count
|
65
|
+
@mutex.unlock
|
66
|
+
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
# Notifies all registered observers with optional args
|
71
|
+
# @param [Object] args arguments to be passed to each observer
|
72
|
+
# @return [CopyOnWriteObserverSet] self
|
73
|
+
def notify_observers(*args, &block)
|
74
|
+
observers = duplicate_observers
|
75
|
+
notify_to(observers, *args, &block)
|
76
|
+
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# Notifies all registered observers with optional args and deletes them.
|
81
|
+
#
|
82
|
+
# @param [Object] args arguments to be passed to each observer
|
83
|
+
# @return [CopyOnWriteObserverSet] self
|
84
|
+
def notify_and_delete_observers(*args, &block)
|
85
|
+
observers = duplicate_and_clear_observers
|
86
|
+
notify_to(observers, *args, &block)
|
87
|
+
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def duplicate_and_clear_observers
|
94
|
+
@mutex.lock
|
95
|
+
observers = @observers.dup
|
96
|
+
@observers.clear
|
97
|
+
@mutex.unlock
|
98
|
+
|
99
|
+
observers
|
100
|
+
end
|
101
|
+
|
102
|
+
def duplicate_observers
|
103
|
+
@mutex.lock
|
104
|
+
observers = @observers.dup
|
105
|
+
@mutex.unlock
|
106
|
+
|
107
|
+
observers
|
108
|
+
end
|
109
|
+
|
110
|
+
def notify_to(observers, *args)
|
111
|
+
raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty?
|
112
|
+
observers.each do |observer, function|
|
113
|
+
args = yield if block_given?
|
114
|
+
observer.send(function, *args)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|