aws-flow 1.0.5 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,50 @@
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
+ # This file simply contains definitions and functions useful to the overall running of flow
17
+ module AWS
18
+ module Flow
19
+ module Core
20
+ class IllegalStateException < Exception; end
21
+ class CancellationException < Exception
22
+ attr_accessor :reason, :details
23
+ def initialize(reason = nil, details = nil)
24
+ @reason = reason
25
+ @details = details
26
+ end
27
+ end
28
+
29
+ def make_backtrace(parent_backtrace)
30
+ # 1 frame for the function that actually removes the stack traces
31
+ # 1 frame for the function that calls into the function that removes
32
+ # frames in AsyncBacktrace
33
+ # 1 frame for the call into this function
34
+ # 1 frame for the initialize call of the BRE or External Task
35
+ # 1 frame for the new call into the BRE or ET
36
+ # 1 frame for the AsyncScope initialize that the BRE/ET has to be in
37
+
38
+ # "./lib/aws/rubyflow/asyncBacktrace.rb:75:in `caller'"
39
+ # "./lib/aws/rubyflow/asyncBacktrace.rb:21:in `create'"
40
+ # "./lib/aws/rubyflow/flow.rb:16:in `make_backtrace'"
41
+ # "./lib/aws/rubyflow/flow.rb:103:in `initialize'"
42
+ # "./lib/aws/rubyflow/asyncScope.rb:17:in `new'"
43
+ # "./lib/aws/rubyflow/asyncScope.rb:17:in `initialize'"
44
+
45
+ frames_to_skip = 7
46
+ backtrace = AsyncBacktrace.create(parent_backtrace, frames_to_skip)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,115 @@
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
+ # This file contains our Future implementation, which allows us to have asynchronous, blocking promises
17
+
18
+ module AWS
19
+ module Flow
20
+ module Core
21
+ class AlreadySetException < Exception; end
22
+
23
+ # A Future represents the result of an asynchronous computation. Methods are
24
+ # provided to check if the computation is complete(Future#set), to wait for
25
+ # its completion(Future#wait), and to retrieve the result of the
26
+ # computation(Future#get). The result can only be retrieved using method get
27
+ # when the computation has completed, blocking if necessary until it is
28
+ # ready. This is okay, however, because it will block that Fiber, and
29
+ # another Fiber will start executing
30
+ class Future
31
+
32
+ # Sets the value of the future, and notifies all of the Fibers that tried
33
+ # to get when this future wasn't ready.
34
+ def set(result=nil)
35
+ raise AlreadySetException if @set
36
+ @set = true
37
+ @result = result
38
+ @conditional.broadcast if @conditional
39
+ @listeners.each { |b| b.call(self) } if @listeners
40
+ self
41
+ end
42
+
43
+ # determines whether the object is a flow future. The contract is that
44
+ # flow futures must have a #get method.
45
+ def is_flow_future?
46
+ true
47
+ end
48
+
49
+ # Blocks if Future is not set
50
+ # raises CancellationError when task is cancelled
51
+ def get
52
+ until @set
53
+ @conditional ||= FiberConditionVariable.new
54
+ @conditional.wait
55
+ end
56
+ @result
57
+ end
58
+
59
+ def set?
60
+ @set
61
+ end
62
+
63
+ def unset
64
+ @set = nil
65
+ @result = nil
66
+ end
67
+
68
+ # Add a callback, block, which will fire when the future is set
69
+ def on_set(&block)
70
+ @listeners ||= []
71
+ # TODO probably want to use lambda here
72
+ @listeners << block
73
+ end
74
+ end
75
+
76
+ # Based on the ruby core source:
77
+ # https://github.com/ruby/ruby/blob/trunk/lib/thread.rb
78
+ class FiberConditionVariable
79
+ #
80
+ # Creates a new ConditionVariable
81
+ #
82
+ def initialize
83
+ @waiters = []
84
+ end
85
+
86
+
87
+ # Have the current fiber wait on this condition variable, and wake up when
88
+ # the FiberConditionVariable is signalled/broadcaster
89
+ def wait
90
+ fiber = ::Fiber.current
91
+ @waiters << fiber
92
+ Fiber.yield
93
+ self
94
+ end
95
+
96
+ #
97
+ # Wakes up the first fiber in line waiting for this lock.
98
+ #
99
+ def signal
100
+ t = @waiters.shift
101
+ t.schedule if t && t.alive?
102
+ self
103
+ end
104
+
105
+ #
106
+ # Wakes up all fibers waiting for this lock.
107
+ #
108
+ def broadcast
109
+ signal until @waiters.empty?
110
+ self
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,151 @@
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
+ # This file contains the externally visible parts of flow that are expected to be used by customers of flow
17
+ module AWS
18
+ module Flow
19
+ module Core
20
+ class NoContextException < Exception; end
21
+
22
+ # @param block
23
+ # The block of code to be executed when the task is run.
24
+ #
25
+ # @raise [NoContextException]
26
+ # If the current fiber does not respond to {#__context__}.
27
+ #
28
+ # @return [Future]
29
+ # The tasks result, which is a {Future}.
30
+ #
31
+ def task(future = nil, &block)
32
+ fiber = ::Fiber.current
33
+ raise NoContextException unless fiber.respond_to? :__context__
34
+ context = fiber.__context__
35
+ t = Task.new(nil, &block)
36
+ task_context = TaskContext.new(:parent => context.get_closest_containing_scope, :task => t)
37
+ context << t
38
+ t.result
39
+ end
40
+
41
+ #
42
+ # @param block
43
+ # The block of code to be executed when the daemon task is run.
44
+ #
45
+ # @return [Future]
46
+ # The tasks result, which is a {Future}.
47
+ #
48
+ # @raise [NoContextException]
49
+ # If the current fiber does not respond to {#__context__}.
50
+ #
51
+ def daemon_task(&block)
52
+ fiber = ::Fiber.current
53
+ raise NoContextException unless fiber.respond_to? :__context__
54
+ context = fiber.__context__
55
+ t = DaemonTask.new(nil, &block)
56
+ task_context = TaskContext.new(:parent => context.get_closest_containing_scope, :task => t)
57
+ context << t
58
+ t.result
59
+ end
60
+
61
+ #
62
+ # @param block
63
+ # The block of code to be executed when the external task is run.
64
+ #
65
+ # @return [nil]
66
+ #
67
+ # @raise [NoContextException]
68
+ # If the current fiber does not respond to {#__context__}.
69
+ #
70
+ def external_task(&block)
71
+ fiber = ::Fiber.current
72
+ raise NoContextException unless fiber.respond_to? :__context__
73
+ context = fiber.__context__
74
+ t = ExternalTask.new(:parent => context.get_closest_containing_scope, &block)
75
+ task_context = TaskContext.new(:parent => context.get_closest_containing_scope, :task => t)
76
+ context << t
77
+ nil
78
+ end
79
+
80
+
81
+ #
82
+ #
83
+ # * *Args* :
84
+ # - block -> a block, which is passed in a BeginRescueEnsureWrapper, and which will define the BeginRescueEnsure#begin, BeginRescueEnsure#rescue, and BeginRescueEnsure#ensure methods
85
+ # * *Returns* :
86
+ # - The result of the begin statement, if there is no error, otherwise the value of the return statement
87
+ # * *Raises* :
88
+ # - +NoContextException+ -> If the current fiber does not respond to #__context__
89
+ #
90
+ def error_handler(&block)
91
+ fiber = ::Fiber.current
92
+ raise NoContextException unless fiber.respond_to? :__context__
93
+ context = fiber.__context__
94
+ begin_rescue_ensure = BeginRescueEnsure.new(:parent => context.get_closest_containing_scope)
95
+ bge = BeginRescueEnsureWrapper.new(block, begin_rescue_ensure)
96
+ context << bge
97
+ context << begin_rescue_ensure
98
+ begin_rescue_ensure
99
+ end
100
+
101
+ # @param block
102
+ # A code block, which is passed within a {BeginRescueEnsureWrapper}, and which must define the
103
+ # {BeginRescueEnsure#begin}, {BeginRescueEnsure#rescue}, and {BeginRescueEnsure#ensure} methods.
104
+ # @!visibility private
105
+ def _error_handler(&block)
106
+ error_handler(&block).result
107
+ end
108
+
109
+
110
+ def wait_for_function(function, *futures)
111
+ conditional = FiberConditionVariable.new
112
+ futures.flatten!
113
+ return nil if futures.empty?
114
+ result = futures.select(&:set?)
115
+ return futures.find(&:set?)if function.call(result, futures)
116
+ futures.each do |f|
117
+ f.on_set do |set_one|
118
+ result << set_one
119
+ conditional.broadcast if function.call(result, futures)
120
+ end
121
+ end
122
+ conditional.wait
123
+ result
124
+ end
125
+
126
+ # Blocks until *any* of the arguments are set.
127
+ #
128
+ # @param [Array] futures
129
+ # A list of futures to wait for. The function will return when at least one of these is set.
130
+ #
131
+ # @return [Array]
132
+ # A list of the set futures, in the order of being set.
133
+ #
134
+ def wait_for_any(*futures)
135
+ wait_for_function(lambda {|result, future_list| result.length >= 1 }, futures)
136
+ end
137
+
138
+ # Blocks until *all* of the arguments are set.
139
+ #
140
+ # @param [Array<Future>] futures
141
+ # A list of futures to wait for. The function will return only when all of them are set.
142
+ #
143
+ # @return [Array]
144
+ # A list of the set futures, in the order of being set.
145
+ #
146
+ def wait_for_all(*futures)
147
+ wait_for_function(lambda {|result, future_list| result.size == future_list.size}, futures)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,85 @@
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
+ module AWS
17
+ module Flow
18
+ module Core
19
+
20
+ # Contains a Data Flow Analysis (DFA)-like framework, where transition functions can perform arbitrary computation
21
+ # before moving to the next state
22
+ module SimpleDFA
23
+ attr_accessor :transitions, :symbols, :states, :start_state
24
+
25
+ # Creates a new SimpleDFA instance.
26
+ #
27
+ # @param start_state
28
+ # The state with which to start the framework.
29
+ #
30
+ def init(start_state)
31
+ include InstanceMethods
32
+ @start_state = start_state
33
+ @symbols = []
34
+ @states = []
35
+ @transitions = {}
36
+ @states << start_state
37
+ end
38
+
39
+ # @return the start state
40
+ # The start state that was provided when this instance was created.
41
+ #
42
+ def get_start_state
43
+ @start_state
44
+ end
45
+
46
+ # @return [Array]
47
+ # The list of all transitions that were added with {#add_transition}.
48
+ #
49
+ def get_transitions
50
+ @transitions
51
+ end
52
+
53
+ def define_general(state, &block)
54
+ @symbols.each do |symbol|
55
+ if @transitions[[state, symbol]].nil?
56
+ @transitions[[state, symbol]] = block
57
+ end
58
+ end
59
+ end
60
+
61
+ def add_transition(state, symbol, &block)
62
+ @symbols << symbol unless @symbols.include? symbol
63
+ @states << state unless @states.include? state
64
+ @transitions[[state, symbol]] = block
65
+ end
66
+
67
+ def uncovered_transitions
68
+ @states.product(@symbols) - @transitions.keys
69
+ end
70
+
71
+ module InstanceMethods
72
+ attr_accessor :current_state
73
+
74
+ def consume(symbol)
75
+ @current_state ||= self.class.get_start_state
76
+ func_to_call = self.class.get_transitions[[@current_state, symbol]]
77
+ raise "This is not a legal transition" unless func_to_call
78
+ func_to_call.call(self)
79
+ end
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -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