aws-flow-core 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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