aws-flow 2.4.0 → 3.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 (41) hide show
  1. checksums.yaml +8 -8
  2. data/aws-flow.gemspec +1 -0
  3. data/lib/aws/decider.rb +0 -1
  4. data/lib/aws/decider/starter.rb +6 -8
  5. data/lib/aws/decider/utilities.rb +6 -0
  6. data/lib/aws/decider/version.rb +1 -1
  7. data/lib/aws/decider/worker.rb +6 -0
  8. data/lib/aws/flow/future.rb +86 -6
  9. data/lib/aws/flow/implementation.rb +84 -13
  10. data/lib/aws/runner.rb +1 -1
  11. data/lib/aws/templates.rb +2 -0
  12. data/lib/aws/templates/activity.rb +41 -0
  13. data/lib/aws/templates/default.rb +11 -8
  14. data/lib/aws/templates/result.rb +183 -0
  15. data/lib/aws/templates/starter.rb +152 -226
  16. data/lib/aws/templates/utilities.rb +59 -0
  17. data/spec/aws/decider/integration/activity_spec.rb +1 -0
  18. data/spec/aws/decider/integration/options_spec.rb +16 -9
  19. data/spec/aws/decider/integration/starter_spec.rb +6 -7
  20. data/spec/aws/decider/unit/starter_spec.rb +2 -2
  21. data/spec/aws/decider/unit/worker_spec.rb +42 -0
  22. data/spec/aws/flow/{async_backtrace_spec.rb → unit/async_backtrace_spec.rb} +0 -0
  23. data/spec/aws/flow/{async_scope_spec.rb → unit/async_scope_spec.rb} +0 -0
  24. data/spec/aws/flow/{begin_rescue_ensure_spec.rb → unit/begin_rescue_ensure_spec.rb} +0 -0
  25. data/spec/aws/flow/unit/external_condition_variable_spec.rb +59 -0
  26. data/spec/aws/flow/{external_task_spec.rb → unit/external_task_spec.rb} +0 -0
  27. data/spec/aws/flow/{factories.rb → unit/factories.rb} +0 -0
  28. data/spec/aws/flow/{fiber_condition_variable_spec.rb → unit/fiber_condition_variable_spec.rb} +0 -0
  29. data/spec/aws/flow/{fiber_spec.rb → unit/fiber_spec.rb} +0 -0
  30. data/spec/aws/flow/{flow_spec.rb → unit/flow_spec.rb} +0 -0
  31. data/spec/aws/flow/{future_spec.rb → unit/future_spec.rb} +188 -0
  32. data/spec/aws/flow/{simple_dfa_spec.rb → unit/simple_dfa_spec.rb} +0 -0
  33. data/spec/aws/runner/integration/runner_integration_spec.rb +1 -0
  34. data/spec/aws/runner/unit/runner_unit_spec.rb +3 -3
  35. data/spec/aws/templates/unit/activity_spec.rb +9 -10
  36. data/spec/aws/templates/unit/base_spec.rb +10 -11
  37. data/spec/aws/templates/unit/default_spec.rb +23 -6
  38. data/spec/aws/templates/unit/result_spec.rb +130 -0
  39. data/spec/aws/templates/unit/starter_spec.rb +32 -105
  40. data/spec/aws/templates/unit/utilities_spec.rb +80 -0
  41. metadata +19 -13
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MDQyOWZlMGM0YjZiZDcwMmQyNjhjNGFhODY3NzE1ZjZhZDhiNmE4YQ==
4
+ MmMyMmUwNjY1YWUzZWExMDUxNDc2MGM3YTRmYTQzYmEzZWExN2YwMw==
5
5
  data.tar.gz: !binary |-
6
- ZTUxM2MwZTJlNzMzMmMxOWY5NTM0MGM2Y2UyMjA2MjE5NWQxMmJkOA==
6
+ ZWJhNjZjMDgzNTE0NDgwYzI1NjQ4ZDVmNjA2MjM5YzYzODI2MzA5ZQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MTJiNjFiODFlY2IyN2Q5ODc2YTViNjY4MTM0NGIwZTc0MzE1Y2FmYzM5NjUw
10
- MDM5YzkyZTFkNDYzOWFmMDA4YjVmNmY5ODM3MGVkMzQwYjMyMmNiNTlmNmU0
11
- NzYyYWNjNmUwYTJkNDg4OTU4ZDVkMjE5MWUxZjA0MDFkZmZiZTM=
9
+ MTg4NTYwYmU3YTI1NzAxYzZhZWUyMWZlZGVlNGVmNWE3MGQ2NDQzYzJlYWM3
10
+ ZmM2MjE0NGNhODhkOWNhYjRkMDM4OGNkMzU5YzU4ZjUzYmE3NmU3NDYxMDRi
11
+ YTFiYjJmOTZmODUxNzk2NDJjYjczOGNhMGY1NTU2NjkxZDhhYjQ=
12
12
  data.tar.gz: !binary |-
13
- ZWY5NmZjZTE2OTg3NjdiMzg5MjBmNjk3OGUyODY5ODQ5NGJlMjc1OGZkMjI5
14
- YTA2NjM3MTA4MjFkMWNmZTVmODVjZTQ2ODZmZDFlMGM5Njk2MGNiMzAxMDc3
15
- M2EwOGNmODg2OWQzZjAwM2EyNjhjYTAxYjQyMjJmYjI0OGI1YzE=
13
+ MTAxY2Y4ZWMwYmIyYThiOGE5MGNmODc5NTE3MTM3NDkxYTYxYTNkYTU4MmM1
14
+ NmI2Njk4NzlhMmI0MTlhZTUzODIzOTZkNTRkMjRlODFhY2FmMDlhZjQ2ZDFm
15
+ Zjg1NTE3ZWU0NGE4MmNjYmQyNjhhYTZlMDU0MmI3YjBhZWM4YzQ=
@@ -4,6 +4,7 @@ Gem::Specification.new do |s|
4
4
  s.name = 'aws-flow'
5
5
  s.version = AWS::Flow::version
6
6
  s.date = Time.now
7
+ s.license = "Apache-2.0"
7
8
  s.summary = "AWS Flow Framework for Ruby"
8
9
  s.description = "Library to provide the AWS Flow Framework for Ruby"
9
10
  s.authors = "Michael Steger, Paritosh Mohan, Jacques Thomas"
@@ -15,7 +15,6 @@
15
15
 
16
16
  require 'aws/flow'
17
17
  include AWS::Flow::Core
18
-
19
18
  require 'aws-sdk-v1'
20
19
  require 'securerandom'
21
20
 
@@ -150,13 +150,11 @@ module AWS
150
150
  # @param [Hash] opts
151
151
  # Additional options to configure the workflow or activity execution.
152
152
  #
153
- # @option opts [true, false] :wait
154
- # *Optional* This boolean flag can be set to true if the result of the
155
- # task is required. Default value is false.
156
- #
157
- # @option opts [Integer] :wait_timeout
158
- # *Optional* This sets the timeout value for :wait. Default value is
159
- # nil.
153
+ # @option opts [true, false] :get_result
154
+ # *Optional* This boolean flag can be set to true if the result future
155
+ # if required. The future can be waited on by using the
156
+ # AWS::Flow::wait_for_all, AWS::Flow::wait_for_any methods or by
157
+ # calling the ExternalFuture#get method. Default value is false.
160
158
  #
161
159
  # @option opts [Hash] :exponential_retry
162
160
  # A hash of {AWS::Flow::ExponentialRetryOptions}. Default value is -
@@ -200,7 +198,7 @@ module AWS
200
198
  # )
201
199
  #
202
200
  def self.start(name_or_klass, input, options = {})
203
- AWS::Flow::Templates.start(name_or_klass, input, options)
201
+ AWS::Flow::Templates::Starter.start(name_or_klass, input, options)
204
202
  end
205
203
 
206
204
  end
@@ -17,6 +17,12 @@ require 'tmpdir'
17
17
 
18
18
  module AWS
19
19
  module Flow
20
+
21
+ def self.on_windows?
22
+ require 'rbconfig'
23
+ (RbConfig::CONFIG['host_os'] =~ /mswin|mingw/).nil? == false
24
+ end
25
+
20
26
  # Utilities for the AWS Flow Framework for Ruby.
21
27
  module Utilities
22
28
  # @api private
@@ -16,7 +16,7 @@
16
16
  module AWS
17
17
  module Flow
18
18
  def self.version
19
- "2.4.0"
19
+ "3.0.0"
20
20
  end
21
21
  end
22
22
  end
@@ -290,14 +290,20 @@ module AWS
290
290
  if @options
291
291
  @logger = @options.logger || Utilities::LogFactory.make_logger(self)
292
292
  @options.logger ||= @logger
293
+ # Set the number of execution workers to 0 if it's not already set and
294
+ # if the platform is Windows
295
+ @options.execution_workers ||= 0 if AWS::Flow.on_windows?
293
296
  max_workers = @options.execution_workers
297
+ # If max_workers is set to 0, then turn forking off
294
298
  @options.use_forking = false if (max_workers && max_workers.zero?)
295
299
  end
296
300
  max_workers = 20 if (max_workers.nil?)
301
+
297
302
  @executor = ForkingExecutor.new(
298
303
  :max_workers => max_workers,
299
304
  :logger => @logger
300
305
  )
306
+
301
307
  @shutdown_first_time_function = lambda do
302
308
  @executor.shutdown Float::INFINITY
303
309
  Kernel.exit
@@ -34,6 +34,11 @@ module AWS
34
34
  #
35
35
  class Future
36
36
 
37
+ def initialize
38
+ @conditional = FiberConditionVariable.new
39
+ @set = false
40
+ end
41
+
37
42
  # Sets the value of the {Future}, and notifies all of the fibers that
38
43
  # tried to call {#get} when this future wasn't ready.
39
44
  # @api private
@@ -41,8 +46,8 @@ module AWS
41
46
  raise AlreadySetException if @set
42
47
  @set = true
43
48
  @result = result
44
- @conditional.broadcast if @conditional
45
49
  @listeners.each { |b| b.call(self) } if @listeners
50
+ @conditional.broadcast if @conditional
46
51
  self
47
52
  end
48
53
 
@@ -62,10 +67,7 @@ module AWS
62
67
  # @raise CancellationError
63
68
  # when the task is cancelled.
64
69
  def get
65
- until @set
66
- @conditional ||= FiberConditionVariable.new
67
- @conditional.wait
68
- end
70
+ @conditional.wait until @set
69
71
  @result
70
72
  end
71
73
 
@@ -79,7 +81,7 @@ module AWS
79
81
  #
80
82
  # @api private
81
83
  def unset
82
- @set = nil
84
+ @set = false
83
85
  @result = nil
84
86
  end
85
87
 
@@ -135,6 +137,84 @@ module AWS
135
137
  self
136
138
  end
137
139
  end
140
+
141
+ # Represents the result of an asynchronous computation. Methods are
142
+ # provided to:
143
+ #
144
+ # * retrieve the result of the computation, once it is complete ({ExternalFuture#get}).
145
+ # * check if the computation is complete ({ExternalFuture#set?})
146
+ # * execute a block when computation is complete ({ExternalFuture#on_set})
147
+ #
148
+ # The result of a Future can only be retrieved when the computation has
149
+ # completed. {ExternalFuture#get} blocks execution, if necessary, until the
150
+ # ExternalFuture is ready.
151
+ #
152
+ # Unlike {Future}, {ExternalFuture#get} doesn't block Fibers. Instead it
153
+ # blocks the current thread by waiting on a ruby {ConditionVariable}. The
154
+ # condition variable is signalled when the future is set, which allows the
155
+ # thread to continue execution when the result is ready. This lets us use
156
+ # the future outside of an {AsyncScope}
157
+ #
158
+ class ExternalFuture < Future
159
+
160
+ def initialize
161
+ @conditional = ConditionVariable.new
162
+ @mutex = Mutex.new
163
+ @set = false
164
+ end
165
+
166
+ # Blocks if future is not set. Returns the result of the future once
167
+ # {#set} is true.
168
+ #
169
+ # @return
170
+ # The result of the future.
171
+ #
172
+ # @raise CancellationError
173
+ # when the task is cancelled.
174
+ def get(timeout=nil)
175
+ @mutex.synchronize do
176
+ unless @set
177
+ @conditional.wait(@mutex, timeout)
178
+ raise Timeout::Error.new unless @set
179
+ end
180
+ end
181
+ @result
182
+ end
183
+
184
+ def method_missing(method, *args, &block)
185
+ @mutex.synchronize do
186
+ super(method, *args, &block)
187
+ end
188
+ end
189
+
190
+ end
191
+
192
+ # Wrapper around a ruby {Mutex} and {ConditionVariable} to avoid
193
+ # writing the synchronization lines repeatedly.
194
+ # {ExternalConditionVariable#wait} will block the thread until
195
+ # {ConditionVariable} @cond is signalled
196
+ #
197
+ class ExternalConditionVariable
198
+
199
+ attr_reader :mutex, :cond
200
+
201
+ def initialize
202
+ @mutex = Mutex.new
203
+ @cond = ConditionVariable.new
204
+ end
205
+
206
+ # Block the thread till @cond is signalled
207
+ def wait(timeout=nil)
208
+ @mutex.synchronize { @cond.wait(@mutex, timeout) }
209
+ end
210
+
211
+ # Pass all messages to the encapsulated {ConditionVariable}
212
+ def method_missing(method, *args)
213
+ @cond.send(method, *args)
214
+ end
215
+
216
+ end
217
+
138
218
  end
139
219
  end
140
220
  end
@@ -124,19 +124,7 @@ module AWS
124
124
  # A list of the set futures, in the order of being set.
125
125
  #
126
126
  def wait_for_function(function, *futures)
127
- conditional = FiberConditionVariable.new
128
- futures.flatten!
129
- return nil if futures.empty?
130
- result = futures.select(&:set?)
131
- return futures.find(&:set?)if function.call(result, futures)
132
- futures.each do |f|
133
- f.on_set do |set_one|
134
- result << set_one
135
- conditional.broadcast if function.call(result, futures)
136
- end
137
- end
138
- conditional.wait
139
- result
127
+ wait_for_function_helper(nil, function, *futures)
140
128
  end
141
129
 
142
130
  # Blocks until *any* of the arguments are set.
@@ -162,6 +150,89 @@ module AWS
162
150
  def wait_for_all(*futures)
163
151
  wait_for_function(lambda {|result, future_list| result.size == future_list.size}, futures)
164
152
  end
153
+
154
+ # Blocks until *any* of the arguments are set.
155
+ #
156
+ # @param [Array<Future>] futures
157
+ # A list of futures to wait for. The function will return when at least one of these is set.
158
+ #
159
+ # @param [Integer] timeout
160
+ # The timeout value after which it will raise a Timeout::Error
161
+ #
162
+ # @return [Array<Future>]
163
+ # A list of the set futures, in the order of being set.
164
+ #
165
+ def timed_wait_for_any(timeout, *futures)
166
+ timed_wait_for_function(timeout, lambda {|result, future_list| result.length >= 1 }, futures)
167
+ end
168
+
169
+ # Blocks until *all* of the arguments are set or until timeout expires
170
+ #
171
+ # @param [Array<Future>] futures
172
+ # A list of futures to wait for. The function will return only when all of them are set.
173
+ #
174
+ # @param [Integer] timeout
175
+ # The timeout value after which it will raise a Timeout::Error
176
+ #
177
+ # @return [Array<Future>]
178
+ # A list of the set futures, in the order of being set.
179
+ #
180
+ def timed_wait_for_all(timeout, *futures)
181
+ timed_wait_for_function(timeout, lambda {|result, future_list| result.size == future_list.size}, futures)
182
+ end
183
+
184
+ # Waits for the passed-in function to complete, setting values for the provided futures when it does.
185
+ #
186
+ # @param function
187
+ # The function to wait for.
188
+ #
189
+ # @param [Array<Future>] futures
190
+ # A list of futures to provide values for when the function completes.
191
+ #
192
+ # @param [Integer] timeout
193
+ # The timeout value after which it will raise a Timeout::Error
194
+ #
195
+ # @return [Array<Future>]
196
+ # A list of the set futures, in the order of being set.
197
+ #
198
+ def timed_wait_for_function(timeout, function, *futures)
199
+ wait_for_function_helper(timeout, function, *futures)
200
+ end
201
+
202
+ # Helper method to refactor away the common implementation of
203
+ # wait_for_function and timed_wait_for_function.
204
+ def wait_for_function_helper(timeout, function, *futures)
205
+ futures.flatten!
206
+
207
+ f = futures.select { |x| x.is_a?(ExternalFuture) }
208
+
209
+ if f.size > 0 && f.size != futures.size
210
+ raise ArgumentError, "The futures array must contain either all "\
211
+ "objects of Future or all objects of ExternalFuture"
212
+ end
213
+
214
+ conditional = f.size == 0 ? FiberConditionVariable.new :
215
+ ExternalConditionVariable.new
216
+
217
+ return nil if futures.empty?
218
+ result = futures.select(&:set?)
219
+ return futures.find(&:set?)if function.call(result, futures)
220
+ futures.each do |f|
221
+ f.on_set do |set_one|
222
+ result << set_one
223
+ conditional.broadcast if function.call(result, futures)
224
+ end
225
+ end
226
+
227
+ if conditional.is_a?(FiberConditionVariable)
228
+ conditional.wait
229
+ else
230
+ conditional.wait(timeout)
231
+ raise Timeout::Error.new unless function.call(result, futures)
232
+ end
233
+ result
234
+ end
235
+
165
236
  end
166
237
  end
167
238
  end
@@ -240,7 +240,7 @@ module AWS
240
240
 
241
241
  if json_config['default_workers']
242
242
  # Also register the default result activity type in the given domain
243
- AWS::Flow::Templates.register_default_result_activity(domain)
243
+ AWS::Flow::Templates::Utils.register_default_result_activity(domain)
244
244
 
245
245
  klass = AWS::Flow::Templates.default_workflow
246
246
  task_list = FlowConstants.defaults[:task_list]
@@ -1,4 +1,6 @@
1
1
  require 'aws/templates/base'
2
2
  require 'aws/templates/activity'
3
3
  require 'aws/templates/default'
4
+ require 'aws/templates/result'
5
+ require 'aws/templates/utilities'
4
6
  require 'aws/templates/starter'
@@ -64,6 +64,47 @@ module AWS
64
64
  ActivityTemplate.new(name, opts)
65
65
  end
66
66
 
67
+ # This template represents a Result Activity in SWF.
68
+ class ResultActivityTemplate < ActivityTemplate
69
+ attr_reader :key
70
+
71
+ def initialize(key, opts = {})
72
+ @key = key
73
+
74
+ # Get the name of the result activity
75
+ name = "#{FlowConstants.defaults[:result_activity_prefix]}."\
76
+ "#{FlowConstants.defaults[:result_activity_method]}"
77
+
78
+ super(name, opts)
79
+ end
80
+
81
+ # Wraps the input into a result hash and calls the ActivityTemplate#run
82
+ # method to report the result
83
+ def run(input, context)
84
+ result = {}
85
+ result[:key] = @key
86
+ result[:result] = input
87
+ super(result, context)
88
+ end
89
+ end
90
+
91
+ # Initializes a result activity template
92
+ # @param {String} key
93
+ # A unique key that identifies the result of an activity execution
94
+ # @param {Hash} options
95
+ def result(key, opts = {})
96
+ AWS::Flow::Templates.send(:result, key, opts)
97
+ end
98
+
99
+ # Initializes a result activity template
100
+ # @param {String} key
101
+ # A unique key that identifies the result of an activity execution
102
+ # @param {Hash} options
103
+ def self.result(key, opts = {})
104
+ ResultActivityTemplate.new(key, opts)
105
+ end
106
+
107
+
67
108
  end
68
109
  end
69
110
  end
@@ -104,8 +104,6 @@ module AWS
104
104
  class FlowDefaultResultActivityRuby
105
105
  extend AWS::Flow::Activities
106
106
 
107
- attr_reader :result
108
-
109
107
  # Create the activity type with default options
110
108
  activity FlowConstants.defaults[:result_activity_method] do
111
109
  {
@@ -116,15 +114,20 @@ module AWS
116
114
  }
117
115
  end
118
116
 
119
- # Initialize the future upon instantiation
120
- def initialize
121
- @result = Future.new
117
+ # @param writer IO
118
+ # An optional IO file descripter to write the result to.
119
+ #
120
+ def initialize(writer=nil)
121
+ @writer = writer
122
122
  end
123
123
 
124
- # Set the future when the activity is run
124
+ # Serialize the input and write it to an IO writer if provided
125
125
  def run(input)
126
- @result.set(input)
127
- input
126
+ unless input.is_a?(Hash) && input.include?(:key) && input.include?(:result)
127
+ raise ArgumentError, "Incorrect input format for "\
128
+ "FlowDefaultResultActivityRuby.run"
129
+ end
130
+ @writer.puts Marshal.dump(input) if @writer
128
131
  end
129
132
 
130
133
  end