concurrent-ruby 0.7.0.rc0-x64-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- 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/2.0/concurrent_ruby_ext.so +0 -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 +141 -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
|