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,405 @@
|
|
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
|
+
# Contains all the task methods, which allow arbitrary blocks of code to be run asynchronously
|
17
|
+
|
18
|
+
module AWS
|
19
|
+
module Flow
|
20
|
+
module Core
|
21
|
+
class Task < FlowFiber
|
22
|
+
attr_reader :result, :block
|
23
|
+
attr_accessor :backtrace, :__context__, :parent
|
24
|
+
|
25
|
+
# Creates a new Task.
|
26
|
+
#
|
27
|
+
# @param __context__
|
28
|
+
# A Task needs a reference to the __context__ that created it so that when the "task" macro is called it can
|
29
|
+
# find the __context__ which the new Task should be added to.
|
30
|
+
#
|
31
|
+
# @param block
|
32
|
+
# A block of code that will be run by the task.
|
33
|
+
#
|
34
|
+
def initialize(__context__, &block)
|
35
|
+
@__context__ = __context__
|
36
|
+
@result = Future.new
|
37
|
+
@block = block
|
38
|
+
|
39
|
+
# Is the task alive?
|
40
|
+
def alive?
|
41
|
+
super && !@cancelled
|
42
|
+
#!!@alive# && !@cancelled
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return
|
46
|
+
# The executor for this task.
|
47
|
+
def executor
|
48
|
+
@__context__.executor
|
49
|
+
end
|
50
|
+
|
51
|
+
super() do
|
52
|
+
begin
|
53
|
+
# Not return because 1.9 will freak about local jump problems if you
|
54
|
+
# try to return, as this is inside a block
|
55
|
+
next if @cancelled
|
56
|
+
@result.set(lambda(&block).call)
|
57
|
+
next if @cancelled
|
58
|
+
@__context__.remove(self)
|
59
|
+
rescue Exception => e
|
60
|
+
if @backtrace != e
|
61
|
+
backtrace = AsyncBacktrace.create_from_exception(@backtrace, e)
|
62
|
+
e.set_backtrace(backtrace.backtrace) if backtrace
|
63
|
+
end
|
64
|
+
@__context__.fail(self, e)
|
65
|
+
ensure
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Passes the get_heirs calls to the context, to ensure uniform handling of get_heirs
|
72
|
+
#
|
73
|
+
def get_heirs
|
74
|
+
@__context__.get_heirs
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Returns true/false, depending on if we are in a daemon task or not
|
79
|
+
#
|
80
|
+
def is_daemon?
|
81
|
+
return false
|
82
|
+
end
|
83
|
+
|
84
|
+
# Used by Future#signal to schedule the task for re-evaluation.
|
85
|
+
#
|
86
|
+
# This will simply add the task back to the list of things to be run in the parent's event loop.
|
87
|
+
#
|
88
|
+
def schedule
|
89
|
+
@__context__ << self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Cancel will prevent the execution of this particular task, if possible
|
93
|
+
#
|
94
|
+
# @param error
|
95
|
+
# The error that is the cause of the cancellation
|
96
|
+
#
|
97
|
+
def cancel(error)
|
98
|
+
@cancelled = true
|
99
|
+
@__context__.remove(self)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Fails the given task with the specified error.
|
103
|
+
#
|
104
|
+
# @param error
|
105
|
+
# The error that is the cause of the cancellation
|
106
|
+
#
|
107
|
+
# @!visibility private
|
108
|
+
def fail(this_task, error)
|
109
|
+
@__context__.fail(this_task, error)
|
110
|
+
end
|
111
|
+
# def fail(this_task, error)
|
112
|
+
# raise error
|
113
|
+
# end
|
114
|
+
|
115
|
+
# Adds a task to this task's context
|
116
|
+
#
|
117
|
+
# @param this_task
|
118
|
+
# The task to add.
|
119
|
+
#
|
120
|
+
def <<(this_task)
|
121
|
+
@__context__.parent << this_task
|
122
|
+
end
|
123
|
+
|
124
|
+
# Removes a task from this task's context
|
125
|
+
#
|
126
|
+
# @param this_task
|
127
|
+
# The task to remove.
|
128
|
+
#
|
129
|
+
def remove(this_task)
|
130
|
+
@__context__.remove(this_task)
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
# Similar to a regular Task in all functioning except that it is not assured a chance to execute. Whereas a
|
138
|
+
# begin/run/execute block cannot be closed out while there are still nonDaemonHeirs, it can happily entered the
|
139
|
+
# closed state with daemon heirs, making them essentially unrunnable.
|
140
|
+
class DaemonTask < Task
|
141
|
+
|
142
|
+
def is_daemon?
|
143
|
+
return true
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
# External Tasks are used to bridge asynchronous execution to external asynchronous APIs or events. It is passed a block, like so
|
149
|
+
#
|
150
|
+
# external_task do |t|
|
151
|
+
# t.cancellation_handler { |h, cause| h.fail(cause) }
|
152
|
+
# t.initiate_task { |h| trace << :task_started; h.complete; }
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# The {ExternalTask#initiate_task} method is expected to call an external API and return without blocking.
|
156
|
+
# Completion or failure of the external task is reported through ExternalTaskCompletionHandle, which is passed into
|
157
|
+
# the initiate_task and cancellation_handler blocks. The cancellation handler, defined in the same block as the
|
158
|
+
# initiate_task, is used to report the cancellation of the external task.
|
159
|
+
#
|
160
|
+
class ExternalTask < FlowFiber
|
161
|
+
attr_reader :block
|
162
|
+
attr_accessor :cancelled, :inCancellationHandler, :parent, :backtrace, :__context__
|
163
|
+
|
164
|
+
|
165
|
+
# Will always be false, provides a common api for BRE's to ensure they are maintaining their nonDaemonHeirsCount
|
166
|
+
# correctly
|
167
|
+
# @!visibility private
|
168
|
+
def is_daemon?
|
169
|
+
false
|
170
|
+
end
|
171
|
+
|
172
|
+
#
|
173
|
+
# Passes the get_heirs calls to the context, to ensure uniform handling of
|
174
|
+
# get_heirs
|
175
|
+
#
|
176
|
+
# @!visibility private
|
177
|
+
def get_heirs
|
178
|
+
@__context__.get_heirs
|
179
|
+
end
|
180
|
+
|
181
|
+
# @!visibility private
|
182
|
+
def initialize(options = {}, &block)
|
183
|
+
@inCancellationHandler = false
|
184
|
+
@block = block
|
185
|
+
# TODO: What should the default value be?
|
186
|
+
@parent = options[:parent]
|
187
|
+
@handle = ExternalTaskCompletionHandle.new(self)
|
188
|
+
block.call(self)
|
189
|
+
end
|
190
|
+
|
191
|
+
# This method is here because the way we create ExternalTasks is a little
|
192
|
+
# tricky - if the parent isn't passed in on construction(as is the case with
|
193
|
+
# the external_task function), then the parent will only be set after
|
194
|
+
# ExternalTask#initialize is called. We'd prefer to set it in the initiailze,
|
195
|
+
# however, the backtrace relies on the parent's backtrace, and so we cannot do
|
196
|
+
# that. Instead, we use this method to lazily create it, when it is
|
197
|
+
# called. The method itself simply sets the backtrace to the the
|
198
|
+
# make_backtrace of the parent's backtrace, if the backtrace is not already
|
199
|
+
# set, and will otherwise simply return the backtrace
|
200
|
+
# @!visibility private
|
201
|
+
def backtrace
|
202
|
+
@backtrace ||= make_backtrace(@parent.backtrace)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Add a task which removes yourself, and pass it through the parents executor
|
206
|
+
# @!visibility private
|
207
|
+
def remove_from_parent
|
208
|
+
@__context__.executor << FlowFiber.new { @parent.remove(self) }
|
209
|
+
end
|
210
|
+
|
211
|
+
# Add a task which fails yourself with the suppiled error, and pass it through
|
212
|
+
# the parents executor
|
213
|
+
# @!visibility private
|
214
|
+
def fail_to_parent(error)
|
215
|
+
@__context__.executor << FlowFiber.new { @parent.fail(self, error) }
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
# Part of the interface provided by Fiber, has to overridden to properly
|
220
|
+
# reflect that an ExternalTasks alive-ness relies on it's
|
221
|
+
# ExternalTaskCompletionHandle
|
222
|
+
# @!visibility private
|
223
|
+
def alive?
|
224
|
+
! @handle.completed
|
225
|
+
end
|
226
|
+
|
227
|
+
# @!visibility private
|
228
|
+
def cancel(cause)
|
229
|
+
return if @cancelled
|
230
|
+
return if @handle.failure != nil || @handle.completed
|
231
|
+
@cancelled = true
|
232
|
+
if @cancellation_task != nil
|
233
|
+
begin
|
234
|
+
@inCancellationHandler = true
|
235
|
+
@cancellation_task.call(cause)
|
236
|
+
rescue Exception => e
|
237
|
+
if ! self.backtrace.nil?
|
238
|
+
backtrace = AsyncBacktrace.create_from_exception(@backtrace, e)
|
239
|
+
e.set_backtrace(backtrace.backtrace) if backtrace
|
240
|
+
end
|
241
|
+
@handle.failure = e
|
242
|
+
ensure
|
243
|
+
@inCancellationHandler = false
|
244
|
+
if ! @handle.failure.nil?
|
245
|
+
fail_to_parent(@handle.failure)
|
246
|
+
elsif @handle.completed
|
247
|
+
remove_from_parent
|
248
|
+
end
|
249
|
+
end
|
250
|
+
else
|
251
|
+
remove_from_parent
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Store the cancellation handler block passed in for later reference
|
256
|
+
# @!visibility private
|
257
|
+
def cancellation_handler(&block)
|
258
|
+
@cancellation_task = lambda { |cause| block.call(@handle, cause) }
|
259
|
+
end
|
260
|
+
|
261
|
+
# Store the block passed in for later
|
262
|
+
# @!visibility private
|
263
|
+
def initiate_task(&block)
|
264
|
+
@initiation_task = lambda { block.call(@handle) }
|
265
|
+
end
|
266
|
+
|
267
|
+
# From the interface provided by Fiber, will execute the External Task
|
268
|
+
# @!visibility private
|
269
|
+
def resume
|
270
|
+
return if @cancelled
|
271
|
+
begin
|
272
|
+
@cancellation_handler = @initiation_task.call
|
273
|
+
rescue Exception => e
|
274
|
+
backtrace = AsyncBacktrace.create_from_exception(self.backtrace, e)
|
275
|
+
e.set_backtrace(backtrace.backtrace) if backtrace
|
276
|
+
@parent.fail(self, e)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Used to complete or fail an external task initiated through
|
282
|
+
# ExternalTask#initiate_task, and thus handles the logic of what to do when the
|
283
|
+
# external task is failed.
|
284
|
+
# @!visibility private
|
285
|
+
class ExternalTaskCompletionHandle
|
286
|
+
attr_accessor :completed, :failure, :external_task
|
287
|
+
|
288
|
+
# @!visibility private
|
289
|
+
def initialize(external_task)
|
290
|
+
@external_task = external_task
|
291
|
+
end
|
292
|
+
|
293
|
+
# Will merge the backtrace, set the @failure, and then fail the task from the parent
|
294
|
+
# @!visibility private
|
295
|
+
#
|
296
|
+
# @param error
|
297
|
+
# The exception to fail on
|
298
|
+
#
|
299
|
+
# @raise IllegalStateException
|
300
|
+
# Raises if failure hasn't been set, or if the task is already completed
|
301
|
+
#
|
302
|
+
# @!visibility private
|
303
|
+
def fail(error)
|
304
|
+
if ! @failure.nil?
|
305
|
+
raise IllegalStateException, "Invalid ExternalTaskCompletionHandle"
|
306
|
+
end
|
307
|
+
if @completed
|
308
|
+
raise IllegalStateException, "Already completed"
|
309
|
+
end
|
310
|
+
#TODO Might want to flip the logic to only alert if variable is set
|
311
|
+
if @stacktrace.nil?
|
312
|
+
if ! @external_task.backtrace.nil?
|
313
|
+
backtrace = AsyncBacktrace.create_from_exception(@external_task.backtrace, error)
|
314
|
+
error.set_backtrace(backtrace.backtrace) if backtrace
|
315
|
+
end
|
316
|
+
end
|
317
|
+
@failure = error
|
318
|
+
if ! @external_task.inCancellationHandler
|
319
|
+
@external_task.fail_to_parent(error)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Set's the task to complete, and removes it from it's parent
|
324
|
+
#
|
325
|
+
# @raise IllegalStateException
|
326
|
+
# If the failure hasn't been set, or if the task is already completed
|
327
|
+
#
|
328
|
+
# @!visibility private
|
329
|
+
def complete
|
330
|
+
if ! failure.nil?
|
331
|
+
raise IllegalStateException, ""
|
332
|
+
end
|
333
|
+
|
334
|
+
if @completed
|
335
|
+
raise IllegalStateException, "Already Completed"
|
336
|
+
end
|
337
|
+
@completed = true
|
338
|
+
@external_task.remove_from_parent if ! @external_task.inCancellationHandler
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# TaskContext is the class that holds some meta-information for tasks, and which stores the parent link for tasks.
|
343
|
+
# It seperates some of the concerns between tasks and what they have to know to follow back up the chain.
|
344
|
+
#
|
345
|
+
# All the methods here will simply delegate calls, either up to the parent, or down to the task
|
346
|
+
# @!visibility private
|
347
|
+
class TaskContext
|
348
|
+
|
349
|
+
attr_accessor :daemon, :parent, :backtrace, :cancelled
|
350
|
+
|
351
|
+
def initialize(options = {})
|
352
|
+
@parent = options[:parent]
|
353
|
+
@task = options[:task]
|
354
|
+
@task.__context__ = self
|
355
|
+
@non_cancelling = options[:non_cancelling]
|
356
|
+
@daemon = options[:daemon]
|
357
|
+
end
|
358
|
+
|
359
|
+
# @!visibility private
|
360
|
+
def get_closest_containing_scope
|
361
|
+
@task
|
362
|
+
# @ parent
|
363
|
+
end
|
364
|
+
|
365
|
+
# @!visibility private
|
366
|
+
def alive?
|
367
|
+
@task.alive?
|
368
|
+
end
|
369
|
+
|
370
|
+
# @!visibility private
|
371
|
+
def executor
|
372
|
+
@parent.executor
|
373
|
+
end
|
374
|
+
|
375
|
+
# @!visibility private
|
376
|
+
def get_heirs
|
377
|
+
str = "I am a #{@task.class}
|
378
|
+
and my block looks like #{@task.block}"
|
379
|
+
end
|
380
|
+
|
381
|
+
# @!visibility private
|
382
|
+
def fail(this_task, error)
|
383
|
+
@parent.fail(this_task, error)
|
384
|
+
end
|
385
|
+
|
386
|
+
# @!visibility private
|
387
|
+
def remove(thread)
|
388
|
+
@parent.remove(thread)
|
389
|
+
end
|
390
|
+
|
391
|
+
# @!visibility private
|
392
|
+
def cancel(error_type)
|
393
|
+
@task.cancelled = true
|
394
|
+
@parent.cancel(self)
|
395
|
+
end
|
396
|
+
|
397
|
+
# @!visibility private
|
398
|
+
def <<(task)
|
399
|
+
@parent << task
|
400
|
+
end
|
401
|
+
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
@@ -0,0 +1,41 @@
|
|
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
|
+
def validate_stacktrace_content(method_to_call_on_async_backtrace, thing_to_look_for, should_it_be_there)
|
17
|
+
it "makes sure that any of #{thing_to_look_for.join", "} #{should_it_be_there} be printed when we call #{method_to_call_on_async_backtrace} on AsyncBacktrace" do
|
18
|
+
AsyncBacktrace.enable
|
19
|
+
AsyncBacktrace.send(method_to_call_on_async_backtrace)
|
20
|
+
scope = AsyncScope.new do
|
21
|
+
error_handler do |t|
|
22
|
+
t.begin { raise StandardError.new }
|
23
|
+
t.rescue(IOError) {}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
begin
|
27
|
+
scope.eventLoop
|
28
|
+
rescue Exception => e
|
29
|
+
matching_lines = thing_to_look_for.select { |part| e.backtrace.to_s.include? part.to_s }
|
30
|
+
|
31
|
+
matching_lines.send(should_it_be_there, be_empty)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe AsyncBacktrace do
|
37
|
+
validate_stacktrace_content(:enable, [:continuation], :should_not)
|
38
|
+
validate_stacktrace_content(:disable, [:continuation], :should)
|
39
|
+
validate_stacktrace_content(:enable_filtering, $RUBY_FLOW_FILES, :should)
|
40
|
+
validate_stacktrace_content(:disable_filtering, $RUBY_FLOW_FILES, :should_not)
|
41
|
+
end
|
@@ -0,0 +1,118 @@
|
|
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 AsyncScope do
|
17
|
+
|
18
|
+
it "makes sure that basic AsyncScoping works" do
|
19
|
+
trace = []
|
20
|
+
this_scope = AsyncScope.new() do
|
21
|
+
task { trace << :inside_task}
|
22
|
+
end
|
23
|
+
trace.should == []
|
24
|
+
this_scope.eventLoop
|
25
|
+
trace.should == [:inside_task]
|
26
|
+
end
|
27
|
+
|
28
|
+
context "empty AsyncScope" do
|
29
|
+
let(:scope) { AsyncScope.new }
|
30
|
+
let(:trace) { [] }
|
31
|
+
|
32
|
+
it "adds tasks like a queue" do
|
33
|
+
scope << Task.new(scope.root_context) { trace << :task }
|
34
|
+
scope.eventLoop
|
35
|
+
trace.should == [:task]
|
36
|
+
end
|
37
|
+
|
38
|
+
it "adds a task if given one on construction" do
|
39
|
+
scope = AsyncScope.new { trace << :task }
|
40
|
+
scope.eventLoop
|
41
|
+
trace.should == [:task]
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
it "runs a task asynchronously" do
|
46
|
+
scope << Task.new(scope.root_context) { trace << :inner }
|
47
|
+
trace << :outer
|
48
|
+
scope.eventLoop
|
49
|
+
trace.should == [:outer, :inner]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "runs multiple tasks in order" do
|
53
|
+
scope << Task.new(scope.root_context) { trace << :first_task }
|
54
|
+
scope << Task.new(scope.root_context) { trace << :second_task }
|
55
|
+
scope.eventLoop
|
56
|
+
trace.should == [:first_task, :second_task]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "resumes tasks after a Future is ready" do
|
60
|
+
f = Future.new
|
61
|
+
scope = AsyncScope.new do
|
62
|
+
task do
|
63
|
+
trace << :first
|
64
|
+
f.get
|
65
|
+
trace << :fourth
|
66
|
+
end
|
67
|
+
|
68
|
+
task do
|
69
|
+
trace << :second
|
70
|
+
f.set(:foo)
|
71
|
+
trace << :third
|
72
|
+
end
|
73
|
+
end
|
74
|
+
scope.eventLoop
|
75
|
+
trace.should == [:first, :second, :third, :fourth]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "tests to make sure that scopes complete after an event loop" do
|
79
|
+
scope = AsyncScope.new do
|
80
|
+
error_handler do |t|
|
81
|
+
t.begin do
|
82
|
+
x = Future.new
|
83
|
+
y = Future.new
|
84
|
+
error_handler do |t|
|
85
|
+
t.begin {}
|
86
|
+
t.ensure { x.set }
|
87
|
+
end
|
88
|
+
x.get
|
89
|
+
error_handler do |t|
|
90
|
+
t.begin {}
|
91
|
+
t.ensure { y.set }
|
92
|
+
end
|
93
|
+
y.get
|
94
|
+
error_handler do |t|
|
95
|
+
t.begin {}
|
96
|
+
t.ensure {}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
completed = scope.eventLoop
|
103
|
+
completed.should == true
|
104
|
+
end
|
105
|
+
it "ensures you can cancel an async scope " do
|
106
|
+
condition = FiberConditionVariable.new
|
107
|
+
@task = nil
|
108
|
+
scope = AsyncScope.new do
|
109
|
+
task do
|
110
|
+
condition.wait
|
111
|
+
end
|
112
|
+
end
|
113
|
+
scope.eventLoop
|
114
|
+
scope.cancel(CancellationException.new)
|
115
|
+
expect { scope.eventLoop }.to raise_error CancellationException
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|