aws-flow 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.
Files changed (62) hide show
  1. data/Gemfile +8 -0
  2. data/LICENSE.TXT +15 -0
  3. data/NOTICE.TXT +14 -0
  4. data/Rakefile +39 -0
  5. data/aws-flow-core/Gemfile +9 -0
  6. data/aws-flow-core/LICENSE.TXT +15 -0
  7. data/aws-flow-core/NOTICE.TXT +14 -0
  8. data/aws-flow-core/Rakefile +27 -0
  9. data/aws-flow-core/aws-flow-core.gemspec +12 -0
  10. data/aws-flow-core/lib/aws/flow.rb +26 -0
  11. data/aws-flow-core/lib/aws/flow/async_backtrace.rb +134 -0
  12. data/aws-flow-core/lib/aws/flow/async_scope.rb +195 -0
  13. data/aws-flow-core/lib/aws/flow/begin_rescue_ensure.rb +386 -0
  14. data/aws-flow-core/lib/aws/flow/fiber.rb +77 -0
  15. data/aws-flow-core/lib/aws/flow/flow_utils.rb +50 -0
  16. data/aws-flow-core/lib/aws/flow/future.rb +109 -0
  17. data/aws-flow-core/lib/aws/flow/implementation.rb +151 -0
  18. data/aws-flow-core/lib/aws/flow/simple_dfa.rb +85 -0
  19. data/aws-flow-core/lib/aws/flow/tasks.rb +405 -0
  20. data/aws-flow-core/test/aws/async_backtrace_spec.rb +41 -0
  21. data/aws-flow-core/test/aws/async_scope_spec.rb +118 -0
  22. data/aws-flow-core/test/aws/begin_rescue_ensure_spec.rb +665 -0
  23. data/aws-flow-core/test/aws/external_task_spec.rb +197 -0
  24. data/aws-flow-core/test/aws/factories.rb +52 -0
  25. data/aws-flow-core/test/aws/fiber_condition_variable_spec.rb +163 -0
  26. data/aws-flow-core/test/aws/fiber_spec.rb +78 -0
  27. data/aws-flow-core/test/aws/flow_spec.rb +255 -0
  28. data/aws-flow-core/test/aws/future_spec.rb +210 -0
  29. data/aws-flow-core/test/aws/rubyflow.rb +22 -0
  30. data/aws-flow-core/test/aws/simple_dfa_spec.rb +63 -0
  31. data/aws-flow-core/test/aws/spec_helper.rb +36 -0
  32. data/aws-flow.gemspec +13 -0
  33. data/lib/aws/decider.rb +67 -0
  34. data/lib/aws/decider/activity.rb +408 -0
  35. data/lib/aws/decider/activity_definition.rb +111 -0
  36. data/lib/aws/decider/async_decider.rb +673 -0
  37. data/lib/aws/decider/async_retrying_executor.rb +153 -0
  38. data/lib/aws/decider/data_converter.rb +40 -0
  39. data/lib/aws/decider/decider.rb +511 -0
  40. data/lib/aws/decider/decision_context.rb +60 -0
  41. data/lib/aws/decider/exceptions.rb +178 -0
  42. data/lib/aws/decider/executor.rb +149 -0
  43. data/lib/aws/decider/flow_defaults.rb +70 -0
  44. data/lib/aws/decider/generic_client.rb +178 -0
  45. data/lib/aws/decider/history_helper.rb +173 -0
  46. data/lib/aws/decider/implementation.rb +82 -0
  47. data/lib/aws/decider/options.rb +607 -0
  48. data/lib/aws/decider/state_machines.rb +373 -0
  49. data/lib/aws/decider/task_handler.rb +76 -0
  50. data/lib/aws/decider/task_poller.rb +207 -0
  51. data/lib/aws/decider/utilities.rb +187 -0
  52. data/lib/aws/decider/worker.rb +324 -0
  53. data/lib/aws/decider/workflow_client.rb +374 -0
  54. data/lib/aws/decider/workflow_clock.rb +104 -0
  55. data/lib/aws/decider/workflow_definition.rb +101 -0
  56. data/lib/aws/decider/workflow_definition_factory.rb +53 -0
  57. data/lib/aws/decider/workflow_enabled.rb +26 -0
  58. data/test/aws/decider_spec.rb +1299 -0
  59. data/test/aws/factories.rb +45 -0
  60. data/test/aws/integration_spec.rb +3108 -0
  61. data/test/aws/spec_helper.rb +23 -0
  62. metadata +138 -0
@@ -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,109 @@
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
+ # Blocks if Future is not set
44
+ # raises CancellationError when task is cancelled
45
+ def get
46
+ until @set
47
+ @conditional ||= FiberConditionVariable.new
48
+ @conditional.wait
49
+ end
50
+ @result
51
+ end
52
+
53
+ def set?
54
+ @set
55
+ end
56
+
57
+ def unset
58
+ @set = nil
59
+ @result = nil
60
+ end
61
+
62
+ # Add a callback, block, which will fire when the future is set
63
+ def on_set(&block)
64
+ @listeners ||= []
65
+ # TODO probably want to use lambda here
66
+ @listeners << block
67
+ end
68
+ end
69
+
70
+ # Based on the ruby core source:
71
+ # https://github.com/ruby/ruby/blob/trunk/lib/thread.rb
72
+ class FiberConditionVariable
73
+ #
74
+ # Creates a new ConditionVariable
75
+ #
76
+ def initialize
77
+ @waiters = []
78
+ end
79
+
80
+
81
+ # Have the current fiber wait on this condition variable, and wake up when
82
+ # the FiberConditionVariable is signalled/broadcaster
83
+ def wait
84
+ fiber = ::Fiber.current
85
+ @waiters << fiber
86
+ Fiber.yield
87
+ self
88
+ end
89
+
90
+ #
91
+ # Wakes up the first fiber in line waiting for this lock.
92
+ #
93
+ def signal
94
+ t = @waiters.shift
95
+ t.schedule if t && t.alive?
96
+ self
97
+ end
98
+
99
+ #
100
+ # Wakes up all fibers waiting for this lock.
101
+ #
102
+ def broadcast
103
+ signal until @waiters.empty?
104
+ self
105
+ end
106
+ end
107
+ end
108
+ end
109
+ 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