concurrent-ruby 0.6.0 → 0.6.1
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 +4 -4
- data/{LICENSE → LICENSE.txt} +0 -0
- data/README.md +55 -51
- data/lib/concurrent/actress.rb +152 -6
- data/lib/concurrent/actress/ad_hoc.rb +6 -0
- data/lib/concurrent/actress/context.rb +10 -8
- data/lib/concurrent/actress/core.rb +44 -20
- data/lib/concurrent/actress/core_delegations.rb +5 -0
- data/lib/concurrent/actress/envelope.rb +21 -5
- data/lib/concurrent/actress/reference.rb +5 -5
- data/lib/concurrent/agent.rb +9 -9
- data/lib/concurrent/configuration.rb +7 -2
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +0 -1
- data/lib/concurrent/executor/{one_by_one.rb → serialized_execution.rb} +21 -8
- data/lib/concurrent/executor/timer_set.rb +6 -1
- data/lib/concurrent/executors.rb +1 -1
- data/lib/concurrent/logging.rb +1 -1
- data/lib/concurrent/observable.rb +1 -1
- data/lib/concurrent/version.rb +1 -1
- data/spec/concurrent/actress_spec.rb +84 -64
- data/spec/concurrent/agent_spec.rb +2 -2
- data/spec/concurrent/configuration_spec.rb +0 -2
- data/spec/concurrent/executor/thread_pool_shared.rb +30 -0
- data/spec/concurrent/scheduled_task_spec.rb +2 -4
- data/spec/concurrent/supervisor_spec.rb +2 -1
- data/spec/concurrent/timer_task_spec.rb +0 -2
- data/spec/spec_helper.rb +10 -0
- data/spec/support/example_group_extensions.rb +14 -10
- metadata +5 -6
- data/lib/concurrent/actress/doc.md +0 -53
@@ -1,12 +1,24 @@
|
|
1
1
|
module Concurrent
|
2
2
|
module Actress
|
3
|
-
Envelope
|
3
|
+
class Envelope
|
4
4
|
include TypeCheck
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
# @!attribute [r] message
|
7
|
+
# @return [Object] a message
|
8
|
+
# @!attribute [r] ivar
|
9
|
+
# @return [IVar] an ivar which becomes resolved after message is processed
|
10
|
+
# @!attribute [r] sender
|
11
|
+
# @return [Reference, Thread] an actor or thread sending the message
|
12
|
+
# @!attribute [r] address
|
13
|
+
# @return [Reference] where this message will be delivered
|
14
|
+
|
15
|
+
attr_reader :message, :ivar, :sender, :address
|
16
|
+
|
17
|
+
def initialize(message, ivar, sender, address)
|
18
|
+
@message = message
|
19
|
+
@ivar = Type! ivar, IVar, NilClass
|
20
|
+
@sender = Type! sender, Reference, Thread
|
21
|
+
@address = Type! address, Reference
|
10
22
|
end
|
11
23
|
|
12
24
|
def sender_path
|
@@ -17,6 +29,10 @@ module Concurrent
|
|
17
29
|
end
|
18
30
|
end
|
19
31
|
|
32
|
+
def address_path
|
33
|
+
address.path
|
34
|
+
end
|
35
|
+
|
20
36
|
def reject!(error)
|
21
37
|
ivar.fail error unless ivar.nil?
|
22
38
|
end
|
@@ -2,8 +2,8 @@ module Concurrent
|
|
2
2
|
module Actress
|
3
3
|
|
4
4
|
# Reference is public interface of Actor instances. It is used for sending messages and can
|
5
|
-
# be freely passed around the program. It also provides some basic information about the actor
|
6
|
-
# see {CoreDelegations}
|
5
|
+
# be freely passed around the program. It also provides some basic information about the actor,
|
6
|
+
# see {CoreDelegations}.
|
7
7
|
class Reference
|
8
8
|
include TypeCheck
|
9
9
|
include CoreDelegations
|
@@ -43,14 +43,14 @@ module Concurrent
|
|
43
43
|
ask(message, ivar).value!
|
44
44
|
end
|
45
45
|
|
46
|
-
# behaves as #tell when no ivar and as #ask when ivar
|
46
|
+
# behaves as {#tell} when no ivar and as {#ask} when ivar
|
47
47
|
def message(message, ivar = nil)
|
48
|
-
core.on_envelope Envelope.new(message, ivar, Actress.current || Thread.current)
|
48
|
+
core.on_envelope Envelope.new(message, ivar, Actress.current || Thread.current, self)
|
49
49
|
return ivar || self
|
50
50
|
end
|
51
51
|
|
52
52
|
def to_s
|
53
|
-
"#<#{self.class} #{path}>"
|
53
|
+
"#<#{self.class} #{path} (#{actor_class})>"
|
54
54
|
end
|
55
55
|
|
56
56
|
alias_method :inspect, :to_s
|
data/lib/concurrent/agent.rb
CHANGED
@@ -62,14 +62,14 @@ module Concurrent
|
|
62
62
|
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
|
63
63
|
# returning the value returned from the proc
|
64
64
|
def initialize(initial, opts = {})
|
65
|
-
@value
|
66
|
-
@rescuers
|
67
|
-
@validator
|
68
|
-
@timeout
|
69
|
-
self.observers
|
70
|
-
@
|
71
|
-
@task_executor
|
72
|
-
@operation_executor
|
65
|
+
@value = initial
|
66
|
+
@rescuers = []
|
67
|
+
@validator = Proc.new { |result| true }
|
68
|
+
@timeout = opts.fetch(:timeout, TIMEOUT).freeze
|
69
|
+
self.observers = CopyOnWriteObserverSet.new
|
70
|
+
@serialized_execution = SerializedExecution.new
|
71
|
+
@task_executor = OptionsParser.get_task_executor_from(opts)
|
72
|
+
@operation_executor = OptionsParser.get_operation_executor_from(opts)
|
73
73
|
init_mutex
|
74
74
|
set_deref_options(opts)
|
75
75
|
end
|
@@ -180,7 +180,7 @@ module Concurrent
|
|
180
180
|
|
181
181
|
def post_on(executor, &block)
|
182
182
|
return nil if block.nil?
|
183
|
-
@
|
183
|
+
@serialized_execution.post(executor) { work(&block) }
|
184
184
|
true
|
185
185
|
end
|
186
186
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'thread'
|
2
2
|
require 'concurrent/delay'
|
3
3
|
require 'concurrent/errors'
|
4
|
+
require 'concurrent/atomic/atomic'
|
4
5
|
require 'concurrent/executor/thread_pool_executor'
|
5
6
|
require 'concurrent/executor/timer_set'
|
6
7
|
require 'concurrent/utility/processor_count'
|
@@ -111,8 +112,12 @@ module Concurrent
|
|
111
112
|
end
|
112
113
|
|
113
114
|
# create the default configuration on load
|
114
|
-
@configuration = Configuration.new
|
115
|
-
|
115
|
+
@configuration = Atomic.new Configuration.new
|
116
|
+
|
117
|
+
# @return [Configuration]
|
118
|
+
def self.configuration
|
119
|
+
@configuration.value
|
120
|
+
end
|
116
121
|
|
117
122
|
# Perform gem-level configuration.
|
118
123
|
#
|
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'concurrent/logging'
|
2
|
+
|
1
3
|
module Concurrent
|
2
4
|
|
3
|
-
# Ensures
|
4
|
-
|
5
|
-
|
5
|
+
# Ensures passed jobs in a serialized order never running at the same time.
|
6
|
+
class SerializedExecution
|
7
|
+
include Logging
|
6
8
|
|
7
9
|
Job = Struct.new(:executor, :args, :block) do
|
8
10
|
def call
|
@@ -30,10 +32,6 @@ module Concurrent
|
|
30
32
|
# @raise [ArgumentError] if no task is given
|
31
33
|
def post(executor, *args, &task)
|
32
34
|
return nil if task.nil?
|
33
|
-
# FIXME Agent#send-off will blow up here
|
34
|
-
# if executor.can_overflow?
|
35
|
-
# raise ArgumentError, 'OneByOne cannot be used in conjunction with executor which may overflow'
|
36
|
-
# end
|
37
35
|
|
38
36
|
job = Job.new executor, args, task
|
39
37
|
|
@@ -56,7 +54,22 @@ module Concurrent
|
|
56
54
|
private
|
57
55
|
|
58
56
|
def call_job(job)
|
59
|
-
|
57
|
+
did_it_run = begin
|
58
|
+
job.executor.post { work(job) }
|
59
|
+
true
|
60
|
+
rescue RejectedExecutionError => ex
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
64
|
+
# TODO not the best idea to run it myself
|
65
|
+
unless did_it_run
|
66
|
+
begin
|
67
|
+
work job
|
68
|
+
rescue => ex
|
69
|
+
# let it fail
|
70
|
+
log DEBUG, ex
|
71
|
+
end
|
72
|
+
end
|
60
73
|
end
|
61
74
|
|
62
75
|
# ensures next job is executed if any is stashed
|
@@ -61,7 +61,12 @@ module Concurrent
|
|
61
61
|
|
62
62
|
end
|
63
63
|
|
64
|
-
|
64
|
+
# For a timer, #kill is like an orderly shutdown, except we need to manually
|
65
|
+
# (and destructively) clear the queue first
|
66
|
+
def kill
|
67
|
+
@queue.clear
|
68
|
+
shutdown
|
69
|
+
end
|
65
70
|
|
66
71
|
# Calculate an Epoch time with milliseconds at which to execute a
|
67
72
|
# task. If the given time is a `Time` object it will be converted
|
data/lib/concurrent/executors.rb
CHANGED
@@ -6,4 +6,4 @@ require 'concurrent/executor/safe_task_executor'
|
|
6
6
|
require 'concurrent/executor/single_thread_executor'
|
7
7
|
require 'concurrent/executor/thread_pool_executor'
|
8
8
|
require 'concurrent/executor/timer_set'
|
9
|
-
require 'concurrent/executor/
|
9
|
+
require 'concurrent/executor/serialized_execution'
|
data/lib/concurrent/logging.rb
CHANGED
@@ -9,7 +9,7 @@ module Concurrent
|
|
9
9
|
# @param [Integer] level one of Logger::Severity constants
|
10
10
|
# @param [String] progname e.g. a path of an Actor
|
11
11
|
# @param [String, nil] message when nil block is used to generate the message
|
12
|
-
# @
|
12
|
+
# @yieldreturn [String] a message
|
13
13
|
def log(level, progname, message = nil, &block)
|
14
14
|
(@logger || Concurrent.configuration.logger).call level, progname, message, &block
|
15
15
|
end
|
@@ -10,7 +10,7 @@ module Concurrent
|
|
10
10
|
observers.add_observer(*args, &block)
|
11
11
|
end
|
12
12
|
|
13
|
-
# as #add_observer but it can be used for
|
13
|
+
# as #add_observer but it can be used for chaining
|
14
14
|
# @return [Observable] self
|
15
15
|
def with_observer(*args, &block)
|
16
16
|
add_observer *args, &block
|
data/lib/concurrent/version.rb
CHANGED
@@ -3,7 +3,27 @@ require 'concurrent/actress'
|
|
3
3
|
|
4
4
|
module Concurrent
|
5
5
|
module Actress
|
6
|
+
i_know_it_is_experimental!
|
7
|
+
|
8
|
+
class Reference
|
9
|
+
def backdoor(&block)
|
10
|
+
core.send :schedule_execution do
|
11
|
+
core.instance_eval &block
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
6
16
|
describe 'Concurrent::Actress' do
|
17
|
+
prepend_before do
|
18
|
+
do_no_reset!
|
19
|
+
end
|
20
|
+
|
21
|
+
def terminate_actors(*actors)
|
22
|
+
actors.each do |actor|
|
23
|
+
actor.backdoor { terminate! }
|
24
|
+
actor.terminated.wait
|
25
|
+
end
|
26
|
+
end
|
7
27
|
|
8
28
|
class Ping
|
9
29
|
include Context
|
@@ -35,82 +55,80 @@ module Concurrent
|
|
35
55
|
# set_trace_func nil
|
36
56
|
# end
|
37
57
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
58
|
+
describe 'stress test' do
|
59
|
+
1.times do |i|
|
60
|
+
it format('run %3d', i) do
|
61
|
+
# puts format('run %3d', i)
|
62
|
+
Array.new(10).map do
|
63
|
+
Thread.new do
|
64
|
+
10.times do
|
65
|
+
# trace! do
|
66
|
+
queue = Queue.new
|
67
|
+
actor = Ping.spawn :ping, queue
|
68
|
+
|
69
|
+
# when spawn returns children are set
|
70
|
+
Concurrent::Actress::ROOT.send(:core).instance_variable_get(:@children).should include(actor)
|
71
|
+
|
72
|
+
actor << 'a' << 1
|
73
|
+
queue.pop.should eq 'a'
|
74
|
+
actor.ask(2).value.should eq 2
|
75
|
+
|
76
|
+
actor.parent.should eq Concurrent::Actress::ROOT
|
77
|
+
Concurrent::Actress::ROOT.path.should eq '/'
|
78
|
+
actor.path.should eq '/ping'
|
79
|
+
child = actor.ask(:child).value
|
80
|
+
child.path.should eq '/ping/pong'
|
81
|
+
queue.clear
|
82
|
+
child.ask(3)
|
83
|
+
queue.pop.should eq 3
|
84
|
+
|
85
|
+
actor << :terminate
|
86
|
+
actor.ask(:blow_up).wait.should be_rejected
|
87
|
+
terminate_actors actor, child
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end.each(&:join)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
74
94
|
|
75
95
|
describe 'spawning' do
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
96
|
+
describe 'Actress#spawn' do
|
97
|
+
behaviour = -> v { -> _ { v } }
|
98
|
+
subjects = { spawn: -> { Actress.spawn(AdHoc, :ping, 'arg', &behaviour) },
|
99
|
+
context_spawn: -> { AdHoc.spawn(:ping, 'arg', &behaviour) },
|
100
|
+
spawn_by_hash: -> { Actress.spawn(class: AdHoc, name: :ping, args: ['arg'], &behaviour) },
|
101
|
+
context_spawn_by_hash: -> { AdHoc.spawn(name: :ping, args: ['arg'], &behaviour) } }
|
102
|
+
|
103
|
+
subjects.each do |desc, subject_definition|
|
104
|
+
describe desc do
|
105
|
+
subject &subject_definition
|
106
|
+
after { terminate_actors subject }
|
107
|
+
its(:path) { should eq '/ping' }
|
108
|
+
its(:parent) { should eq ROOT }
|
109
|
+
its(:name) { should eq 'ping' }
|
110
|
+
it('executor should be global') { subject.executor.should eq Concurrent.configuration.global_task_pool }
|
111
|
+
its(:reference) { should eq subject }
|
112
|
+
it 'returns ars' do
|
113
|
+
subject.ask!(:anything).should eq 'arg'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
97
118
|
|
98
119
|
it 'terminates on failed initialization' do
|
99
|
-
pending('may cause deadlock which prevents test run from completing.')
|
100
120
|
a = AdHoc.spawn(name: :fail, logger: Concurrent.configuration.no_logger) { raise }
|
101
121
|
a.ask(nil).wait.rejected?.should be_true
|
102
122
|
a.terminated?.should be_true
|
103
123
|
end
|
104
124
|
|
105
125
|
it 'terminates on failed initialization and raises with spawn!' do
|
106
|
-
pending('may cause deadlock which prevents test run from completing.')
|
107
126
|
expect do
|
108
127
|
AdHoc.spawn!(name: :fail, logger: Concurrent.configuration.no_logger) { raise 'm' }
|
109
128
|
end.to raise_error(StandardError, 'm')
|
110
129
|
end
|
111
130
|
|
112
131
|
it 'terminates on failed message processing' do
|
113
|
-
pending('may cause deadlock which prevents test run from completing.')
|
114
132
|
a = AdHoc.spawn(name: :fail, logger: Concurrent.configuration.no_logger) { -> _ { raise } }
|
115
133
|
a.ask(nil).wait.rejected?.should be_true
|
116
134
|
a.terminated?.should be_true
|
@@ -120,11 +138,11 @@ module Concurrent
|
|
120
138
|
describe 'messaging' do
|
121
139
|
subject { AdHoc.spawn(:add) { c = 0; -> v { c = c + v } } }
|
122
140
|
specify do
|
123
|
-
pending('may cause deadlock which prevents test run from completing.')
|
124
141
|
subject.tell(1).tell(1)
|
125
142
|
subject << 1 << 1
|
126
143
|
subject.ask(0).value!.should eq 4
|
127
144
|
end
|
145
|
+
after { terminate_actors subject }
|
128
146
|
end
|
129
147
|
|
130
148
|
describe 'children' do
|
@@ -141,23 +159,24 @@ module Concurrent
|
|
141
159
|
end
|
142
160
|
|
143
161
|
it 'has children set after a child is created' do
|
144
|
-
pending('may cause deadlock which prevents test run from completing.')
|
145
162
|
child = parent.ask!(:child)
|
146
163
|
parent.ask!(nil).should include(child)
|
147
164
|
child.ask!(nil).should eq parent
|
165
|
+
|
166
|
+
terminate_actors parent, child
|
148
167
|
end
|
149
168
|
end
|
150
169
|
|
151
170
|
describe 'envelope' do
|
152
171
|
subject { AdHoc.spawn(:subject) { -> _ { envelope } } }
|
153
172
|
specify do
|
154
|
-
pending('may cause deadlock which prevents test run from completing.')
|
155
173
|
envelope = subject.ask!('a')
|
156
174
|
envelope.should be_a_kind_of Envelope
|
157
175
|
envelope.message.should eq 'a'
|
158
176
|
envelope.ivar.should be_completed
|
159
177
|
envelope.ivar.value.should eq envelope
|
160
178
|
envelope.sender.should eq Thread.current
|
179
|
+
terminate_actors subject
|
161
180
|
end
|
162
181
|
end
|
163
182
|
|
@@ -176,13 +195,14 @@ module Concurrent
|
|
176
195
|
end
|
177
196
|
|
178
197
|
it 'terminates with all its children' do
|
179
|
-
pending('may cause deadlock which prevents test run from completing.')
|
180
198
|
child = subject.ask! :child
|
181
199
|
subject.terminated?.should be_false
|
182
200
|
subject.ask(:terminate).wait
|
183
201
|
subject.terminated?.should be_true
|
184
202
|
child.terminated.wait
|
185
203
|
child.terminated?.should be_true
|
204
|
+
|
205
|
+
terminate_actors subject, child
|
186
206
|
end
|
187
207
|
end
|
188
208
|
|