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 ADDED
@@ -0,0 +1,9 @@
1
+ source "http://www.rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "rspec", "1.3.0"
7
+ gem "factory_girl"
8
+ gem "rake"
9
+ end
data/LICENSE.TXT ADDED
@@ -0,0 +1,15 @@
1
+ # @markup markdown
2
+ # @title AWS Flow Framework for Ruby License
3
+
4
+ Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License"). You
7
+ may not use this file except in compliance with the License. A copy of
8
+ the License is located at:
9
+
10
+ * <http://aws.amazon.com/apache2.0/>
11
+
12
+ or in the "LICENSE" file accompanying this file. This file is
13
+ distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
14
+ ANY KIND, either express or implied. See the License for the specific
15
+ language governing permissions and limitations under the License.
data/NOTICE.TXT ADDED
@@ -0,0 +1,14 @@
1
+ AWS Flow for Ruby
2
+ Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+
4
+ This product includes software developed by
5
+ Amazon Technologies, Inc (http://www.amazon.com/).
6
+
7
+ **********************
8
+ THIRD PARTY COMPONENTS
9
+ **********************
10
+ This software includes third party software subject to the following copyrights:
11
+ - XML parsing and utility functions from JetS3t - Copyright 2006-2009 James Murty.
12
+ - JSON parsing and utility functions from JSON.org - Copyright 2002 JSON.org.
13
+
14
+ The licenses for these third party components are included in LICENSE.txt
data/Rakefile ADDED
@@ -0,0 +1,27 @@
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 'spec/rake/spectask'
17
+
18
+ Spec::Rake::SpecTask.new(:spec) do |t|
19
+ t.libs << 'lib'
20
+ t.spec_opts = ['--color', '--format nested']
21
+ t.spec_files = FileList['test/**/*.rb']
22
+ t.spec_files.delete_if {|x| x =~ /.*factories.rb/ || x =~ /.*spec_helper.rb/}
23
+ t.spec_files.unshift("test/aws/factories.rb")
24
+ t.spec_files.unshift("test/aws/spec_helper.rb")
25
+ end
26
+
27
+ task :test => :spec
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'aws-flow-core'
3
+ s.version = '1.0.0'
4
+ s.date = Time.now
5
+ s.summary = "AWS Flow Core"
6
+ s.description = "Library to provide all the base asynchronous constructs that aws-flow uses"
7
+ s.authors = "Michael Steger"
8
+ s.email = ""
9
+ s.files = `git ls-files`.split("\n")
10
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
11
+ s.require_paths << "lib/aws/"
12
+ end
data/lib/aws/flow.rb ADDED
@@ -0,0 +1,26 @@
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 require_all(path)
17
+ glob = File.join(File.dirname(__FILE__), path, "*.rb")
18
+ Dir[glob].each { |f| require f}
19
+ Dir[glob].map { |f| File.basename(f) }
20
+ end
21
+
22
+
23
+ # Everything depends on fiber, so we have to require that before anything else
24
+ require 'aws/flow/fiber'
25
+
26
+ $RUBY_FLOW_FILES = require_all 'flow/'
@@ -0,0 +1,134 @@
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 AsyncBacktrace, which takes care of decorating and properly filtering backtraces
17
+
18
+ module AWS
19
+ module Flow
20
+ module Core
21
+ # @!visibility private
22
+ class AsyncBacktrace
23
+
24
+ # @!visibility private
25
+ def initialize(parent, backtrace)
26
+ @backtrace = AsyncBacktrace.filter(backtrace)
27
+ @parent = parent
28
+ end
29
+
30
+ # @!visibility private
31
+ def backtrace
32
+ if @parent
33
+ AsyncBacktrace.merge(@backtrace, @parent.backtrace)
34
+ else
35
+ @backtrace
36
+ end
37
+ end
38
+
39
+ # @!visibility private
40
+ class << self
41
+
42
+ # @!visibility private
43
+ def create(parent, frames_to_skip)
44
+
45
+ unless @disable_async_backtrace
46
+ b = AsyncBacktrace.caller(frames_to_skip)
47
+ AsyncBacktrace.new(parent, b)
48
+ end
49
+ end
50
+
51
+ # @!visibility private
52
+ def create_from_exception(parent, exception)
53
+ unless @disable_async_backtrace
54
+ AsyncBacktrace.new(parent, exception.backtrace);
55
+ end
56
+ end
57
+
58
+ # Remove all framework related frames after application frames. Keep framework frames before application
59
+ # frames.
60
+ #
61
+ # @todo
62
+ # The correct implementation should not have framework frames before application frames as it is expected to
63
+ # call Kernel.caller with the correct number. But in cases when due to changes this number is not correct
64
+ # the frames are kept to not create confusion.
65
+ #
66
+ def filter(backtrace)
67
+ if @disable_filtering
68
+ backtrace
69
+ else
70
+ do_filter(backtrace)
71
+ end
72
+ end
73
+
74
+ # @!visibility private
75
+ def merge(*backtraces)
76
+ result = []
77
+ backtraces.each do | b |
78
+ if b
79
+ result << "------ continuation ------" if result.size > 0
80
+ result += b
81
+ end
82
+ end
83
+ result
84
+ end
85
+
86
+ # @!visibility private
87
+ def disable_filtering
88
+ @disable_filtering = true
89
+ end
90
+
91
+ # @!visibility private
92
+ def enable_filtering
93
+ @disable_filtering = false
94
+ end
95
+
96
+ # @!visibility private
97
+ def disable
98
+ @disable_async_backtrace = true
99
+ end
100
+
101
+ # @!visibility private
102
+ def enable
103
+ @disable_async_backtrace = false
104
+ end
105
+
106
+ # @!visibility private
107
+ def caller(skip)
108
+ random_var = Kernel.caller 0
109
+ this_stuff = 1.upto(6).map { |x| Kernel.caller(x) }
110
+ other_var = Kernel.caller skip
111
+ Kernel.caller(@disable_filtering ? 0 : skip)
112
+ end
113
+
114
+ private
115
+
116
+ # @!visibility private
117
+ def do_filter(backtrace)
118
+ return nil unless backtrace
119
+ # keep asynchrony frames at the top of the backtrace only
120
+ # then cut all frames starting from asynchrony frame
121
+ skip_asynchrony_frames = false
122
+ @backtrace = backtrace.take_while do |frame|
123
+ if ! $RUBY_FLOW_FILES.select {|file| Regexp.new(file) =~ frame}.empty?
124
+ !skip_asynchrony_frames
125
+ else
126
+ skip_asynchrony_frames = true
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,195 @@
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 module contains the Root of the heirarchy for calls into flow, the AsyncScope
17
+
18
+ module AWS
19
+ module Flow
20
+ module Core
21
+
22
+ def gate_by_version(version, method, &block)
23
+ if RUBY_VERSION.send(method, version)
24
+ block.call
25
+ end
26
+ end
27
+
28
+ # @!visibility private
29
+ class AsyncScope
30
+ attr_accessor :stackTrace, :root, :failure, :root_context
31
+
32
+ def is_complete?
33
+ @root_context.complete
34
+ end
35
+
36
+ def get_closest_containing_scope
37
+ @root_error_handler
38
+ end
39
+
40
+ def cancel(error); @root_error_handler.cancel(error); end
41
+
42
+ def initialize(&block)
43
+ @root_context = RootAsyncScope.new
44
+
45
+
46
+
47
+ # 1 for the function that skips frames
48
+ # 1 for the create function
49
+ # 1 for the the initialize of the backtrace
50
+
51
+ # "./lib/aws/rubyflow/asyncBacktrace.rb:75:in `caller'"
52
+ # "./lib/aws/rubyflow/asyncBacktrace.rb:21:in `create'"
53
+ # "./lib/aws/rubyflow/asyncScope.rb:18:in `initialize'"
54
+
55
+ @root_context.backtrace = AsyncBacktrace.create(nil, 3)
56
+ @root_error_handler = BeginRescueEnsure.new(:parent => @root_context)
57
+ begin
58
+ @root_error_handler.begin lambda { block.call if ! block.nil? }
59
+ @root_error_handler.rescue(Exception, lambda { |e| raise e })
60
+ end
61
+ @root_context << @root_error_handler
62
+ end
63
+
64
+ # Collects all the heirs of a task for use in async_stack_dump
65
+ def get_heirs
66
+ @root_error_handler.get_heirs
67
+ end
68
+
69
+ # Execute all queued tasks. If execution of those tasks results in addition of new tasks to the queue, execute
70
+ # them as well.
71
+ #
72
+ # Unless there are external dependencies or bugs in the tasks to be executed, a single call to this method
73
+ # performs the complete asynchronous execution.
74
+ #
75
+ # @note In the presence of external dependencies, it is expected that {AsyncScope#eventLoop} is called every
76
+ # time after a change in the state in a dependency can unblock asynchronous execution.
77
+ #
78
+ def eventLoop
79
+ #TODO Figure out when to raise Done raise "Done" if ! @root_task.alive?
80
+ raise IllegalStateException, "Already complete" if is_complete?
81
+ @root_context.eventLoop
82
+ # TODO Does this need to be taken care of? It's supposed to protect
83
+ # against people having errors that are classes, so like, passing
84
+ # Exception into cancel. We might want to just catch that at the
85
+ # entry point
86
+ if @root_context.failure
87
+ if @root_context.failure.respond_to? :message
88
+ failure_message = @root_context.failure.message + "\n" +
89
+ @root_context.failure.backtrace.join("\n")
90
+ raise @root_context.failure, failure_message
91
+ else
92
+ raise @root_context.failure
93
+ end
94
+ end
95
+
96
+ return is_complete?
97
+ end
98
+
99
+ def <<(task)
100
+ @root_context << task
101
+ task.parent = @root_context
102
+ end
103
+ end
104
+
105
+ # @!visibility private
106
+ class RootAsyncScope < FlowFiber
107
+
108
+ attr_accessor :backtrace, :failure, :executor, :complete
109
+
110
+ def initialize(options = {}, &block)
111
+ @parent = options[:parent_context]
112
+ @daemon = options[:daemon]
113
+ @context = @parent
114
+ @executor = AsyncEventLoop.new
115
+ @task_queue = []
116
+ @complete = false
117
+ @task_queue << Task.new(context, &block) if block
118
+ end
119
+
120
+ # The only thing that should be removed from the RootAsyncScope is the
121
+ # root BeginRescueEnsure, so upon removal we are complete
122
+ def remove(task)
123
+ @complete = true
124
+ end
125
+
126
+ # As with remove, the only thing that is under RootAsyncScope should be
127
+ # the root BeginRescueEnsure, so upon failure we will be complete. Also
128
+ # sets failure variable for later raising.
129
+ def fail(task, error)
130
+ @failure = error
131
+ @complete = true
132
+ end
133
+
134
+ def <<(this_task)
135
+ @executor << this_task
136
+ end
137
+
138
+ # Reture self, a RootAsyncScope is the closest containing scope
139
+ def get_closest_containing_scope
140
+ self
141
+ end
142
+
143
+ # Call out to the AsyncEventLoop
144
+ def eventLoop
145
+ @executor.executeQueuedTasks
146
+ end
147
+
148
+
149
+ private
150
+ DELEGATED_METHODS = [:push, :<<, :enq, :empty?, :length, :size, :delete, :shift]
151
+
152
+ def method_missing(method_name, *args)
153
+ if DELEGATED_METHODS.include? method_name
154
+ @executor.send(method_name, *args)
155
+ else
156
+ super
157
+ end
158
+ end
159
+ end
160
+
161
+ # @!visibility private
162
+ class AsyncEventLoop
163
+
164
+ def initialize
165
+ @tasks = []
166
+ end
167
+
168
+ def remove(task)
169
+ @tasks.delete(task)
170
+ end
171
+ # TODO Make sure that it's okay to fail from the AsyncEventLoop, and that
172
+ # this is the correct behavior
173
+ def fail(task, error)
174
+ raise error
175
+ end
176
+ def <<(task)
177
+ @tasks << task
178
+
179
+ end
180
+
181
+
182
+ # TODO should this be synchronized somehow?
183
+
184
+ # Actually executes the eventLoop
185
+ def executeQueuedTasks
186
+ until @tasks.empty?
187
+ task = @tasks.shift
188
+ task.resume if task.alive?
189
+ end
190
+ end
191
+ end
192
+
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,386 @@
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 'aws/flow/simple_dfa'
17
+ require 'set'
18
+
19
+ module AWS
20
+ module Flow
21
+ module Core
22
+
23
+ # This class allows asynchronous error handling within the AWS Flow Framework for Ruby. Calling
24
+ # {#begin}/{#rescue}/{#ensure} is similar to Ruby's native `begin`/`rescue`/`end` semantics.
25
+ class BeginRescueEnsure < FlowFiber
26
+
27
+ extend SimpleDFA
28
+ attr_accessor :parent, :begin_task, :ensure_task, :rescue_tasks,
29
+ :rescue_exceptions, :failure, :cancelled, :heirs, :nonDaemonHeirsCount, :executor, :result
30
+ attr_reader :backtrace, :__context__
31
+
32
+ # Create a new BeginRescueEnsure object, with the provided options.
33
+ #
34
+ # @param options
35
+ # Options to set for the class.
36
+ #
37
+ # @option options [Object] :parent
38
+ # The parent object.
39
+ #
40
+ def initialize(options = {})
41
+ # We have two different arrays, rather than a hash,
42
+ # because we want to ensure that we process the rescues in the order
43
+ # they are written, and because prior to Ruby 1.9, hashes will not
44
+ # return their elements in the order they were inserted.
45
+ @rescue_exceptions = []
46
+ @rescue_tasks = []
47
+ @parent = options[:parent] || Fiber.current.__context__
48
+ @current = @parent
49
+ @executor = @parent.executor
50
+ @__context__ = self
51
+ @nonDaemonHeirsCount = 0
52
+ @current_state ||= self.class.get_start_state
53
+ @heirs = Set.new
54
+ @backtrace = make_backtrace(@parent.backtrace)
55
+ @result = Future.new
56
+ super() { consume(:run) }
57
+ end
58
+
59
+
60
+ # @!visibility private
61
+ def is_daemon?
62
+ false
63
+ end
64
+
65
+
66
+ # @!visibility private
67
+ def <<(async_task)
68
+ # Not going to include the promise to wait for, as it would appear that
69
+ # Fibers can wait on futures from their point of origin as part of their
70
+ # implementation, as opposed to adding the callback here.
71
+ check_closed
72
+ if ! @heirs.member? async_task
73
+ @heirs << async_task
74
+ if ! async_task.is_daemon?
75
+ @nonDaemonHeirsCount += 1
76
+ end
77
+ end
78
+ @executor << async_task
79
+ self
80
+ end
81
+
82
+ # @!visibility private
83
+ def get_closest_containing_scope
84
+ # BRE's are special in that they act as a containing scope, so that things
85
+ # created in BRE's treat it as the parent, so that it can track the heirs
86
+ # correctly and close only when nonDaemonHeirsCount is 0
87
+ self
88
+ end
89
+
90
+ # @!visibility private
91
+ def check_closed
92
+ raise IllegalStateException, @failure if @current_state == :closed
93
+ end
94
+
95
+ # Fails the task, cancels all of its heirs, and then updates the state.
96
+ #
97
+ # @param this_task
98
+ # The task to fail.
99
+ #
100
+ # @param error
101
+ # The error associated with the failure.
102
+ #
103
+ def fail(this_task, error)
104
+ check_closed
105
+ if ( ! (error.class <= CancellationException) || @failure == nil && !@daemondCausedCancellation)
106
+ backtrace = AsyncBacktrace.create_from_exception(@backtrace, error)
107
+ error.set_backtrace(backtrace.backtrace) if backtrace
108
+ @failure = error
109
+ end
110
+ task_out = @heirs.delete?(this_task)
111
+ raise "There was a task attempted to be removed from a BRE, when the BRE did not have that task as an heir" unless task_out
112
+ @nonDaemonHeirsCount -= 1 if ! this_task.is_daemon?
113
+ cancelHeirs
114
+ update_state
115
+ end
116
+
117
+ # Removes the task and updates the state
118
+ #
119
+ # @param this_task
120
+ # The task to remove.
121
+ #
122
+ def remove(this_task)
123
+ check_closed
124
+
125
+ task_out = @heirs.delete?(this_task)
126
+ raise "There was a task attempted to be removed from a BRE, when the BRE did not have that task as an heir" unless task_out
127
+ @nonDaemonHeirsCount -= 1 if ! this_task.is_daemon?
128
+ update_state
129
+ end
130
+
131
+ # @!visibility private
132
+ def cancelHeirs
133
+ toCancel = @heirs.dup
134
+ toCancel.each { |heir| heir.cancel(@failure) }
135
+ end
136
+
137
+ # @!visibility private
138
+ def merge_stacktraces(failure, this_backtrace, error)
139
+ backtrace = AsyncBacktrace.create_from_exception(this_backtrace, error)
140
+ failure.set_backtrace(backtrace.backtrace) if backtrace
141
+ end
142
+
143
+ # @!visibility private
144
+ def cancel(error)
145
+ if @current_state == :created
146
+ @current_state = :closed
147
+ @parent.remove(self)
148
+ return
149
+ end
150
+ if @failure == nil
151
+ @cancelled = true
152
+ details = (error.respond_to? :details) ? error.details : nil
153
+ reason = (error.respond_to? :reason) ? error.reason : nil
154
+ @failure = CancellationException.new(reason, details)
155
+ @failure.set_backtrace(@backtrace.backtrace) if @backtrace
156
+ if @current_state == :begin
157
+ cancelHeirs
158
+ end
159
+ end
160
+ end
161
+
162
+ # Actually runs the BRE, by going through the DFA with the symbol :run.
163
+ # @!visibility private
164
+ def run
165
+ this_failure = @failure
166
+ begin
167
+ consume(:run)
168
+ rescue Exception => error
169
+ if this_failure != error
170
+ backtrace = AsyncBacktrace.create_from_exception(@backtrace, error)
171
+ error.set_backtrace(backtrace.backtrace) if backtrace
172
+ end
173
+ @failure = error
174
+ cancelHeirs
175
+ ensure
176
+ update_state
177
+ raise @failure if (!!@failure && @current_state == :closed)
178
+ end
179
+ end
180
+
181
+ # @!visibility private
182
+ def alive?
183
+ @current_state != :closed
184
+ end
185
+
186
+ # Updates the state based on the most recent transitions in the DFA
187
+ # @!visibility private
188
+ def update_state
189
+ #TODO ? Add the ! @executed part
190
+ #return if @current_state == :closed || ! @executed
191
+ return if @current_state == :closed
192
+ if @nonDaemonHeirsCount == 0
193
+ if @heirs.empty?
194
+ consume(:update_state)
195
+ else
196
+ @daemondCausedCancellation = true if @failure == nil
197
+ cancelHeirs
198
+ end
199
+ end
200
+ end
201
+
202
+ # @!visibility private
203
+ def get_heirs
204
+ # TODO fix this so it returns string instead of printing to stdout
205
+ str = "I am a BeginRescueEnsure with #{heirs.length} heirs
206
+ my begin block looks like #{@begin_task}" +
207
+ @heirs.map(&:get_heirs).to_s
208
+
209
+ # (@heirs.each(&:get_heirs) + [self]).flatten
210
+ end
211
+
212
+ # @!visibility private
213
+ init(:created)
214
+ {
215
+ [:created, :run] => lambda { |bre| bre.current_state = :begin; bre.run },
216
+ [:begin, :run] => lambda { |bre| bre << bre.begin_task },
217
+ [:begin, :update_state] => lambda do |bre|
218
+ if bre.failure == nil
219
+ bre.current_state = :ensure
220
+ else
221
+ bre.current_state = :rescue;
222
+ end
223
+ bre.run
224
+ end,
225
+ [:rescue, :run] => lambda do |bre|
226
+ # Emulates the behavior of the actual Ruby rescue, see
227
+ # http://Ruby-doc.org/docs/ProgrammingRuby/html/tut_exceptions.html
228
+ # for more details
229
+ bre.rescue_exceptions.each_index do |index|
230
+ this_failure = bre.failure
231
+ failure_class = bre.failure.is_a?(Exception) ? bre.failure.class : bre.failure
232
+ if failure_class <= bre.rescue_exceptions[index]
233
+ bre.result.unset
234
+ bre.failure = nil
235
+ task = bre.rescue_tasks[index]
236
+ bre << Task.new(bre) { bre.result.set(task.call(this_failure)) }
237
+ # bre.rescue_tasks[index].call(this_failure)
238
+ break
239
+ end
240
+ end
241
+ end,
242
+ [:rescue, :update_state] => lambda { |bre| bre.current_state = :ensure; bre.run},
243
+ [:ensure, :run] => lambda do |bre|
244
+ bre << bre.ensure_task if bre.ensure_task
245
+ end,
246
+ [:ensure, :update_state] => lambda do |bre|
247
+ bre.current_state = :closed
248
+ if bre.failure == nil
249
+ bre.parent.remove(bre)
250
+ else
251
+ bre.parent.fail(bre, bre.failure)
252
+ end
253
+ end,
254
+ }.each_pair do |key, func|
255
+ add_transition(key.first, key.last) { |t| func.call(t) }
256
+ end
257
+ # That is, any transition from closed leads back to itself
258
+ define_general(:closed) { |t| t.current_state = :closed }
259
+
260
+ # Binds the block to the a lambda to be called when we get to the begin part of the DFA
261
+ #
262
+ # @param block
263
+ # The code block to be called when asynchronous *begin* starts.
264
+ #
265
+ def begin(block)
266
+ raise "Duplicated begin" if @begin_task
267
+ # @begin_task = lambda { block.call }
268
+ @begin_task = Task.new(self) { @result.set(block.call) }
269
+ end
270
+
271
+ # Binds the block to the a lambda to be called when we get to the rescue part of the DFA
272
+ #
273
+ # @param error_type
274
+ # The error type.
275
+ #
276
+ # @param block
277
+ # The code block to be called when asynchronous *rescue* starts.
278
+ #
279
+ def rescue(error_type, block)
280
+ this_task = lambda { |failure| block.call(failure) }
281
+ if @rescue_exceptions.include? error_type
282
+ raise "You have already registered #{error_type}!"
283
+ end
284
+ @rescue_exceptions << error_type
285
+ @rescue_tasks << this_task
286
+ end
287
+
288
+ # Binds the block to the a lambda to be called when we get to the ensure part of the DFA
289
+ #
290
+ # @param block
291
+ # The code block to be called when asynchronous *ensure* starts.
292
+ #
293
+ def ensure(block)
294
+ raise "Duplicated ensure" if @ensure_task
295
+ @ensure_task = Task.new(self) { block.call }
296
+ end
297
+
298
+ def schedule
299
+ @parent << self
300
+ end
301
+ end
302
+
303
+ # Class to ensure that all the inner guts of BRE aren't exposed. This function is passed in when error_handler is
304
+ # called, like this:
305
+ #
306
+ # error_handler do |t|
307
+ # t.begin { "This is the begin" }
308
+ # t.rescue(Exception) { "This is the rescue" }
309
+ # t.ensure { trace << t.begin_task }
310
+ # end
311
+ #
312
+ # The *t* that is passed in is actually a {BeginRescueEnsureWrapper}, which will only pass begin/rescue/ensure
313
+ # onto the {BeginRescueEnsure} class itself.
314
+ #
315
+ class BeginRescueEnsureWrapper < FlowFiber
316
+ # Also has a few methods to ensure Fiber-ness, such as get_heirs and cancel.
317
+ attr_reader :__context__
318
+
319
+ # Creates a new BeginRescueEnsureWrapper instance.
320
+ #
321
+ # @param block
322
+ # A code block to be called.
323
+ #
324
+ # @param begin_rescue_ensure
325
+ # The {BeginRescueEnsure} instance to wrap.
326
+ #
327
+ def initialize(block, begin_rescue_ensure)
328
+ @beginRescueEnsure = begin_rescue_ensure
329
+ @__context__ = @beginRescueEnsure
330
+ super() do
331
+ begin
332
+ block.call(self)
333
+ ensure
334
+ @__context__.parent.remove(self)
335
+ end
336
+
337
+ end
338
+ end
339
+
340
+ # @!visibility private
341
+ def get_heirs
342
+ p "I am a BREWrapper"
343
+ return
344
+ end
345
+
346
+ def cancel(error_type)
347
+ @beginRescueEnsure.parent.cancel(self)
348
+ end
349
+
350
+ # @!visibility private
351
+ #
352
+ # @return [false]
353
+ # Always returns `false`.
354
+ #
355
+ def is_daemon?
356
+ false
357
+ end
358
+
359
+ # Gets the parent of the {BeginRescueEnsure} instance held by this class.
360
+ def get_closest_containing_scope
361
+ @beginRescueEnsure.parent
362
+ end
363
+
364
+ # (see BeginRescueEnsure#begin)
365
+ def begin(&block) @beginRescueEnsure.begin(block) end
366
+
367
+ # (see BeginRescueEnsure#ensure)
368
+ def ensure(&block) @beginRescueEnsure.ensure(block) end
369
+
370
+ # (see BeginRescueEnsure#rescue)
371
+ def rescue(error_type, &block)
372
+ @beginRescueEnsure.rescue(error_type, block)
373
+ end
374
+
375
+ private
376
+ attr_accessor :beginRescueEnsure
377
+ end
378
+
379
+ class DaemonBeginRescueEnsure < BeginRescueEnsure
380
+ def is_daemon?
381
+ true
382
+ end
383
+ end
384
+ end
385
+ end
386
+ end