aws-flow-core 1.0.0
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.
- data/Gemfile +9 -0
- data/LICENSE.TXT +15 -0
- data/NOTICE.TXT +14 -0
- data/Rakefile +27 -0
- data/aws-flow-core.gemspec +12 -0
- data/lib/aws/flow.rb +26 -0
- data/lib/aws/flow/async_backtrace.rb +134 -0
- data/lib/aws/flow/async_scope.rb +195 -0
- data/lib/aws/flow/begin_rescue_ensure.rb +386 -0
- data/lib/aws/flow/fiber.rb +77 -0
- data/lib/aws/flow/flow_utils.rb +50 -0
- data/lib/aws/flow/future.rb +109 -0
- data/lib/aws/flow/implementation.rb +151 -0
- data/lib/aws/flow/simple_dfa.rb +85 -0
- data/lib/aws/flow/tasks.rb +405 -0
- data/test/aws/async_backtrace_spec.rb +41 -0
- data/test/aws/async_scope_spec.rb +118 -0
- data/test/aws/begin_rescue_ensure_spec.rb +665 -0
- data/test/aws/external_task_spec.rb +197 -0
- data/test/aws/factories.rb +52 -0
- data/test/aws/fiber_condition_variable_spec.rb +163 -0
- data/test/aws/fiber_spec.rb +78 -0
- data/test/aws/flow_spec.rb +255 -0
- data/test/aws/future_spec.rb +210 -0
- data/test/aws/rubyflow.rb +22 -0
- data/test/aws/simple_dfa_spec.rb +63 -0
- data/test/aws/spec_helper.rb +36 -0
- metadata +85 -0
@@ -0,0 +1,255 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
##
|
15
|
+
|
16
|
+
require 'pp'
|
17
|
+
|
18
|
+
# TODO Split out fibers-related tests into a fibers_spec and
|
19
|
+
# put the interface-related tests into something like interface_spec
|
20
|
+
# Or maybe they can stay in flow_spec
|
21
|
+
|
22
|
+
|
23
|
+
# Doesn't make sense to have this for daemon tasks, as the BRE will close before
|
24
|
+
# it raises
|
25
|
+
|
26
|
+
{:DaemonTask => lambda { |&x| daemon_task(&x) },
|
27
|
+
:Task => lambda { |&x| task(&x) } }.each_pair do |name, task_creation|
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
describe "#{name} method" do
|
33
|
+
let(:condition) { FiberConditionVariable.new }
|
34
|
+
# TODO Reinstate this test once we figure out how to fix Fibers in 1.8 sharing state
|
35
|
+
|
36
|
+
it "fails when called from outside of a context" do
|
37
|
+
expect do
|
38
|
+
task_creation.call do
|
39
|
+
"Invalid call"
|
40
|
+
end
|
41
|
+
end.to raise_error(NoContextException)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should execute #{name} children recursively"do
|
45
|
+
@executed = []
|
46
|
+
def recursive(i, task_creation)
|
47
|
+
return if i == 0
|
48
|
+
task_creation.call do
|
49
|
+
recursive(i - 1, task_creation)
|
50
|
+
@executed << i
|
51
|
+
end
|
52
|
+
end
|
53
|
+
scope = AsyncScope.new do
|
54
|
+
task { condition.wait }
|
55
|
+
recursive(100, task_creation)
|
56
|
+
end
|
57
|
+
@executed.length.should == 0
|
58
|
+
scope.eventLoop
|
59
|
+
@executed.length.should == 100
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
context 'tests that #{name} do things in the right order' do
|
65
|
+
let(:trace) { [] }
|
66
|
+
let(:condition) { FiberConditionVariable.new }
|
67
|
+
it "executes the block asynchronously" do
|
68
|
+
trace << :outside_task
|
69
|
+
scope = AsyncScope.new do
|
70
|
+
task_creation.call { trace << :inside_task; condition.signal }
|
71
|
+
task { condition.wait }
|
72
|
+
end
|
73
|
+
scope.eventLoop
|
74
|
+
trace.should == [:outside_task, :inside_task]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "returns something which contains the block's return value" do
|
78
|
+
result = nil
|
79
|
+
scope = AsyncScope.new do
|
80
|
+
result = task_creation.call { condition.signal; :task_executed }
|
81
|
+
task { condition.wait }
|
82
|
+
end
|
83
|
+
scope.eventLoop
|
84
|
+
result.get.should == :task_executed
|
85
|
+
end
|
86
|
+
|
87
|
+
it "executes multiple #{name}s in order" do
|
88
|
+
scope = AsyncScope.new do
|
89
|
+
task_creation.call { trace << :first_task }
|
90
|
+
task_creation.call { trace << :second_task; condition.signal }
|
91
|
+
task { condition.wait }
|
92
|
+
end
|
93
|
+
scope.eventLoop
|
94
|
+
trace.should == [:first_task, :second_task]
|
95
|
+
end
|
96
|
+
|
97
|
+
it "executes nested #{name} after the parent task" do
|
98
|
+
|
99
|
+
scope = AsyncScope.new do
|
100
|
+
task_creation.call do
|
101
|
+
task_creation.call { trace << :inside_task; condition.signal }
|
102
|
+
trace << :outside_task
|
103
|
+
end
|
104
|
+
task { condition.wait }
|
105
|
+
end
|
106
|
+
scope.eventLoop
|
107
|
+
trace.should == [:outside_task, :inside_task]
|
108
|
+
end
|
109
|
+
|
110
|
+
it "executes nested #{name}" do
|
111
|
+
scope = AsyncScope.new do
|
112
|
+
task_creation.call do
|
113
|
+
trace << :outside_task
|
114
|
+
task_creation.call { trace << :inside_task; condition.signal }
|
115
|
+
end
|
116
|
+
task { condition.wait }
|
117
|
+
end
|
118
|
+
scope.eventLoop
|
119
|
+
trace.should == [:outside_task, :inside_task]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
{:Task => lambda { |x, &y| Task.new(x, &y) },
|
125
|
+
:DaemonTask => lambda { |x, &y| DaemonTask.new(x, &y) }}.each_pair do |name, task_creation|
|
126
|
+
|
127
|
+
describe name do
|
128
|
+
let(:trace) { [] }
|
129
|
+
let(:scope) { AsyncScope.new }
|
130
|
+
let(:task) { task_creation.call(scope.root_context) { trace << :inner } }
|
131
|
+
|
132
|
+
context "result for a task that is not run" do
|
133
|
+
let(:result) { task.result }
|
134
|
+
subject { result }
|
135
|
+
its(:class) { should == Future }
|
136
|
+
it { should_not be_set }
|
137
|
+
end
|
138
|
+
|
139
|
+
it "executes a block asynchronously" do
|
140
|
+
trace << :outer
|
141
|
+
task.resume
|
142
|
+
trace.should == [:outer, :inner]
|
143
|
+
end
|
144
|
+
|
145
|
+
it "is the currently running Fiber when the block executes" do
|
146
|
+
task = task_creation.call(scope.root_context) do
|
147
|
+
fiber = ::Fiber.current
|
148
|
+
fiber.should == task
|
149
|
+
trace << :executed
|
150
|
+
end
|
151
|
+
task.resume
|
152
|
+
trace.should == [:executed]
|
153
|
+
end
|
154
|
+
it "doesn't let the block return from the block's parent method" do
|
155
|
+
def create_and_run_task(task_creation)
|
156
|
+
t = task_creation.call(scope.root_context) { return :inner_return }
|
157
|
+
t.resume
|
158
|
+
:outer_return
|
159
|
+
end
|
160
|
+
result = create_and_run_task(task_creation)
|
161
|
+
result.should == :outer_return
|
162
|
+
end
|
163
|
+
|
164
|
+
# context "cancelled Task" do
|
165
|
+
# it "makes sure that a cancelled task does not run" do
|
166
|
+
# task = task_creation.call(scope.root_context) { trace << :should_never_happen }
|
167
|
+
# scope << task
|
168
|
+
# task.cancel(StandardError)
|
169
|
+
|
170
|
+
# trace.should == []
|
171
|
+
# end
|
172
|
+
# end
|
173
|
+
|
174
|
+
context "Dead Fiber" do
|
175
|
+
let(:dead_task) { t = task_creation.call(scope.root_context) { 1 + 1 }; t.resume; t }
|
176
|
+
subject { dead_task }
|
177
|
+
it { should_not be_alive }
|
178
|
+
it "should raise FiberError on resume" do
|
179
|
+
expect { dead_task.resume }.to raise_error FiberError
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
it "makes sure that tasks return a Future" do
|
184
|
+
a = task_creation.call(scope.root_context) { trace << :first }
|
185
|
+
b = task_creation.call(scope.root_context) do
|
186
|
+
a.result.get
|
187
|
+
trace << :second
|
188
|
+
end
|
189
|
+
scope << a
|
190
|
+
scope << b
|
191
|
+
scope.eventLoop
|
192
|
+
trace.should == [:first, :second]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
{:Fiber => lambda {|&block| Fiber.new &block},
|
198
|
+
:Task => lambda {|&block| Task.new(AsyncScope.new.root_context, &block)},
|
199
|
+
}.each_pair do |klass, initialize|
|
200
|
+
describe "#{klass}#alive?" do
|
201
|
+
let(:simple_unit) { initialize.call { 2 + 2 } }
|
202
|
+
let(:waiting_unit) { initialize.call { Fiber.yield } }
|
203
|
+
it "tells you the #{klass} is alive before the #{klass} starts" do
|
204
|
+
simple_unit.alive?.should == true
|
205
|
+
end
|
206
|
+
|
207
|
+
it "tells you the #{klass} is not alive once the #{klass} has exited" do
|
208
|
+
simple_unit.resume
|
209
|
+
simple_unit.alive?.should == false
|
210
|
+
end
|
211
|
+
|
212
|
+
it "tells you the #{klass} is alive if the #{klass} is partially executed" do
|
213
|
+
waiting_unit.resume
|
214
|
+
# TODO: Discuss whether this should fail or not
|
215
|
+
waiting_unit.alive?.should == true
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe Task do
|
221
|
+
|
222
|
+
it "ensures that raising an error in a task is handled correctly" do
|
223
|
+
scope = AsyncScope.new do
|
224
|
+
task do
|
225
|
+
raise "Boo"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
begin
|
229
|
+
scope.eventLoop
|
230
|
+
rescue Exception => e
|
231
|
+
e.message.should =~ /Boo.*/
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it "ensures that cancelling a task will not have it remove itself twice " do
|
236
|
+
condition = FiberConditionVariable.new
|
237
|
+
scope = AsyncScope.new do
|
238
|
+
error_handler do |t|
|
239
|
+
t.begin do
|
240
|
+
task do
|
241
|
+
condition.wait
|
242
|
+
end
|
243
|
+
task do
|
244
|
+
condition.signal
|
245
|
+
# i.e.,
|
246
|
+
raise "simulated error"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
expect { scope.eventLoop }.to raise_error "simulated error"
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
##
|
15
|
+
|
16
|
+
describe Future do
|
17
|
+
let(:future) { Future.new }
|
18
|
+
let(:trace) { [] }
|
19
|
+
it "throws an exception when set multiple times" do
|
20
|
+
future.set(:foo)
|
21
|
+
expect { future.set(:bar) }.to raise_error AlreadySetException
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns the value which was set" do
|
25
|
+
value = :foo
|
26
|
+
future.set(value)
|
27
|
+
future.get.should == value
|
28
|
+
end
|
29
|
+
|
30
|
+
it "blocks a task until a value is ready" do
|
31
|
+
scope = AsyncScope.new do
|
32
|
+
task do
|
33
|
+
trace << :first
|
34
|
+
future.get
|
35
|
+
# TODO technically this should only be checked in a TaskContext test;
|
36
|
+
# a Future test would just make sure that this comes after :second
|
37
|
+
trace << :fourth
|
38
|
+
end
|
39
|
+
|
40
|
+
task do
|
41
|
+
trace << :second
|
42
|
+
future.set(:foo)
|
43
|
+
trace << :third
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
scope.eventLoop
|
48
|
+
trace.should == [:first, :second, :third, :fourth]
|
49
|
+
end
|
50
|
+
|
51
|
+
it "blocks multiple Fibers until a value is ready" do
|
52
|
+
scope = AsyncScope.new do
|
53
|
+
task do
|
54
|
+
trace << :first
|
55
|
+
future.get
|
56
|
+
trace << :fifth
|
57
|
+
end
|
58
|
+
|
59
|
+
task do
|
60
|
+
trace << :second
|
61
|
+
future.get
|
62
|
+
trace << :sixth
|
63
|
+
end
|
64
|
+
|
65
|
+
task do
|
66
|
+
trace << :third
|
67
|
+
future.set(:foo)
|
68
|
+
trace << :fourth
|
69
|
+
end
|
70
|
+
end
|
71
|
+
scope.eventLoop
|
72
|
+
trace.should == [:first, :second, :third, :fourth, :fifth, :sixth]
|
73
|
+
end
|
74
|
+
|
75
|
+
it "supports wait_for_any" do
|
76
|
+
scope = AsyncScope.new do
|
77
|
+
f1 = Future.new
|
78
|
+
f2 = Future.new
|
79
|
+
f3 = Future.new
|
80
|
+
trace << :before_task
|
81
|
+
task do
|
82
|
+
trace << :before_set
|
83
|
+
f2.set("bar")
|
84
|
+
trace << :after_set
|
85
|
+
end
|
86
|
+
trace << :before_wait
|
87
|
+
|
88
|
+
f = Future.wait_for_any(f1, f2, f3).first
|
89
|
+
trace << :after_wait
|
90
|
+
f.should == f2
|
91
|
+
f.get.should == "bar"
|
92
|
+
end
|
93
|
+
trace.length.should == 0
|
94
|
+
scope.eventLoop
|
95
|
+
trace.should == [:before_task, :before_wait, :before_set, :after_set, :after_wait]
|
96
|
+
end
|
97
|
+
|
98
|
+
it "supports wait_for_any with all set" do
|
99
|
+
scope = AsyncScope.new do
|
100
|
+
f1 = Future.new
|
101
|
+
f2 = Future.new
|
102
|
+
f3 = Future.new
|
103
|
+
trace << :before_task
|
104
|
+
task do
|
105
|
+
trace << :before_set
|
106
|
+
f2.set("bar")
|
107
|
+
f1.set("baz")
|
108
|
+
f3.set("foo")
|
109
|
+
trace << :after_set
|
110
|
+
end
|
111
|
+
trace << :before_wait
|
112
|
+
r1, r2, r3 = Future.wait_for_any(f1, f2, f3)
|
113
|
+
trace << :after_wait
|
114
|
+
r1.should == f2
|
115
|
+
r1.get.should == "bar"
|
116
|
+
r2.should == f1
|
117
|
+
r2.get.should == "baz"
|
118
|
+
r3.should == f3
|
119
|
+
r3.get.should == "foo"
|
120
|
+
end
|
121
|
+
trace.length.should == 0
|
122
|
+
scope.eventLoop
|
123
|
+
trace.should == [:before_task, :before_wait, :before_set, :after_set, :after_wait]
|
124
|
+
end
|
125
|
+
|
126
|
+
it "supports wait_for_all" do
|
127
|
+
scope = AsyncScope.new do
|
128
|
+
f1 = Future.new
|
129
|
+
f2 = Future.new
|
130
|
+
f3 = Future.new
|
131
|
+
trace << :before_task
|
132
|
+
task do
|
133
|
+
trace << :before_f2_set
|
134
|
+
f2.set("bar")
|
135
|
+
task do
|
136
|
+
trace << :before_f1_set
|
137
|
+
f1.set("baz")
|
138
|
+
task do
|
139
|
+
trace << :before_f3_set
|
140
|
+
f3.set("foo")
|
141
|
+
trace << :after_set
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
trace << :before_wait
|
146
|
+
r1, r2, r3 = Future.wait_for_all(f1, f2, f3)
|
147
|
+
trace << :after_wait
|
148
|
+
r1.should == f2
|
149
|
+
r1.get.should == "bar"
|
150
|
+
r2.should == f1
|
151
|
+
r2.get.should == "baz"
|
152
|
+
r3.should == f3
|
153
|
+
r3.get.should == "foo"
|
154
|
+
end
|
155
|
+
trace.length.should == 0
|
156
|
+
scope.eventLoop
|
157
|
+
trace.should == [:before_task, :before_wait, :before_f2_set, :before_f1_set, :before_f3_set, :after_set, :after_wait]
|
158
|
+
end
|
159
|
+
|
160
|
+
it "supports wait_for_all with no futures" do
|
161
|
+
scope = AsyncScope.new do
|
162
|
+
task do
|
163
|
+
wait_for_all
|
164
|
+
end
|
165
|
+
end
|
166
|
+
scope.eventLoop
|
167
|
+
scope.is_complete?.should == true
|
168
|
+
end
|
169
|
+
|
170
|
+
it "supports wait_for_any with no futures" do
|
171
|
+
scope = AsyncScope.new do
|
172
|
+
task do
|
173
|
+
wait_for_any
|
174
|
+
end
|
175
|
+
end
|
176
|
+
scope.eventLoop
|
177
|
+
scope.is_complete?.should == true
|
178
|
+
end
|
179
|
+
|
180
|
+
it "supports wait_for_all with a set future" do
|
181
|
+
scope = AsyncScope.new do
|
182
|
+
future = Future.new.set
|
183
|
+
wait_for_all(future)
|
184
|
+
end
|
185
|
+
scope.eventLoop
|
186
|
+
scope.is_complete?.should == true
|
187
|
+
end
|
188
|
+
|
189
|
+
it "supports wait_for_any with a set future" do
|
190
|
+
scope = AsyncScope.new do
|
191
|
+
future = Future.new.set
|
192
|
+
wait_for_any(future)
|
193
|
+
end
|
194
|
+
scope.eventLoop
|
195
|
+
scope.is_complete?.should == true
|
196
|
+
end
|
197
|
+
|
198
|
+
it "supports wait_for_all with one set future and one not set" do
|
199
|
+
future2 = Future.new
|
200
|
+
scope = AsyncScope.new do
|
201
|
+
future = Future.new.set
|
202
|
+
wait_for_all(future, future2)
|
203
|
+
end
|
204
|
+
scope.eventLoop
|
205
|
+
future2.set
|
206
|
+
scope.eventLoop
|
207
|
+
scope.is_complete?.should == true
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|