aws-flow 2.0.2 → 2.1.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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OTZjNzcwMDhjZjc5MzZlNjZjOTlmZDgxYjQzMmNlZmVkOWE1YTFhZA==
4
+ YzZjOTA1Y2MzMjE0MTg0N2I2ZjAwMjQ0ZjY0NDBhNGQ1YjhjMTA1NQ==
5
5
  data.tar.gz: !binary |-
6
- NzNhZDZlZmI5Y2JiMGU1OTY2NjEwNGIzZDEwZTJjNDAyYTVhMmVkNQ==
6
+ MzI3ZjhmNTE4ZTZhNWFkOTc2NGFiYTgxYjhlN2JiMDNmYWIwYmUwYw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- N2U0N2RjOWIzYWExM2JmMzI2ZDc2NjgyNzNmNjA0OThlZjcyNGM2YTQwNDEy
10
- NTg5ZTVlZTU3MmYwMjU5NzA3NzgyNzUwOWE2ZGMwNmU1YzE1MWE1Njk3MjBh
11
- ODBjM2I5NjMyNWM5MGExYWE2NjdiMGM5MjA5NTQ2YTY1OGZiZTQ=
9
+ Mzc5NDdjMzcyMGZlNmZmODQ1YTMxYzU5ZTU2MjVhZDZhZjcyMWZmZjMzZDU0
10
+ ODgyNmU3ZjdlZDk5MDc1MWYyMDVlY2IzZjk4NGFlN2Q2NDkwYTI1YjAxOWFl
11
+ M2Q0NDQ3MDI3NTkyNzZiNTZiNGM5ZDYwNGZiMDM2MDhhMjRlMGQ=
12
12
  data.tar.gz: !binary |-
13
- ODFiYmQyY2NmNTA0MTg0OGIzOGI0Y2Q5ZTUyZmVjNWE1ZTBhMDI5MzQ1OThl
14
- NDNjNDhhN2U2ODNjYWU1ZGQ1MTM2NDkyNDJjNDlhYmEzYTcwNzFlOWQzOGQ0
15
- ZDg5NTMzYmI2OWE4NmExMTEzZjZjMzA4ZTVhZTBmM2MxZjk4MmE=
13
+ Njk4Y2ZkMzUxNGY5ZGI3MjA1NGI2YTQ5N2QwYTBlZDFiMzU3ZWIxZDIxOGZl
14
+ NjViN2YyZjE3ODgzZjBjMGNhYzA2YjU5MTllODI5ODdiMjFkZjQ4M2QzYTM1
15
+ NzZhNmFmNTYxM2E2MTMyMTQyOGI5NjY5OTU0N2I0YzBiZGI3N2Q=
data/Rakefile CHANGED
@@ -17,19 +17,19 @@ require "rspec/core/rake_task"
17
17
 
18
18
  desc "Run unit tests"
19
19
  RSpec::Core::RakeTask.new(:unit_tests) do |spec|
20
- spec.rspec_opts = ['--color', '--format nested']
20
+ spec.rspec_opts = ['--color', '--format nested', '--profile']
21
21
  spec.pattern = 'spec/**/unit/*.rb'
22
22
  end
23
23
 
24
24
  desc "Run integration tests"
25
25
  RSpec::Core::RakeTask.new(:integration_tests) do |spec|
26
- spec.rspec_opts = ['--color', '--format nested']
26
+ spec.rspec_opts = ['--color', '--format nested', '--profile']
27
27
  spec.pattern = 'spec/**/integration/*.rb'
28
28
  end
29
29
 
30
30
  desc "Run all tests"
31
31
  RSpec::Core::RakeTask.new(:all_tests) do |spec|
32
- spec.rspec_opts = ['--color', '--format nested']
32
+ spec.rspec_opts = ['--color', '--format nested', '--profile']
33
33
  spec.pattern = 'spec/**/*.rb'
34
34
  end
35
35
 
@@ -69,16 +69,21 @@ module AWS
69
69
  result = @instance.send(@activity_method, *ruby_input)
70
70
  end
71
71
  rescue Exception => e
72
- #TODO we need the proper error handling here
73
72
  raise e if e.is_a? CancellationException
74
- raise ActivityFailureException.new(e.message, @converter.dump(e))
73
+
74
+ # Check if serialized exception violates the 32k limit and truncate it
75
+ reason, converted_failure = AWS::Flow::Utilities::check_and_truncate_exception(e, @converter)
76
+
77
+ # Wrap the exception that we got into an ActivityFailureException so
78
+ # that the task poller can handle it properly.
79
+ raise ActivityFailureException.new(reason, converted_failure)
75
80
  ensure
76
81
  @instance._activity_execution_context = nil
77
82
  end
78
83
  converted_result = @converter.dump(result)
79
84
  # We are going to have to convert this object into a string to submit it, and that's where the 32k limit will be enforced, so it's valid to turn the object to a string and check the size of the result
80
- if converted_result.to_s.size > 32768
81
- return @converter.dump("The result was too large, so we could not serialize it correctly. You can find the full result in the ActivityTaskPoller logs."), result, true
85
+ if converted_result.to_s.size > FlowConstants::DATA_LIMIT
86
+ return converted_result, result, true
82
87
  end
83
88
  return converted_result, result, false
84
89
  end
@@ -278,27 +278,16 @@ module AWS
278
278
  def make_fail_decision(decision_id, failure)
279
279
  decision_type = "FailWorkflowExecution"
280
280
 
281
- # Sizes taken from
282
- # http://docs.aws.amazon.com/amazonswf/latest/apireference/API_FailWorkflowExecutionDecisionAttributes.html
283
- #reason = failure.reason if (failure.respond_to? :reason)
284
- max_response_size = 32768
285
- truncation_overhead = 8000
286
- reason ||= failure.message.slice(0, 255)
287
- detail_size = max_response_size - truncation_overhead
288
-
289
- # If you don't have details, you must be some other type of
290
- # exception. We can't do anything exceedingly clever, so lets just get
291
- # the stack trace and pop that out.
292
- details = failure.details if (failure.respond_to? :details)
293
- details ||= failure.backtrace.join("")
294
- new_details = details[0..(max_response_size - truncation_overhead)]
295
- if details.length > (max_response_size - truncation_overhead)
296
- new_details += "->->->->->THIS BACKTRACE WAS TRUNCATED"
297
- end
298
- # details.unshift(reason)
299
- # details = details.join("\n")
300
-
301
- fail_workflow_execution_decision_attributes = {:reason => reason, :details => new_details}
281
+ # Get the reason from the failure. Or get the message if a
282
+ # CancellationException is initialized without a reason. Fall back to
283
+ # a default string if nothing is provided
284
+ reason = failure.reason || failure.message || "Workflow failure did not provide any reason."
285
+ # Get the details from the failure. Or get the backtrace if a
286
+ # CancellationException is initialized without a details. Fall back to
287
+ # a default string if nothing is provided
288
+ details = failure.details || failure.backtrace.to_s || "Workflow failure did not provide any details."
289
+
290
+ fail_workflow_execution_decision_attributes = { reason: reason, details: details }
302
291
  decision = {:decision_type => decision_type, :fail_workflow_execution_decision_attributes => fail_workflow_execution_decision_attributes}
303
292
  CompleteWorkflowStateMachine.new(decision_id, decision)
304
293
 
@@ -352,7 +341,7 @@ module AWS
352
341
  else
353
342
  @decision_helper[decision_id] = make_completion_decision(decision_id, {
354
343
  :decision_type => "CompleteWorkflowExecution",
355
- :complete_workflow_execution_decision_attributes => {:result => @result.get}})
344
+ :complete_workflow_execution_decision_attributes => {:result => @result.get }})
356
345
  end
357
346
  end
358
347
  end
@@ -158,7 +158,7 @@ module AWS
158
158
  if status.success?
159
159
  @log.debug "Child process #{pid} exited successfully"
160
160
  else
161
- @log.error "Child process #{pid} exited with non-zero status code"
161
+ @log.error "Child process #{pid} exited with non-zero status code: #{status}"
162
162
  end
163
163
 
164
164
  # Reap
@@ -96,6 +96,20 @@ module AWS
96
96
  attr_reader :exponential_retry_maximum_retry_interval_seconds, :exponential_retry_retry_expiration_seconds, :exponential_retry_backoff_coefficient, :exponential_retry_maximum_attempts, :exponential_retry_function, :default_data_converter, :exponential_retry_exceptions_to_include, :exponential_retry_exceptions_to_exclude, :jitter_function, :should_jitter, :exponential_retry_initial_retry_interval, :use_worker_task_list
97
97
  end
98
98
 
99
+ # Sizes taken from
100
+ # http://docs.aws.amazon.com/amazonswf/latest/apireference/API_FailWorkflowExecutionDecisionAttributes.html
101
+ DATA_LIMIT = 32768
102
+
103
+ # Number of chars that can fit in FlowException's reason
104
+ REASON_LIMIT = 256
105
+ # Number of chars that can fit in FlowException's details. Same as
106
+ # DATA_LIMIT
107
+ DETAILS_LIMIT = DATA_LIMIT
108
+ # This is the truncation overhead for serialization.
109
+ TRUNCATION_OVERHEAD = 8000
110
+ # Truncation string added to the end of a trucated string"
111
+ TRUNCATED = "[TRUNCATED]"
112
+
99
113
  INFINITY = -1
100
114
  RETENTION_DEFAULT = 7
101
115
  NUM_OF_WORKERS_DEFAULT = 1
@@ -64,19 +64,40 @@ module AWS
64
64
  @logger.info Utilities.workflow_task_to_debug_string("Got decision task", task)
65
65
 
66
66
  task_completed_request = @handler.handle_decision_task(task)
67
- @logger.debug "Response to the task will be #{task_completed_request}"
67
+ @logger.debug "Response to the task will be #{task_completed_request.inspect}"
68
+
68
69
  if !task_completed_request[:decisions].empty? && (task_completed_request[:decisions].first.keys.include?(:fail_workflow_execution_decision_attributes))
69
70
  fail_hash = task_completed_request[:decisions].first[:fail_workflow_execution_decision_attributes]
70
71
  reason = fail_hash[:reason]
71
72
  details = fail_hash[:details]
72
- @logger.debug "#{reason}, #{details}"
73
73
  end
74
- @service.respond_decision_task_completed(task_completed_request)
74
+
75
+ begin
76
+ @service.respond_decision_task_completed(task_completed_request)
77
+ rescue AWS::SimpleWorkflow::Errors::ValidationException => e
78
+ if e.message.include? "failed to satisfy constraint: Member must have length less than or equal to"
79
+ # We want to ensure that the WorkflowWorker doesn't just sit around and
80
+ # time the workflow out. If there is a validation failure possibly
81
+ # because of large inputs to child workflows/activities or large custom
82
+ # exceptions we should fail the workflow with some minimal details.
83
+ task_completed_request[:decisions] = [
84
+ {
85
+ decision_type: "FailWorkflowExecution",
86
+ fail_workflow_execution_decision_attributes: {
87
+ reason: Utilities.validation_error_string("Workflow"),
88
+ details: "AWS::SimpleWorkflow::Errors::ValidationException"
89
+ }
90
+ }
91
+ ]
92
+ @service.respond_decision_task_completed(task_completed_request)
93
+ end
94
+ @logger.error "#{task.workflow_type.inspect} failed with exception: #{e.inspect}"
95
+ end
75
96
  @logger.info Utilities.workflow_task_to_debug_string("Finished executing task", task)
76
97
  rescue AWS::SimpleWorkflow::Errors::UnknownResourceFault => e
77
- @logger.error "Error in the poller, #{e.class}, #{e}"
98
+ @logger.error "Error in the poller, #{e.inspect}"
78
99
  rescue Exception => e
79
- @logger.error "Error in the poller, #{e.class}, #{e}"
100
+ @logger.error "Error in the poller, #{e.inspect}"
80
101
  end
81
102
  end
82
103
  end
@@ -135,19 +156,23 @@ module AWS
135
156
  begin
136
157
  context = ActivityExecutionContext.new(@service, @domain, task)
137
158
  unless activity_implementation = @activity_definition_map[activity_type]
138
- raise "This activity worker was told to work on activity type #{activity_type.name}.#{activity_type.version}, but this activity worker only knows how to work on #{@activity_definition_map.keys.map(&:name).join' '}"
159
+ raise "This activity worker was told to work on activity type "\
160
+ "#{activity_type.inspect}, but this activity worker only knows "\
161
+ "how to work on #{@activity_definition_map.keys.map(&:name).join' '}"
139
162
  end
140
163
 
141
164
  output, original_result, too_large = activity_implementation.execute(task.input, context)
142
165
 
143
- @logger.debug "Responding on task_token #{task.task_token} for task #{task}."
166
+ @logger.debug "Responding on task_token #{task.task_token.inspect}."
144
167
  if too_large
145
- @logger.error "Activity #{activity_type.name}.#{activity_type.version} failed: The output of this activity was too large (greater than 2^15), and therefore aws-flow could not return it to SWF. aws-flow is now attempting to mark this activity as failed. For reference, the result was #{original_result}"
168
+ @logger.error "#{task.activity_type.inspect} failed: "\
169
+ "#{Utilities.validation_error_string_partial("Activity")} For "\
170
+ "reference, the result was #{original_result}"
146
171
 
147
172
  respond_activity_task_failed_with_retry(
148
173
  task.task_token,
149
- "An activity cannot send a response with a result larger than 32768 characters. Please reduce the response size. A truncated prefix output is included in the details field.",
150
- output
174
+ Utilities.validation_error_string("Activity"),
175
+ ""
151
176
  )
152
177
  elsif ! activity_implementation.execution_options.manual_completion
153
178
  @service.respond_activity_task_completed(
@@ -156,15 +181,13 @@ module AWS
156
181
  )
157
182
  end
158
183
  rescue ActivityFailureException => e
159
- @logger.error "Activity #{activity_type.name}.#{activity_type.version} with input #{task.input} failed with exception #{e}."
160
-
184
+ @logger.error "#{task.activity_type.inspect} failed with exception: #{e.inspect}."
161
185
  respond_activity_task_failed_with_retry(
162
186
  task.task_token,
163
187
  e.message,
164
188
  e.details
165
189
  )
166
190
  end
167
- #TODO all the completion stuffs
168
191
  end
169
192
 
170
193
  # Responds to the decider that the activity task has failed, and attempts
@@ -208,7 +231,28 @@ module AWS
208
231
  # is cancelled.
209
232
  #
210
233
  def respond_activity_task_canceled(task_token, message)
211
- @service.respond_activity_task_canceled({:task_token => task_token, :details => message})
234
+
235
+ begin
236
+ @service.respond_activity_task_canceled(
237
+ :task_token => task_token,
238
+ :details => message
239
+ )
240
+ rescue AWS::SimpleWorkflow::Errors::ValidationException => e
241
+ if e.message.include? "failed to satisfy constraint: Member must have length less than or equal to"
242
+ # We want to ensure that the ActivityWorker doesn't just sit
243
+ # around and time the activity out. If there is a validation failure
244
+ # possibly because of large custom exceptions we should fail the
245
+ # activity task with some minimal details
246
+ respond_activity_task_failed_with_retry(
247
+ task_token,
248
+ Utilities.validation_error_string("Activity"),
249
+ "AWS::SimpleWorkflow::Errors::ValidationException"
250
+ )
251
+ end
252
+ @logger.error "respond_activity_task_canceled call failed with "\
253
+ "exception: #{e.inspect}"
254
+ end
255
+
212
256
  end
213
257
 
214
258
  # Responds to the decider that the activity task has failed. No retry is
@@ -231,7 +275,28 @@ module AWS
231
275
  #
232
276
  def respond_activity_task_failed(task_token, reason, details)
233
277
  @logger.debug "The task token to be reported on is #{task_token}"
234
- @service.respond_activity_task_failed(:task_token => task_token, :reason => reason.to_s, :details => details.to_s)
278
+
279
+ begin
280
+ @service.respond_activity_task_failed(
281
+ task_token: task_token,
282
+ reason: reason.to_s,
283
+ details: details.to_s
284
+ )
285
+ rescue AWS::SimpleWorkflow::Errors::ValidationException => e
286
+ if e.message.include? "failed to satisfy constraint: Member must have length less than or equal to"
287
+ # We want to ensure that the ActivityWorker doesn't just sit
288
+ # around and time the activity out. If there is a validation failure
289
+ # possibly because of large custom exceptions we should fail the
290
+ # activity task with some minimal details
291
+ respond_activity_task_failed_with_retry(
292
+ task.task_token,
293
+ Utilities.validation_error_string("Activity"),
294
+ "AWS::SimpleWorkflow::Errors::ValidationException"
295
+ )
296
+ end
297
+ @logger.error "respond_activity_task_failed call failed with "\
298
+ "exception: #{e.inspect}"
299
+ end
235
300
  end
236
301
 
237
302
  # Processes the specified activity task.
@@ -263,18 +328,17 @@ module AWS
263
328
  begin
264
329
  execute(task)
265
330
  rescue CancellationException => e
266
- @logger.error "Got an error, #{e.message}, while executing #{task.activity_type.name}."
331
+ @logger.error "#{task.activity_type.inspect} failed with exception: #{e.inspect}"
267
332
  respond_activity_task_canceled_with_retry(task.task_token, e.message)
268
333
  rescue Exception => e
269
- @logger.error "Got an error, #{e.message}, while executing #{task.activity_type.name}. Full stack trace: #{e.backtrace}"
334
+ @logger.error "#{task.activity_type.inspect} failed with exception: #{e.inspect}"
270
335
  respond_activity_task_failed_with_retry(task.task_token, e.message, e.backtrace)
271
- #Do rescue stuff
272
336
  ensure
273
337
  @poll_semaphore.release
274
338
  end
275
339
  rescue Exception => e
276
340
  semaphore_needs_release = true
277
- @logger.error "Got into the other error mode: #{e}"
341
+ @logger.error "Error in the poller, exception: #{e.inspect}. stacktrace: #{e.backtrace}"
278
342
  raise e
279
343
  ensure
280
344
  @poll_semaphore.release if semaphore_needs_release
@@ -307,7 +371,7 @@ module AWS
307
371
  @logger.info Utilities.activity_task_to_debug_string("Got activity task", task)
308
372
  end
309
373
  rescue Exception => e
310
- @logger.error "Error in the poller, #{e.class}, #{e}"
374
+ @logger.error "Error in the poller, #{e.inspect}"
311
375
  @poll_semaphore.release
312
376
  return false
313
377
  end
@@ -40,6 +40,20 @@ module AWS
40
40
  return "#{message} #{task.activity_type.name}.#{task.activity_type.version} with input: #{task.input} and task_token: #{task.task_token}"
41
41
  end
42
42
 
43
+ # The following two methods are used to generate an error string when
44
+ # response size of a workflow or activity is greater than 32k.
45
+ def self.validation_error_string_partial(infix)
46
+ str = infix.downcase == "workflow" ? "A" : "An"
47
+ str += " #{infix} cannot send a response with data larger than "\
48
+ "#{FlowConstants::DATA_LIMIT} characters. Please limit the size of the "\
49
+ "response."
50
+ str
51
+ end
52
+
53
+ def self.validation_error_string(infix)
54
+ "#{self.validation_error_string_partial(infix)} You can look at the "\
55
+ "#{infix} Worker logs to see the original response."
56
+ end
43
57
 
44
58
  # @api private
45
59
  def self.drill_on_future(future)
@@ -73,6 +87,78 @@ module AWS
73
87
  client_options
74
88
  end
75
89
 
90
+ # @api private
91
+ # This method is used to truncate Activity and Workflow exceptions to
92
+ # fit them into responses to the SWF service.
93
+ def self.check_and_truncate_exception error, converter
94
+
95
+ # serialize the exception so that we can check the actual size of the
96
+ # payload.
97
+ converted_failure = converter.dump(error)
98
+ # get the reason/message of the exception
99
+ reason = error.message
100
+
101
+ # truncate the reason if needed and add a smaller version of the
102
+ # truncation string at the end
103
+ if reason.size > FlowConstants::REASON_LIMIT
104
+ # saving some space at the end to add the truncation string
105
+ reason = reason.slice(0, FlowConstants::REASON_LIMIT - FlowConstants::TRUNCATED.size)
106
+ reason += FlowConstants::TRUNCATED
107
+ end
108
+
109
+ if converted_failure.to_s.size > FlowConstants::DETAILS_LIMIT
110
+ detail_limit = FlowConstants::DETAILS_LIMIT - (reason.size + FlowConstants::TRUNCATION_OVERHEAD)
111
+ # Get the exception details if the exception is from the flow family of
112
+ # exceptions
113
+ details = error.details if error.respond_to? :details
114
+ # If you don't have details, you must be some other type of
115
+ # exception. We can't do anything exceedingly clever, so lets just get
116
+ # the stack trace and pop that out.
117
+ details ||= error.backtrace.join unless error.backtrace.nil?
118
+ details ||= ""
119
+
120
+ # If the exception was indeed a flow family of exceptions, then details
121
+ # inside would most likely be another exception. Instead of digging for
122
+ # more exceptions inside this one, let's just get all the information
123
+ # from this class and put it in a string so that we can truncate and
124
+ # serialize it.
125
+ if details.is_a? Exception
126
+ details = "exception.class=#{details.class}|exception.message=#{details.message}|exception.backtrace=#{details.backtrace}"
127
+ if details.respond_to? :details
128
+ details += "|exception.details=#{details.details}"
129
+ end
130
+ end
131
+
132
+ # truncate the details if needed and add truncation string at the end
133
+ if details.size > detail_limit
134
+ # saving some space at the end to add the truncation string
135
+ details = details.slice(0, detail_limit - FlowConstants::TRUNCATED.size)
136
+ details += FlowConstants::TRUNCATED
137
+ end
138
+
139
+ # Here we generate a new exception with the reason and details that we
140
+ # got above. We are using the 'exception' factory method instead of
141
+ # initializing it directly because Flow Exceptions' constructors are not
142
+ # uniform and could require 2..4 arguments. Whereas a regular ruby
143
+ # exception only requires 0..1. Other custom exceptions could require
144
+ # arbitrary number of arguments.
145
+ new_exception = error.exception(reason)
146
+ if new_exception.respond_to? :details
147
+ new_exception.details = details
148
+ else
149
+ new_exception.set_backtrace(details)
150
+ end
151
+ converted_failure = converter.dump(new_exception)
152
+
153
+ end
154
+
155
+ # Return back both - reason and exception so that the caller doesn't
156
+ # need to check whether this exception responds to :reason or not, i.e.
157
+ # whether this is a flow exception or a regular ruby exception
158
+ [reason, converted_failure]
159
+
160
+ end
161
+
76
162
 
77
163
  # @api private
78
164
  def self.interpret_block_for_options(option_class, block, use_defaults = false)
@@ -16,7 +16,7 @@
16
16
  module AWS
17
17
  module Flow
18
18
  def self.version
19
- "2.0.2"
19
+ "2.1.0"
20
20
  end
21
21
  end
22
22
  end
@@ -346,10 +346,10 @@ module AWS
346
346
  client_options = Utilities::client_options_from_method_name(method_name, @options)
347
347
  options = Utilities::merge_all_options(client_options, options)
348
348
 
349
- @converter ||= YAMLDataConverter.new
349
+ @data_converter = options[:data_converter]
350
350
  # Basically, we want to avoid the special "NoInput, but allow stuff like nil in"
351
351
  if ! (input.class <= NoInput || input.empty?)
352
- options[:input] = @converter.dump input
352
+ options[:input] = @data_converter.dump input
353
353
  end
354
354
  if @workflow_class.nil?
355
355
  execution_method = @options.execution_method
@@ -50,15 +50,32 @@ module AWS
50
50
  method_output.set(@instance.send(@workflow_method, *ruby_input))
51
51
  end
52
52
  end
53
- t.rescue(Exception) do |error|
54
- @failure = WorkflowException.new(error.message, @converter.dump(error))
55
- #TODO error handling stuff
53
+ t.rescue(Exception) do |e|
54
+
55
+ # Check if serialized exception violates the 32k limit and truncate it
56
+ reason, converted_failure = AWS::Flow::Utilities::check_and_truncate_exception(e, @converter)
57
+
58
+ # Wrap the exception that we got into a WorkflowException so that it
59
+ # can be handled correctly.
60
+
61
+ @failure = WorkflowException.new(reason, converted_failure)
56
62
  end
57
63
  t.ensure do
58
64
  raise @failure if @failure
59
- result.set(@converter.dump method_output.get)
65
+ # We are going to have to convert this object into a string to submit it,
66
+ # and that's where the 32k limit will be enforced, so it's valid to turn
67
+ # the object to a string and check the size of the result
68
+ output = @converter.dump method_output.get
69
+
70
+ if output.to_s.size > FlowConstants::DATA_LIMIT
71
+ raise WorkflowException.new(
72
+ Utilities.validation_error_string_partial("Workflow"),
73
+ ""
74
+ )
75
+ end
76
+ result.set(output)
77
+ end
60
78
  end
61
- end
62
79
  return result
63
80
  end
64
81
 
@@ -25,6 +25,7 @@ module AWS
25
25
  def initialize(reason = nil, details = nil)
26
26
  @reason = reason
27
27
  @details = details
28
+ super(reason)
28
29
  end
29
30
  end
30
31
 
@@ -194,7 +194,7 @@ module AWS
194
194
  task_list = expand_task_list(w['task_list'])
195
195
 
196
196
  # create a worker
197
- worker = ActivityWorker.new(swf.client, domain, task_list, *w['activities']) {{ max_workers: fork_count }}
197
+ worker = ActivityWorker.new(swf.client, domain, task_list, *w['activities']) {{ execution_workers: fork_count }}
198
198
  add_implementations(worker, w, {config_key: 'activity_classes',
199
199
  clazz: AWS::Flow::Activities})
200
200
 
@@ -189,12 +189,11 @@ describe Activities do
189
189
 
190
190
  activity :run_activity1, :run_activity2, :run_activity3, :run_activity4 do
191
191
  {
192
- default_task_heartbeat_timeout: 60,
193
192
  version: "1.0",
194
193
  default_task_list: "large_activity_task_list",
195
- default_task_schedule_to_close_timeout: 10,
196
- default_task_schedule_to_start_timeout: 5,
197
- default_task_start_to_close_timeout: 5,
194
+ default_task_schedule_to_close_timeout: 60,
195
+ default_task_schedule_to_start_timeout: 30,
196
+ default_task_start_to_close_timeout: 30,
198
197
  exponential_retry: {
199
198
  retries_per_exception: {
200
199
  ActivityTaskTimedOutException => Float::INFINITY,
@@ -191,29 +191,288 @@ describe "RubyFlowDecider" do
191
191
  @my_workflow_client = workflow_client(@domain.client, @domain) { { from_class: @workflow_class } }
192
192
  end
193
193
 
194
- it "ensures that an activity returning more than 32k data fails the activity" do
195
- general_test(:task_list => "ActivityTaskLargeOutput", :class_name => "ActivityTaskLargeOutput")
196
- @activity_class.class_eval do
197
- def run_activity1
198
- # Make sure we return something that's over 32k. Note this won't
199
- # necessarily work with all converters, as it's pretty trivially
200
- # compressible
201
- return ":" + "a" * 33000
194
+ describe "Workflow/Activity return values/exceptions" do
195
+ it "ensures that an activity returning more than 32k data fails the activity" do
196
+ general_test(:task_list => "ActivityTaskLargeOutput", :class_name => "ActivityTaskLargeOutput")
197
+ @activity_class.class_eval do
198
+ def run_activity1
199
+ # Make sure we return something that's over 32k. Note this won't
200
+ # necessarily work with all converters, as it's pretty trivially
201
+ # compressible
202
+ return ":" + "a" * 33000
203
+ end
202
204
  end
205
+ workflow_execution = @my_workflow_client.start_execution
206
+ @worker.run_once
207
+ @activity_worker.run_once
208
+ @worker.run_once
209
+ wait_for_execution(workflow_execution)
210
+ history_events = workflow_execution.events.map(&:event_type)
211
+ # Previously, it would time out, as the failure would include the original
212
+ # large output that killed the completion and failure call. Thus, we need to
213
+ # check that we fail the ActivityTask.
214
+ history_events.should include "ActivityTaskFailed"
215
+
216
+ workflow_execution.events.to_a.last.attributes.details.should_not =~ /Psych/
217
+ workflow_execution.events.to_a.last.attributes.reason.should == Utilities.validation_error_string("Activity")
218
+ history_events.last.should == "WorkflowExecutionFailed"
219
+ end
220
+
221
+ it "ensures that an activity returning an exception of size more than 32k fails the activity correctly and truncates the message" do
222
+ general_test(:task_list => "ActivityTaskExceptionLargeOutput", :class_name => "ActivityTaskExceptionLargeOutput")
223
+ @activity_class.class_eval do
224
+ def run_activity1
225
+ raise ":" + "a" * 33000
226
+ end
227
+ end
228
+ workflow_execution = @my_workflow_client.start_execution
229
+ @worker.run_once
230
+ @activity_worker.run_once
231
+ @worker.run_once
232
+ wait_for_execution(workflow_execution)
233
+ history_events = workflow_execution.events.map(&:event_type)
234
+ # Previously, it would time out, as the failure would include the original
235
+ # large output that killed the completion and failure call. Thus, we need to
236
+ # check that we fail the ActivityTask.
237
+ history_events.should include "ActivityTaskFailed"
238
+
239
+ workflow_execution.events.to_a.last.attributes.details.should_not =~ /Psych/
240
+ history_events.last.should == "WorkflowExecutionFailed"
241
+ workflow_execution.events.to_a.last.attributes.reason.should include("[TRUNCATED]")
242
+ details = workflow_execution.events.to_a.last.attributes.details
243
+ exception = FlowConstants.default_data_converter.load(details)
244
+ exception.class.should == AWS::Flow::ActivityTaskFailedException
245
+ end
246
+
247
+ it "ensures that an activity returning a Cancellation Exception of size more than 32k fails the activity" do
248
+ general_test(:task_list => "ActivityTaskCancellationExceptionLargeOutput", :class_name => "ActivityTaskCancellationExceptionLargeOutput")
249
+ @activity_class.class_eval do
250
+ def run_activity1
251
+ raise CancellationException.new("a" * 33000)
252
+ end
253
+ end
254
+ workflow_execution = @my_workflow_client.start_execution
255
+ @worker.run_once
256
+ @activity_worker.run_once
257
+ @worker.run_once
258
+ wait_for_execution(workflow_execution)
259
+ history_events = workflow_execution.events.map(&:event_type)
260
+ history_events.should include "ActivityTaskFailed"
261
+
262
+ history_events.last.should == "WorkflowExecutionFailed"
263
+ event = workflow_execution.events.to_a.select { |x| x.event_type == "ActivityTaskFailed"}
264
+ event.first.attributes.reason.should == Utilities.validation_error_string("Activity")
265
+ event.first.attributes.details.should == "AWS::SimpleWorkflow::Errors::ValidationException"
266
+ end
267
+
268
+ it "ensures that a workflow output > 32k fails the workflow" do
269
+ general_test(:task_list => "WorkflowOutputTooLarge", :class_name => "WorkflowOutputTooLarge")
270
+ @workflow_class.class_eval do
271
+ def entry_point
272
+ return ":" + "a" * 33000
273
+ end
274
+ end
275
+ workflow_execution = @my_workflow_client.start_execution
276
+ @worker.run_once
277
+ wait_for_execution(workflow_execution)
278
+ last_event = workflow_execution.events.to_a.last
279
+ last_event.event_type.should == "WorkflowExecutionFailed"
280
+ last_event.attributes.reason.should == Utilities.validation_error_string_partial("Workflow")
281
+ end
282
+
283
+ it "ensures that a workflow exception details > 32k fails the workflow correctly and truncates the details" do
284
+ general_test(:task_list => "WorkflowExceptionDetailsTooLarge", :class_name => "WorkflowExceptionDetailsTooLarge")
285
+ @workflow_class.class_eval do
286
+ def entry_point
287
+ e = RuntimeError.new("a")
288
+ e.set_backtrace("a"*25769)
289
+ raise e
290
+ end
291
+ end
292
+ workflow_execution = @my_workflow_client.start_execution
293
+ @worker.run_once
294
+ wait_for_execution(workflow_execution)
295
+ last_event = workflow_execution.events.to_a.last
296
+ last_event.event_type.should == "WorkflowExecutionFailed"
297
+ details = workflow_execution.events.to_a.last.attributes.details
298
+ exception = FlowConstants.default_data_converter.load(details)
299
+ exception.class.should == RuntimeError
300
+ exception.backtrace.first.should include ("[TRUNCATED]")
301
+ end
302
+
303
+ it "ensures that a workflow exception message > 256 characters fails the workflow correctly and truncates the message" do
304
+ general_test(:task_list => "WorkflowExceptionMessageTooLarge", :class_name => "WorkflowExceptionMessageTooLarge")
305
+ @workflow_class.class_eval do
306
+ def entry_point
307
+ raise "a" * 257
308
+ end
309
+ end
310
+ workflow_execution = @my_workflow_client.start_execution
311
+ @worker.run_once
312
+ wait_for_execution(workflow_execution)
313
+ last_event = workflow_execution.events.to_a.last
314
+ last_event.event_type.should == "WorkflowExecutionFailed"
315
+ workflow_execution.events.to_a.last.attributes.reason.should include("[TRUNCATED]")
316
+ details = workflow_execution.events.to_a.last.attributes.details
317
+ exception = FlowConstants.default_data_converter.load(details)
318
+ exception.class.should == RuntimeError
319
+ end
320
+
321
+
322
+ it "ensures that a respond_decision_task_completed call with response > 32k that we can't truncate fails the workflow correctly" do
323
+ class CustomException < FlowException
324
+ def initialize(reason, details)
325
+ @something = "a"*50000
326
+ super(reason, details)
327
+ end
328
+ end
329
+ general_test(:task_list => "CustomWorkflowExceptionTooLarge", :class_name => "CustomWorkflowExceptionTooLarge")
330
+ @workflow_class.class_eval do
331
+ def entry_point
332
+ raise CustomException.new("asdf", "sdf")
333
+ end
334
+ end
335
+ workflow_execution = @my_workflow_client.start_execution
336
+ @worker.run_once
337
+ wait_for_execution(workflow_execution)
338
+ last_event = workflow_execution.events.to_a.last
339
+ last_event.event_type.should == "WorkflowExecutionFailed"
340
+ workflow_execution.events.to_a.last.attributes.reason.should == Utilities.validation_error_string("Workflow")
341
+ end
342
+
343
+ it "ensures that an activity input > 32k data fails the workflow" do
344
+ general_test(:task_list => "ActivityTaskLargeInput", :class_name => "ActivityTaskLargeInput")
345
+ @workflow_class.class_eval do
346
+ def entry_point
347
+ activity.run_activity1("A"*50000)
348
+ end
349
+ end
350
+ workflow_execution = @my_workflow_client.start_execution
351
+ worker = WorkflowWorker.new(@domain.client, @domain, "ActivityTaskLargeInput", @workflow_class)
352
+ worker.register
353
+ worker.run_once
354
+ wait_for_execution(workflow_execution)
355
+ last_event = workflow_execution.events.to_a.last
356
+ last_event.event_type.should == "WorkflowExecutionFailed"
357
+ last_event.attributes.reason.should == Utilities.validation_error_string("Workflow")
358
+ last_event.attributes.details.should == "AWS::SimpleWorkflow::Errors::ValidationException"
359
+ end
360
+
361
+
362
+ it "ensures that a child workflow input > 32k fails the workflow" do
363
+ general_test(:task_list => "ChildWorkflowInputTooLarge", :class_name => "ChildWorkflowInputTooLarge")
364
+ @workflow_class.class_eval do
365
+ workflow(:child) do
366
+ {
367
+ version: "1.0",
368
+ default_execution_start_to_close_timeout: 300,
369
+ default_task_list: "ChildWorkflowInputTooLarge",
370
+ prefix_name: "ChildWorkflowInputTooLargeWorkflow"
371
+ }
372
+ end
373
+ def entry_point
374
+ child_client = AWS::Flow::workflow_client(nil, nil) { { from_class: "ChildWorkflowInputTooLargeWorkflow" } }
375
+ child_client.child("A"*50000)
376
+ end
377
+ def child(input); end
378
+ end
379
+
380
+ worker = WorkflowWorker.new(@domain.client, @domain, "ChildWorkflowInputTooLarge", @workflow_class)
381
+ worker.register
382
+ workflow_execution = @my_workflow_client.start_execution
383
+ worker.run_once
384
+
385
+ wait_for_execution(workflow_execution)
386
+ last_event = workflow_execution.events.to_a.last
387
+ last_event.event_type.should == "WorkflowExecutionFailed"
388
+ workflow_execution.events.to_a.last.attributes.reason.should == Utilities.validation_error_string("Workflow")
389
+ workflow_execution.events.to_a.last.attributes.details.should == "AWS::SimpleWorkflow::Errors::ValidationException"
390
+ end
391
+
392
+
393
+
394
+ it "ensures that a child workflow exception > 32k fails the workflow correctly and truncates the stacktrace" do
395
+ general_test(:task_list => "ChildWorkflowExceptionTooLarge", :class_name => "ChildWorkflowExceptionTooLarge")
396
+ @workflow_class.class_eval do
397
+ workflow(:child) do
398
+ {
399
+ version: "1.0",
400
+ default_execution_start_to_close_timeout: 300,
401
+ default_task_list: "ChildWorkflowExceptionTooLarge",
402
+ prefix_name: "ChildWorkflowExceptionTooLargeWorkflow"
403
+ }
404
+ end
405
+ def entry_point
406
+ child_client = AWS::Flow::workflow_client(nil, nil) { { from_class: "ChildWorkflowExceptionTooLargeWorkflow" } }
407
+ child_client.child
408
+ end
409
+ def child
410
+ raise ":" + "a" * 33000
411
+ end
412
+ end
413
+
414
+ worker = WorkflowWorker.new(@domain.client, @domain, "ChildWorkflowExceptionTooLarge", @workflow_class)
415
+ worker.register
416
+ workflow_execution = @my_workflow_client.start_execution
417
+ worker.run_once
418
+ worker.run_once
419
+ worker.run_once
420
+ worker.run_once
421
+
422
+ wait_for_execution(workflow_execution)
423
+ last_event = workflow_execution.events.to_a.last
424
+ last_event.event_type.should == "WorkflowExecutionFailed"
425
+ workflow_execution.events.to_a.last.attributes.reason.should include("[TRUNCATED]")
426
+ details = workflow_execution.events.to_a.last.attributes.details
427
+ exception = FlowConstants.default_data_converter.load(details)
428
+ exception.class.should == AWS::Flow::ChildWorkflowFailedException
429
+ exception.cause.class.should == RuntimeError
430
+ end
431
+
432
+
433
+ it "ensures that a child child workflow exception > 32k fails the workflow correctly and truncates the stacktrace" do
434
+ general_test(:task_list => "ChildChildWorkflowExceptionTooLarge", :class_name => "ChildChildWorkflowExceptionTooLarge")
435
+ @workflow_class.class_eval do
436
+ workflow(:child, :child_1) do
437
+ {
438
+ version: "1.0",
439
+ default_execution_start_to_close_timeout: 300,
440
+ default_task_list: "ChildChildWorkflowExceptionTooLarge",
441
+ prefix_name: "ChildChildWorkflowExceptionTooLargeWorkflow"
442
+ }
443
+ end
444
+ def entry_point
445
+ child_client = AWS::Flow::workflow_client(nil, nil) { { from_class: "ChildChildWorkflowExceptionTooLargeWorkflow" } }
446
+ child_client.child
447
+ end
448
+ def child
449
+ child_1_client = AWS::Flow::workflow_client(nil, nil) { { from_class: "ChildChildWorkflowExceptionTooLargeWorkflow" } }
450
+ child_1_client.child_1
451
+ end
452
+ def child_1
453
+ raise ":" + "a" * 33000
454
+ end
455
+ end
456
+ worker = WorkflowWorker.new(@domain.client, @domain, "ChildChildWorkflowExceptionTooLarge", @workflow_class)
457
+ worker.register
458
+ workflow_execution = @my_workflow_client.start_execution
459
+ worker.run_once
460
+ worker.run_once
461
+ worker.run_once
462
+ worker.run_once
463
+ worker.run_once
464
+ worker.run_once
465
+ worker.run_once
466
+
467
+ wait_for_execution(workflow_execution)
468
+ last_event = workflow_execution.events.to_a.last
469
+ last_event.event_type.should == "WorkflowExecutionFailed"
470
+ workflow_execution.events.to_a.last.attributes.reason.should include("[TRUNCATED]")
471
+ details = workflow_execution.events.to_a.last.attributes.details
472
+ exception = FlowConstants.default_data_converter.load(details)
473
+ exception.class.should == AWS::Flow::ChildWorkflowFailedException
474
+ exception.cause.class.should == AWS::Flow::ChildWorkflowFailedException
203
475
  end
204
- workflow_execution = @my_workflow_client.start_execution
205
- @worker.run_once
206
- @activity_worker.run_once
207
- @worker.run_once
208
- wait_for_execution(workflow_execution)
209
- history_events = workflow_execution.events.map(&:event_type)
210
- # Previously, it would time out, as the failure would include the original
211
- # large output that killed the completion and failure call. Thus, we need to
212
- # check that we fail the ActivityTask.
213
- history_events.should include "ActivityTaskFailed"
214
-
215
- workflow_execution.events.to_a.last.attributes.details.should_not =~ /Psych/
216
- history_events.last.should == "WorkflowExecutionFailed"
217
476
  end
218
477
 
219
478
  it "ensures that activities can be processed with different configurations" do
@@ -508,7 +767,7 @@ describe "RubyFlowDecider" do
508
767
  def entry_point
509
768
  domain = get_test_domain
510
769
  wf = AWS::Flow.workflow_client(domain.client, domain) { { from_class: "FooBar" } }
511
- wf.start_execution("foo")
770
+ wf.start_execution
512
771
  end
513
772
  end
514
773
  workflow_execution = @my_workflow_client.start_execution
@@ -517,6 +776,7 @@ describe "RubyFlowDecider" do
517
776
  @worker.run_once
518
777
  child_worker.run_once
519
778
  @worker.run_once
779
+ @worker.run_once
520
780
  wait_for_execution(workflow_execution)
521
781
  workflow_execution.events.map(&:event_type).last.should == "WorkflowExecutionFailed"
522
782
  # Make sure this is actually caused by a child workflow failed
@@ -732,16 +992,17 @@ describe "RubyFlowDecider" do
732
992
  general_test(:task_list => "exponential_retry_key", :class_name => "ExponentialRetryKey")
733
993
  @workflow_class.class_eval do
734
994
  def entry_point
735
- activity.reconfigure(:run_activity1) {
995
+ activity.run_activity1 do
736
996
  {
737
997
  :exponential_retry => {:maximum_attempts => 1},
738
- :default_task_schedule_to_start_timeout => 5}
739
- }
740
- activity.run_activity1
998
+ :schedule_to_start_timeout => 1
999
+ }
1000
+ end
741
1001
  end
742
1002
  end
1003
+ worker = WorkflowWorker.new(@domain.client, @domain, "exponential_retry_key", @workflow_class)
743
1004
  workflow_execution = @my_workflow_client.start_execution
744
- 4.times { @worker.run_once }
1005
+ 4.times { worker.run_once }
745
1006
  wait_for_execution(workflow_execution)
746
1007
  workflow_execution.events.to_a.last.event_type.should == "WorkflowExecutionFailed"
747
1008
  end
@@ -1232,6 +1232,7 @@ describe "FakeHistory" do
1232
1232
  #workflow_execution = my_workflow.start_execution
1233
1233
  swf_client.trace.first[:decisions].first[:decision_type].should == "CompleteWorkflowExecution"
1234
1234
  end
1235
+
1235
1236
  end
1236
1237
 
1237
1238
  describe "Misc tests" do
@@ -1283,3 +1284,258 @@ describe "Misc tests" do
1283
1284
  end
1284
1285
  end
1285
1286
 
1287
+ describe "Workflow/Activity return values/exceptions" do
1288
+ it "ensures that a workflow exception message > 32k fails the workflow correctly and truncates the message" do
1289
+
1290
+ class WorkflowOutputTooLarge
1291
+ extend Workflows
1292
+ workflow(:entry_point) do
1293
+ {
1294
+ version: "1.0",
1295
+ default_execution_start_to_close_timeout: 600,
1296
+ }
1297
+ end
1298
+
1299
+ def entry_point
1300
+ raise "a"*33000
1301
+ end
1302
+ end
1303
+
1304
+ class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1305
+ def get_decision_task
1306
+ TestHistoryWrapper.new($workflow_type, FakeWorkflowExecution.new(nil, nil),
1307
+ FakeEvents.new(["WorkflowExecutionStarted",
1308
+ "DecisionTaskScheduled",
1309
+ "DecisionTaskStarted",
1310
+ ]))
1311
+ end
1312
+ end
1313
+
1314
+ $workflow_type = FakeWorkflowType.new(nil, "WorkflowOutputTooLarge.entry_point", "1.0")
1315
+ swf_client = FakeServiceClient.new
1316
+ domain = FakeDomain.new($workflow_type)
1317
+ client = AWS::Flow::workflow_client(swf_client, domain) { { from_class: "WorkflowOutputTooLarge" } }
1318
+
1319
+ task_list = "WorkflowsOutputTooLarge"
1320
+
1321
+ client.start_execution
1322
+ worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, WorkflowOutputTooLarge)
1323
+ worker.start
1324
+
1325
+ swf_client.trace.first[:decisions].first[:decision_type].should == "FailWorkflowExecution"
1326
+
1327
+ reason = swf_client.trace.first[:decisions].first[:fail_workflow_execution_decision_attributes][:reason]
1328
+ details = swf_client.trace.first[:decisions].first[:fail_workflow_execution_decision_attributes][:details]
1329
+ reason.should include("TRUNCATED")
1330
+ exception = FlowConstants.default_data_converter.load(details)
1331
+ exception.class.should == RuntimeError
1332
+ exception.message.should == "a"*245+"[TRUNCATED]"
1333
+ end
1334
+
1335
+ it "ensures that a workflow backtrace > 32k fails the workflow correctly and truncates the backtrace" do
1336
+
1337
+ class WorkflowOutputTooLarge
1338
+ extend Workflows
1339
+ workflow(:entry_point) do
1340
+ {
1341
+ version: "1.0",
1342
+ default_execution_start_to_close_timeout: 600,
1343
+ }
1344
+ end
1345
+
1346
+ def entry_point
1347
+ a = StandardError.new("SIMULATION")
1348
+ a.set_backtrace("a"*33000)
1349
+ raise a
1350
+ end
1351
+ end
1352
+
1353
+ class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1354
+ def get_decision_task
1355
+ TestHistoryWrapper.new($workflow_type, FakeWorkflowExecution.new(nil, nil),
1356
+ FakeEvents.new(["WorkflowExecutionStarted",
1357
+ "DecisionTaskScheduled",
1358
+ "DecisionTaskStarted",
1359
+ ]))
1360
+ end
1361
+ end
1362
+
1363
+ $workflow_type = FakeWorkflowType.new(nil, "WorkflowOutputTooLarge.entry_point", "1.0")
1364
+ swf_client = FakeServiceClient.new
1365
+ domain = FakeDomain.new($workflow_type)
1366
+ client = AWS::Flow::workflow_client(swf_client, domain) { { from_class: "WorkflowOutputTooLarge" } }
1367
+
1368
+ task_list = "WorkflowsOutputTooLarge"
1369
+
1370
+ client.start_execution
1371
+ worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, WorkflowOutputTooLarge)
1372
+ worker.start
1373
+
1374
+ reason = swf_client.trace.first[:decisions].first[:fail_workflow_execution_decision_attributes][:reason]
1375
+ details = swf_client.trace.first[:decisions].first[:fail_workflow_execution_decision_attributes][:details]
1376
+ exception = FlowConstants.default_data_converter.load(details)
1377
+ exception.class.should == StandardError
1378
+ exception.message.should == "SIMULATION"
1379
+ exception.backtrace.first.should include("[TRUNCATED]")
1380
+ swf_client.trace.first[:decisions].first[:decision_type].should == "FailWorkflowExecution"
1381
+ end
1382
+
1383
+ it "ensures that a workflow output > 32k fails the workflow correctly" do
1384
+
1385
+ class WorkflowOutputTooLarge
1386
+ extend Workflows
1387
+ workflow(:entry_point) do
1388
+ {
1389
+ version: "1.0",
1390
+ default_execution_start_to_close_timeout: 600,
1391
+ }
1392
+ end
1393
+
1394
+ def entry_point
1395
+ return "a"*33000
1396
+ end
1397
+ end
1398
+
1399
+ class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1400
+ def get_decision_task
1401
+ TestHistoryWrapper.new($workflow_type, FakeWorkflowExecution.new(nil, nil),
1402
+ FakeEvents.new(["WorkflowExecutionStarted",
1403
+ "DecisionTaskScheduled",
1404
+ "DecisionTaskStarted",
1405
+ ]))
1406
+ end
1407
+ end
1408
+
1409
+ $workflow_type = FakeWorkflowType.new(nil, "WorkflowOutputTooLarge.entry_point", "1.0")
1410
+ swf_client = FakeServiceClient.new
1411
+ domain = FakeDomain.new($workflow_type)
1412
+ client = AWS::Flow::workflow_client(swf_client, domain) { { from_class: "WorkflowOutputTooLarge" } }
1413
+
1414
+ task_list = "WorkflowsOutputTooLarge"
1415
+
1416
+ client.start_execution
1417
+ worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, WorkflowOutputTooLarge)
1418
+ worker.start
1419
+ swf_client.trace.first[:decisions].first[:decision_type].should == "FailWorkflowExecution"
1420
+ end
1421
+
1422
+
1423
+ it "ensures that child workflows returning exceptions > 32k get wrapped correctly" do
1424
+
1425
+ class ChildWorkflowOutputTooLargeTestWorkflow
1426
+ extend Workflows
1427
+ workflow(:entry_point, :child) do
1428
+ {
1429
+ version: "1.0",
1430
+ default_execution_start_to_close_timeout: 600,
1431
+ }
1432
+ end
1433
+
1434
+ def entry_point
1435
+ $my_workflow_client.child { { workflow_id: "child_workflow_test" }}
1436
+ end
1437
+ def child; end
1438
+ end
1439
+ class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1440
+ def get_decision_task
1441
+ fake_workflow_type = FakeWorkflowType.new(nil, "ChildWorkflowOutputTooLargeTestWorkflow.entry_point", "1.0")
1442
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
1443
+ FakeEvents.new(["WorkflowExecutionStarted",
1444
+ "DecisionTaskScheduled",
1445
+ "DecisionTaskStarted",
1446
+ "DecisionTaskCompleted",
1447
+ ["StartChildWorkflowExecutionInitiated", {:workflow_id => "child_workflow_test"}],
1448
+ ["ChildWorkflowExecutionStarted", {:workflow_execution => FakeWorkflowExecution.new("1", "child_workflow_test"), :workflow_id => "child_workflow_test"}],
1449
+ "DecisionTaskScheduled",
1450
+ "DecisionTaskStarted",
1451
+ ["ChildWorkflowExecutionFailed", {:workflow_execution => FakeWorkflowExecution.new("1", "child_workflow_test"), :workflow_id => "child_workflow_test", :workflow_type => fake_workflow_type, :reason => "a"*245+"[TRUNCATED]", :details => "SIMULATED"}],
1452
+ "DecisionTaskScheduled",
1453
+ "DecisionTaskStarted",
1454
+ ]))
1455
+ end
1456
+ end
1457
+ workflow_type = FakeWorkflowType.new(nil, "ChildWorkflowOutputTooLargeTestWorkflow.entry_point", "1")
1458
+
1459
+ domain = FakeDomain.new(workflow_type)
1460
+ swf_client = FakeServiceClient.new
1461
+ $my_workflow_client = workflow_client(swf_client, domain) { { from_class: "ChildWorkflowOutputTooLargeTestWorkflow" } }
1462
+
1463
+ task_list = "ChildWorkflowOutputTooLargeTestWorkflow"
1464
+
1465
+ $my_workflow_client.start_execution
1466
+ worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, ChildWorkflowOutputTooLargeTestWorkflow)
1467
+ worker.start
1468
+ swf_client.trace.first[:decisions].first[:decision_type].should == "FailWorkflowExecution"
1469
+ reason = swf_client.trace.first[:decisions].first[:fail_workflow_execution_decision_attributes][:reason]
1470
+ details = swf_client.trace.first[:decisions].first[:fail_workflow_execution_decision_attributes][:details]
1471
+ reason.should include("TRUNCATED")
1472
+ exception = FlowConstants.default_data_converter.load(details)
1473
+ exception.class.should == AWS::Flow::ChildWorkflowFailedException
1474
+ exception.reason.should == "a"*245+"[TRUNCATED]"
1475
+ exception.details.should == "SIMULATED"
1476
+ end
1477
+
1478
+ it "ensures that activities returning exceptions > 32k get wrapped correctly" do
1479
+
1480
+ class ActivityExceptionTooLargeActivity
1481
+ extend Activities
1482
+ activity(:activity_a) do
1483
+ {
1484
+ version: "1.0"
1485
+ }
1486
+ end
1487
+ def activity_a; end
1488
+ end
1489
+ class ActivityExceptionTooLargeTestWorkflow
1490
+ extend Workflows
1491
+ workflow(:entry_point) do
1492
+ {
1493
+ version: "1.0",
1494
+ default_execution_start_to_close_timeout: 600,
1495
+ }
1496
+ end
1497
+
1498
+ activity_client(:client) { { from_class: "ActivityExceptionTooLargeActivity" } }
1499
+ def entry_point
1500
+ client.activity_a
1501
+ end
1502
+ end
1503
+ class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1504
+ def get_decision_task
1505
+ fake_workflow_type = FakeWorkflowType.new(nil, "ActivityExceptionTooLargeTestWorkflow.entry_point", "1.0")
1506
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
1507
+ FakeEvents.new(["WorkflowExecutionStarted",
1508
+ "DecisionTaskScheduled",
1509
+ "DecisionTaskStarted",
1510
+ "DecisionTaskCompleted",
1511
+ ["ActivityTaskScheduled", {:activity_id => "Activity1"}],
1512
+ "ActivityTaskStarted",
1513
+ ["ActivityTaskFailed", scheduled_event_id: 5, activity_id: "Activity1", reason: "a"*245+"[TRUNCATED]", details: "SIMULATED"],
1514
+ "DecisionTaskScheduled",
1515
+ "DecisionTaskStarted",
1516
+ ]))
1517
+ end
1518
+ end
1519
+ workflow_type = FakeWorkflowType.new(nil, "ActivityExceptionTooLargeTestWorkflow.entry_point", "1")
1520
+
1521
+ domain = FakeDomain.new(workflow_type)
1522
+ swf_client = FakeServiceClient.new
1523
+ $my_workflow_client = workflow_client(swf_client, domain) { { from_class: "ActivityExceptionTooLargeTestWorkflow" } }
1524
+
1525
+ task_list = "ActivityExceptionTooLargeTestWorkflow"
1526
+
1527
+ $my_workflow_client.start_execution
1528
+ worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, ActivityExceptionTooLargeTestWorkflow)
1529
+ worker.start
1530
+ swf_client.trace.first[:decisions].first[:decision_type].should == "FailWorkflowExecution"
1531
+ reason = swf_client.trace.first[:decisions].first[:fail_workflow_execution_decision_attributes][:reason]
1532
+ details = swf_client.trace.first[:decisions].first[:fail_workflow_execution_decision_attributes][:details]
1533
+ reason.should include("TRUNCATED")
1534
+ exception = FlowConstants.default_data_converter.load(details)
1535
+ exception.class.should == AWS::Flow::ActivityTaskFailedException
1536
+ exception.reason.should == "a"*245+"[TRUNCATED]"
1537
+ exception.details.should == "SIMULATED"
1538
+ end
1539
+
1540
+ end
1541
+
@@ -75,6 +75,29 @@ describe WorkflowClient do
75
75
  client.workflow_b
76
76
  end
77
77
 
78
+ it "ensures workflow client uses user supplied data_converter" do
79
+ class FooWorkflow
80
+ extend AWS::Flow::Workflows
81
+ workflow :foo_workflow do
82
+ { version: "1.0" }
83
+ end
84
+ end
85
+ class FooDataConverter; end
86
+
87
+ swf = double(AWS::SimpleWorkflow)
88
+ domain = double(AWS::SimpleWorkflow::Domain)
89
+
90
+ swf.stub(:start_workflow_execution).and_return({"runId" => "111"})
91
+ domain.stub(:name)
92
+ array = []
93
+ domain.stub(:workflow_executions).and_return(array)
94
+ array.stub(:at)
95
+
96
+ client = AWS::Flow::workflow_client(swf, domain) { { from_class: "WorkflowClientTestWorkflow" } }
97
+ expect_any_instance_of(FooDataConverter).to receive(:dump)
98
+ client.start_execution(:foo_workflow, "some_input") { { data_converter: FooDataConverter.new } }
99
+ end
100
+
78
101
  end
79
102
 
80
103
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-flow
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Steger, Paritosh Mohan, Jacques Thomas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-30 00:00:00.000000000 Z
11
+ date: 2014-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-v1