temporal-ruby 0.0.1.pre.pre1 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/README.md +23 -3
- data/lib/gen/temporal/api/command/v1/message_pb.rb +1 -1
- data/lib/gen/temporal/api/enums/v1/common_pb.rb +7 -0
- data/lib/gen/temporal/api/errordetails/v1/message_pb.rb +5 -6
- data/lib/gen/temporal/api/version/v1/message_pb.rb +19 -8
- data/lib/gen/temporal/api/workflowservice/v1/request_response_pb.rb +19 -8
- data/lib/gen/temporal/api/workflowservice/v1/service_services_pb.rb +0 -3
- data/lib/temporal.rb +104 -0
- data/lib/temporal/activity/context.rb +5 -1
- data/lib/temporal/activity/poller.rb +26 -9
- data/lib/temporal/activity/task_processor.rb +33 -20
- data/lib/temporal/client/converter/base.rb +35 -0
- data/lib/temporal/client/converter/composite.rb +49 -0
- data/lib/temporal/client/converter/payload/bytes.rb +30 -0
- data/lib/temporal/client/converter/payload/json.rb +28 -0
- data/lib/temporal/client/converter/payload/nil.rb +27 -0
- data/lib/temporal/client/grpc_client.rb +102 -27
- data/lib/temporal/client/retryer.rb +49 -0
- data/lib/temporal/client/serializer.rb +2 -0
- data/lib/temporal/client/serializer/cancel_timer.rb +2 -2
- data/lib/temporal/client/serializer/complete_workflow.rb +6 -4
- data/lib/temporal/client/serializer/continue_as_new.rb +37 -0
- data/lib/temporal/client/serializer/fail_workflow.rb +2 -2
- data/lib/temporal/client/serializer/failure.rb +4 -2
- data/lib/temporal/client/serializer/record_marker.rb +6 -4
- data/lib/temporal/client/serializer/request_activity_cancellation.rb +2 -2
- data/lib/temporal/client/serializer/retry_policy.rb +24 -0
- data/lib/temporal/client/serializer/schedule_activity.rb +8 -20
- data/lib/temporal/client/serializer/start_child_workflow.rb +9 -20
- data/lib/temporal/client/serializer/start_timer.rb +2 -2
- data/lib/temporal/concerns/payloads.rb +51 -0
- data/lib/temporal/configuration.rb +31 -4
- data/lib/temporal/error_handler.rb +11 -0
- data/lib/temporal/errors.rb +24 -0
- data/lib/temporal/execution_options.rb +9 -1
- data/lib/temporal/json.rb +3 -1
- data/lib/temporal/logger.rb +17 -0
- data/lib/temporal/metadata.rb +11 -3
- data/lib/temporal/metadata/activity.rb +15 -2
- data/lib/temporal/metadata/workflow.rb +8 -0
- data/lib/temporal/metadata/workflow_task.rb +11 -0
- data/lib/temporal/retry_policy.rb +6 -9
- data/lib/temporal/saga/concern.rb +1 -1
- data/lib/temporal/testing.rb +1 -0
- data/lib/temporal/testing/future_registry.rb +1 -1
- data/lib/temporal/testing/local_activity_context.rb +1 -1
- data/lib/temporal/testing/local_workflow_context.rb +38 -14
- data/lib/temporal/testing/scheduled_workflows.rb +75 -0
- data/lib/temporal/testing/temporal_override.rb +35 -7
- data/lib/temporal/testing/workflow_override.rb +6 -1
- data/lib/temporal/version.rb +1 -1
- data/lib/temporal/worker.rb +28 -10
- data/lib/temporal/workflow.rb +8 -2
- data/lib/temporal/workflow/command.rb +3 -0
- data/lib/temporal/workflow/context.rb +40 -5
- data/lib/temporal/workflow/errors.rb +39 -0
- data/lib/temporal/workflow/executor.rb +1 -1
- data/lib/temporal/workflow/future.rb +18 -6
- data/lib/temporal/workflow/history/event.rb +1 -3
- data/lib/temporal/workflow/history/event_target.rb +4 -0
- data/lib/temporal/workflow/history/window.rb +1 -1
- data/lib/temporal/workflow/poller.rb +41 -13
- data/lib/temporal/workflow/replay_aware_logger.rb +4 -4
- data/lib/temporal/workflow/state_manager.rb +33 -52
- data/lib/temporal/workflow/task_processor.rb +41 -11
- metadata +21 -9
- data/lib/temporal/client/serializer/payload.rb +0 -25
@@ -1,16 +1,23 @@
|
|
1
1
|
require 'temporal/client'
|
2
|
+
require 'temporal/thread_pool'
|
2
3
|
require 'temporal/middleware/chain'
|
3
4
|
require 'temporal/workflow/task_processor'
|
5
|
+
require 'temporal/error_handler'
|
4
6
|
|
5
7
|
module Temporal
|
6
8
|
class Workflow
|
7
9
|
class Poller
|
8
|
-
|
10
|
+
DEFAULT_OPTIONS = {
|
11
|
+
thread_pool_size: 10
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
def initialize(namespace, task_queue, workflow_lookup, middleware = [], options = {})
|
9
15
|
@namespace = namespace
|
10
16
|
@task_queue = task_queue
|
11
17
|
@workflow_lookup = workflow_lookup
|
12
18
|
@middleware = middleware
|
13
19
|
@shutting_down = false
|
20
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
14
21
|
end
|
15
22
|
|
16
23
|
def start
|
@@ -18,50 +25,71 @@ module Temporal
|
|
18
25
|
@thread = Thread.new(&method(:poll_loop))
|
19
26
|
end
|
20
27
|
|
21
|
-
def
|
28
|
+
def stop_polling
|
22
29
|
@shutting_down = true
|
23
|
-
|
30
|
+
Temporal.logger.info('Shutting down a workflow poller')
|
31
|
+
end
|
32
|
+
|
33
|
+
def cancel_pending_requests
|
34
|
+
client.cancel_polling_request
|
24
35
|
end
|
25
36
|
|
26
37
|
def wait
|
27
|
-
|
38
|
+
thread.join
|
39
|
+
thread_pool.shutdown
|
28
40
|
end
|
29
41
|
|
30
42
|
private
|
31
43
|
|
32
|
-
attr_reader :namespace, :task_queue, :
|
44
|
+
attr_reader :namespace, :task_queue, :workflow_lookup, :middleware, :options, :thread
|
33
45
|
|
34
46
|
def client
|
35
47
|
@client ||= Temporal::Client.generate
|
36
48
|
end
|
37
49
|
|
38
|
-
def middleware_chain
|
39
|
-
@middleware_chain ||= Middleware::Chain.new(middleware)
|
40
|
-
end
|
41
|
-
|
42
50
|
def shutting_down?
|
43
51
|
@shutting_down
|
44
52
|
end
|
45
53
|
|
46
54
|
def poll_loop
|
47
|
-
|
48
|
-
|
55
|
+
last_poll_time = Time.now
|
56
|
+
metrics_tags = { namespace: namespace, task_queue: task_queue }.freeze
|
57
|
+
|
58
|
+
loop do
|
59
|
+
thread_pool.wait_for_available_threads
|
60
|
+
|
61
|
+
return if shutting_down?
|
62
|
+
|
63
|
+
time_diff_ms = ((Time.now - last_poll_time) * 1000).round
|
64
|
+
Temporal.metrics.timing('workflow_poller.time_since_last_poll', time_diff_ms, metrics_tags)
|
65
|
+
Temporal.logger.debug("Polling Worklow task queue", { namespace: namespace, task_queue: task_queue })
|
49
66
|
|
50
67
|
task = poll_for_task
|
51
|
-
|
68
|
+
last_poll_time = Time.now
|
69
|
+
next unless task&.workflow_type
|
70
|
+
|
71
|
+
thread_pool.schedule { process(task) }
|
52
72
|
end
|
53
73
|
end
|
54
74
|
|
55
75
|
def poll_for_task
|
56
76
|
client.poll_workflow_task_queue(namespace: namespace, task_queue: task_queue)
|
57
77
|
rescue StandardError => error
|
58
|
-
Temporal.logger.error("Unable to poll
|
78
|
+
Temporal.logger.error("Unable to poll Workflow task queue", { namespace: namespace, task_queue: task_queue, error: error.inspect })
|
79
|
+
Temporal::ErrorHandler.handle(error)
|
80
|
+
|
59
81
|
nil
|
60
82
|
end
|
61
83
|
|
62
84
|
def process(task)
|
85
|
+
middleware_chain = Middleware::Chain.new(middleware)
|
86
|
+
|
63
87
|
TaskProcessor.new(task, namespace, workflow_lookup, client, middleware_chain).process
|
64
88
|
end
|
89
|
+
|
90
|
+
def thread_pool
|
91
|
+
@thread_pool ||= ThreadPool.new(options[:thread_pool_size])
|
92
|
+
end
|
65
93
|
end
|
66
94
|
end
|
67
95
|
end
|
@@ -11,17 +11,17 @@ module Temporal
|
|
11
11
|
end
|
12
12
|
|
13
13
|
SEVERITIES.each do |severity|
|
14
|
-
define_method severity do |message|
|
14
|
+
define_method severity do |message, data = {}|
|
15
15
|
return if replay?
|
16
16
|
|
17
|
-
main_logger.public_send(severity, message)
|
17
|
+
main_logger.public_send(severity, message, data)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
def log(severity, message)
|
21
|
+
def log(severity, message, data = {})
|
22
22
|
return if replay?
|
23
23
|
|
24
|
-
main_logger.log(severity, message)
|
24
|
+
main_logger.log(severity, message, data)
|
25
25
|
end
|
26
26
|
|
27
27
|
private
|
@@ -1,13 +1,17 @@
|
|
1
|
-
require '
|
1
|
+
require 'set'
|
2
2
|
require 'temporal/errors'
|
3
3
|
require 'temporal/workflow/command'
|
4
4
|
require 'temporal/workflow/command_state_machine'
|
5
5
|
require 'temporal/workflow/history/event_target'
|
6
6
|
require 'temporal/metadata'
|
7
|
+
require 'temporal/concerns/payloads'
|
8
|
+
require 'temporal/workflow/errors'
|
7
9
|
|
8
10
|
module Temporal
|
9
11
|
class Workflow
|
10
12
|
class StateManager
|
13
|
+
include Concerns::Payloads
|
14
|
+
|
11
15
|
SIDE_EFFECT_MARKER = 'SIDE_EFFECT'.freeze
|
12
16
|
RELEASE_MARKER = 'RELEASE'.freeze
|
13
17
|
|
@@ -101,7 +105,7 @@ module Temporal
|
|
101
105
|
dispatch(
|
102
106
|
History::EventTarget.workflow,
|
103
107
|
'started',
|
104
|
-
|
108
|
+
from_payloads(event.attributes.input),
|
105
109
|
Metadata.generate(Metadata::WORKFLOW_TYPE, event.attributes)
|
106
110
|
)
|
107
111
|
|
@@ -131,26 +135,26 @@ module Temporal
|
|
131
135
|
|
132
136
|
when 'ACTIVITY_TASK_SCHEDULED'
|
133
137
|
state_machine.schedule
|
134
|
-
discard_command(
|
138
|
+
discard_command(target)
|
135
139
|
|
136
140
|
when 'ACTIVITY_TASK_STARTED'
|
137
141
|
state_machine.start
|
138
142
|
|
139
143
|
when 'ACTIVITY_TASK_COMPLETED'
|
140
144
|
state_machine.complete
|
141
|
-
dispatch(target, 'completed',
|
145
|
+
dispatch(target, 'completed', from_result_payloads(event.attributes.result))
|
142
146
|
|
143
147
|
when 'ACTIVITY_TASK_FAILED'
|
144
148
|
state_machine.fail
|
145
|
-
dispatch(target, 'failed',
|
149
|
+
dispatch(target, 'failed', Temporal::Workflow::Errors.generate_error(event.attributes.failure, ActivityException))
|
146
150
|
|
147
151
|
when 'ACTIVITY_TASK_TIMED_OUT'
|
148
152
|
state_machine.time_out
|
149
|
-
dispatch(target, 'failed',
|
153
|
+
dispatch(target, 'failed', Temporal::Workflow::Errors.generate_error(event.attributes.failure))
|
150
154
|
|
151
155
|
when 'ACTIVITY_TASK_CANCEL_REQUESTED'
|
152
156
|
state_machine.requested
|
153
|
-
discard_command(
|
157
|
+
discard_command(target)
|
154
158
|
|
155
159
|
when 'REQUEST_CANCEL_ACTIVITY_TASK_FAILED'
|
156
160
|
state_machine.fail
|
@@ -158,11 +162,11 @@ module Temporal
|
|
158
162
|
|
159
163
|
when 'ACTIVITY_TASK_CANCELED'
|
160
164
|
state_machine.cancel
|
161
|
-
dispatch(target, 'failed',
|
165
|
+
dispatch(target, 'failed', Temporal::Workflow::Errors.generate_error(event.attributes.failure))
|
162
166
|
|
163
167
|
when 'TIMER_STARTED'
|
164
168
|
state_machine.start
|
165
|
-
discard_command(
|
169
|
+
discard_command(target)
|
166
170
|
|
167
171
|
when 'TIMER_FIRED'
|
168
172
|
state_machine.complete
|
@@ -193,10 +197,10 @@ module Temporal
|
|
193
197
|
|
194
198
|
when 'MARKER_RECORDED'
|
195
199
|
state_machine.complete
|
196
|
-
handle_marker(event.id, event.attributes.marker_name,
|
200
|
+
handle_marker(event.id, event.attributes.marker_name, from_details_payloads(event.attributes.details['data']))
|
197
201
|
|
198
202
|
when 'WORKFLOW_EXECUTION_SIGNALED'
|
199
|
-
dispatch(target, 'signaled', event.attributes.signal_name,
|
203
|
+
dispatch(target, 'signaled', event.attributes.signal_name, from_signal_payloads(event.attributes.input))
|
200
204
|
|
201
205
|
when 'WORKFLOW_EXECUTION_TERMINATED'
|
202
206
|
# todo
|
@@ -206,30 +210,30 @@ module Temporal
|
|
206
210
|
|
207
211
|
when 'START_CHILD_WORKFLOW_EXECUTION_INITIATED'
|
208
212
|
state_machine.schedule
|
209
|
-
discard_command(
|
213
|
+
discard_command(target)
|
210
214
|
|
211
215
|
when 'START_CHILD_WORKFLOW_EXECUTION_FAILED'
|
212
216
|
state_machine.fail
|
213
|
-
dispatch(target, 'failed', 'StandardError',
|
217
|
+
dispatch(target, 'failed', 'StandardError', from_payloads(event.attributes.cause))
|
214
218
|
|
215
219
|
when 'CHILD_WORKFLOW_EXECUTION_STARTED'
|
216
220
|
state_machine.start
|
217
221
|
|
218
222
|
when 'CHILD_WORKFLOW_EXECUTION_COMPLETED'
|
219
223
|
state_machine.complete
|
220
|
-
dispatch(target, 'completed',
|
224
|
+
dispatch(target, 'completed', from_result_payloads(event.attributes.result))
|
221
225
|
|
222
226
|
when 'CHILD_WORKFLOW_EXECUTION_FAILED'
|
223
227
|
state_machine.fail
|
224
|
-
dispatch(target, 'failed',
|
228
|
+
dispatch(target, 'failed', Temporal::Workflow::Errors.generate_error(event.attributes.failure))
|
225
229
|
|
226
230
|
when 'CHILD_WORKFLOW_EXECUTION_CANCELED'
|
227
231
|
state_machine.cancel
|
228
|
-
dispatch(target, 'failed',
|
232
|
+
dispatch(target, 'failed', Temporal::Workflow::Errors.generate_error(event.attributes.failure))
|
229
233
|
|
230
234
|
when 'CHILD_WORKFLOW_EXECUTION_TIMED_OUT'
|
231
235
|
state_machine.time_out
|
232
|
-
dispatch(target, 'failed',
|
236
|
+
dispatch(target, 'failed', Temporal::Workflow::Errors.generate_error(event.attributes.failure))
|
233
237
|
|
234
238
|
when 'CHILD_WORKFLOW_EXECUTION_TERMINATED'
|
235
239
|
# todo
|
@@ -277,8 +281,18 @@ module Temporal
|
|
277
281
|
dispatcher.dispatch(target, name, attributes)
|
278
282
|
end
|
279
283
|
|
280
|
-
def discard_command(
|
281
|
-
|
284
|
+
def discard_command(target)
|
285
|
+
# Pop the first command from the list, it is expected to match
|
286
|
+
existing_command_id, existing_command = commands.shift
|
287
|
+
|
288
|
+
if !existing_command_id
|
289
|
+
raise NonDeterministicWorkflowError, "A command #{target} was not scheduled upon replay"
|
290
|
+
end
|
291
|
+
|
292
|
+
existing_target = event_target_from(existing_command_id, existing_command)
|
293
|
+
if target != existing_target
|
294
|
+
raise NonDeterministicWorkflowError, "Unexpected command #{existing_target} (expected #{target})"
|
295
|
+
end
|
282
296
|
end
|
283
297
|
|
284
298
|
def handle_marker(id, type, details)
|
@@ -304,39 +318,6 @@ module Temporal
|
|
304
318
|
end
|
305
319
|
end
|
306
320
|
|
307
|
-
def parse_payload(payload)
|
308
|
-
return if payload.nil? || payload.payloads.empty?
|
309
|
-
|
310
|
-
binary = payload.payloads.first.data
|
311
|
-
JSON.deserialize(binary)
|
312
|
-
end
|
313
|
-
|
314
|
-
def parse_failure(failure, default_exception_class = StandardError)
|
315
|
-
case failure.failure_info
|
316
|
-
when :application_failure_info
|
317
|
-
exception_class = safe_constantize(failure.application_failure_info.type)
|
318
|
-
exception_class ||= default_exception_class
|
319
|
-
details = parse_payload(failure.application_failure_info.details)
|
320
|
-
backtrace = failure.stack_trace.split("\n")
|
321
|
-
|
322
|
-
exception_class.new(details).tap do |exception|
|
323
|
-
exception.set_backtrace(backtrace) if !backtrace.empty?
|
324
|
-
end
|
325
|
-
when :timeout_failure_info
|
326
|
-
TimeoutError.new("Timeout type: #{failure.timeout_failure_info.timeout_type.to_s}")
|
327
|
-
when :canceled_failure_info
|
328
|
-
# TODO: Distinguish between different entity cancellations
|
329
|
-
StandardError.new(parse_payload(failure.canceled_failure_info.details))
|
330
|
-
else
|
331
|
-
StandardError.new(failure.message)
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
|
-
def safe_constantize(const)
|
336
|
-
Object.const_get(const) if Object.const_defined?(const)
|
337
|
-
rescue NameError
|
338
|
-
nil
|
339
|
-
end
|
340
321
|
end
|
341
322
|
end
|
342
323
|
end
|
@@ -1,14 +1,18 @@
|
|
1
1
|
require 'temporal/workflow/executor'
|
2
2
|
require 'temporal/workflow/history'
|
3
3
|
require 'temporal/metadata'
|
4
|
+
require 'temporal/error_handler'
|
4
5
|
require 'temporal/errors'
|
5
6
|
|
6
7
|
module Temporal
|
7
8
|
class Workflow
|
8
9
|
class TaskProcessor
|
10
|
+
MAX_FAILED_ATTEMPTS = 1
|
11
|
+
|
9
12
|
def initialize(task, namespace, workflow_lookup, client, middleware_chain)
|
10
13
|
@task = task
|
11
14
|
@namespace = namespace
|
15
|
+
@metadata = Metadata.generate(Metadata::WORKFLOW_TASK_TYPE, task, namespace)
|
12
16
|
@task_token = task.task_token
|
13
17
|
@workflow_name = task.workflow_type.name
|
14
18
|
@workflow_class = workflow_lookup.find(workflow_name)
|
@@ -19,37 +23,35 @@ module Temporal
|
|
19
23
|
def process
|
20
24
|
start_time = Time.now
|
21
25
|
|
22
|
-
Temporal.logger.
|
26
|
+
Temporal.logger.debug("Processing Workflow task", metadata.to_h)
|
23
27
|
Temporal.metrics.timing('workflow_task.queue_time', queue_time_ms, workflow: workflow_name)
|
24
28
|
|
25
29
|
if !workflow_class
|
26
30
|
raise Temporal::WorkflowNotRegistered, 'Workflow is not registered with this worker'
|
27
31
|
end
|
28
32
|
|
29
|
-
history =
|
33
|
+
history = fetch_full_history
|
30
34
|
# TODO: For sticky workflows we need to cache the Executor instance
|
31
35
|
executor = Workflow::Executor.new(workflow_class, history)
|
32
|
-
metadata = Metadata.generate(Metadata::WORKFLOW_TASK_TYPE, task, namespace)
|
33
36
|
|
34
37
|
commands = middleware_chain.invoke(metadata) do
|
35
38
|
executor.run
|
36
39
|
end
|
37
40
|
|
38
41
|
complete_task(commands)
|
39
|
-
rescue Temporal::ClientError => error
|
40
|
-
fail_task(error)
|
41
42
|
rescue StandardError => error
|
42
|
-
Temporal.
|
43
|
-
|
43
|
+
Temporal::ErrorHandler.handle(error, metadata: metadata)
|
44
|
+
|
45
|
+
fail_task(error)
|
44
46
|
ensure
|
45
47
|
time_diff_ms = ((Time.now - start_time) * 1000).round
|
46
48
|
Temporal.metrics.timing('workflow_task.latency', time_diff_ms, workflow: workflow_name)
|
47
|
-
Temporal.logger.debug("Workflow task processed
|
49
|
+
Temporal.logger.debug("Workflow task processed", metadata.to_h.merge(execution_time: time_diff_ms))
|
48
50
|
end
|
49
51
|
|
50
52
|
private
|
51
53
|
|
52
|
-
attr_reader :task, :namespace, :task_token, :workflow_name, :workflow_class, :client, :middleware_chain
|
54
|
+
attr_reader :task, :namespace, :task_token, :workflow_name, :workflow_class, :client, :middleware_chain, :metadata
|
53
55
|
|
54
56
|
def queue_time_ms
|
55
57
|
scheduled = task.scheduled_time.to_f
|
@@ -57,21 +59,49 @@ module Temporal
|
|
57
59
|
((started - scheduled) * 1_000).round
|
58
60
|
end
|
59
61
|
|
62
|
+
def fetch_full_history
|
63
|
+
events = task.history.events.to_a
|
64
|
+
next_page_token = task.next_page_token
|
65
|
+
|
66
|
+
while !next_page_token.empty? do
|
67
|
+
response = client.get_workflow_execution_history(
|
68
|
+
namespace: namespace,
|
69
|
+
workflow_id: task.workflow_execution.workflow_id,
|
70
|
+
run_id: task.workflow_execution.run_id,
|
71
|
+
next_page_token: next_page_token
|
72
|
+
)
|
73
|
+
|
74
|
+
events += response.history.events.to_a
|
75
|
+
next_page_token = response.next_page_token
|
76
|
+
end
|
77
|
+
|
78
|
+
Workflow::History.new(events)
|
79
|
+
end
|
80
|
+
|
60
81
|
def complete_task(commands)
|
61
|
-
Temporal.logger.info("Workflow task
|
82
|
+
Temporal.logger.info("Workflow task completed", metadata.to_h)
|
62
83
|
|
63
84
|
client.respond_workflow_task_completed(task_token: task_token, commands: commands)
|
64
85
|
end
|
65
86
|
|
66
87
|
def fail_task(error)
|
67
|
-
Temporal.logger.error("Workflow task
|
88
|
+
Temporal.logger.error("Workflow task failed", metadata.to_h.merge(error: error.inspect))
|
68
89
|
Temporal.logger.debug(error.backtrace.join("\n"))
|
69
90
|
|
91
|
+
# Only fail the workflow task on the first attempt. Subsequent failures of the same workflow task
|
92
|
+
# should timeout. This is to avoid spinning on the failed workflow task as the service doesn't
|
93
|
+
# yet exponentially backoff on retries.
|
94
|
+
return if task.attempt > MAX_FAILED_ATTEMPTS
|
95
|
+
|
70
96
|
client.respond_workflow_task_failed(
|
71
97
|
task_token: task_token,
|
72
98
|
cause: Temporal::Api::Enums::V1::WorkflowTaskFailedCause::WORKFLOW_TASK_FAILED_CAUSE_UNHANDLED_COMMAND,
|
73
99
|
exception: error
|
74
100
|
)
|
101
|
+
rescue StandardError => error
|
102
|
+
Temporal.logger.error("Unable to fail Workflow task", metadata.to_h.merge(error: error.inspect))
|
103
|
+
|
104
|
+
Temporal::ErrorHandler.handle(error, metadata: metadata)
|
75
105
|
end
|
76
106
|
end
|
77
107
|
end
|