temporalio 0.3.0 → 0.4.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 +4 -4
- data/Cargo.lock +503 -395
- data/Gemfile +4 -0
- data/README.md +183 -10
- data/Rakefile +1 -1
- data/ext/Cargo.toml +1 -0
- data/lib/temporalio/activity/context.rb +13 -0
- data/lib/temporalio/activity/definition.rb +22 -5
- data/lib/temporalio/activity/info.rb +3 -0
- data/lib/temporalio/api/batch/v1/message.rb +6 -1
- data/lib/temporalio/api/command/v1/message.rb +1 -1
- data/lib/temporalio/api/common/v1/message.rb +2 -1
- data/lib/temporalio/api/deployment/v1/message.rb +38 -0
- data/lib/temporalio/api/enums/v1/batch_operation.rb +1 -1
- data/lib/temporalio/api/enums/v1/common.rb +1 -1
- data/lib/temporalio/api/enums/v1/deployment.rb +23 -0
- data/lib/temporalio/api/enums/v1/event_type.rb +1 -1
- data/lib/temporalio/api/enums/v1/failed_cause.rb +1 -1
- data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
- data/lib/temporalio/api/enums/v1/reset.rb +1 -1
- data/lib/temporalio/api/enums/v1/workflow.rb +2 -1
- data/lib/temporalio/api/errordetails/v1/message.rb +3 -1
- data/lib/temporalio/api/failure/v1/message.rb +3 -1
- data/lib/temporalio/api/history/v1/message.rb +3 -1
- data/lib/temporalio/api/nexus/v1/message.rb +2 -1
- data/lib/temporalio/api/payload_visitor.rb +75 -7
- data/lib/temporalio/api/query/v1/message.rb +2 -1
- data/lib/temporalio/api/taskqueue/v1/message.rb +4 -1
- data/lib/temporalio/api/workflow/v1/message.rb +9 -1
- data/lib/temporalio/api/workflowservice/v1/request_response.rb +40 -11
- data/lib/temporalio/api/workflowservice/v1/service.rb +1 -1
- data/lib/temporalio/api.rb +1 -0
- data/lib/temporalio/client/connection/workflow_service.rb +238 -28
- data/lib/temporalio/client/interceptor.rb +39 -0
- data/lib/temporalio/client/schedule.rb +25 -1
- data/lib/temporalio/client/with_start_workflow_operation.rb +115 -0
- data/lib/temporalio/client/workflow_execution.rb +19 -0
- data/lib/temporalio/client/workflow_handle.rb +3 -3
- data/lib/temporalio/client.rb +125 -2
- data/lib/temporalio/contrib/open_telemetry.rb +470 -0
- data/lib/temporalio/error.rb +1 -0
- data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +1 -1
- data/lib/temporalio/internal/bridge/api/common/common.rb +2 -1
- data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +1 -1
- data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +1 -1
- data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +2 -1
- data/lib/temporalio/internal/bridge/runtime.rb +3 -0
- data/lib/temporalio/internal/bridge/testing.rb +3 -0
- data/lib/temporalio/internal/client/implementation.rb +232 -10
- data/lib/temporalio/internal/proto_utils.rb +34 -2
- data/lib/temporalio/internal/worker/activity_worker.rb +20 -8
- data/lib/temporalio/internal/worker/multi_runner.rb +2 -2
- data/lib/temporalio/internal/worker/workflow_instance/context.rb +57 -3
- data/lib/temporalio/internal/worker/workflow_instance/details.rb +4 -2
- data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +11 -26
- data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +22 -2
- data/lib/temporalio/internal/worker/workflow_instance.rb +76 -32
- data/lib/temporalio/internal/worker/workflow_worker.rb +62 -19
- data/lib/temporalio/runtime/metric_buffer.rb +94 -0
- data/lib/temporalio/runtime.rb +48 -10
- data/lib/temporalio/search_attributes.rb +13 -0
- data/lib/temporalio/testing/activity_environment.rb +42 -14
- data/lib/temporalio/testing/workflow_environment.rb +26 -3
- data/lib/temporalio/version.rb +1 -1
- data/lib/temporalio/worker/interceptor.rb +3 -0
- data/lib/temporalio/worker/thread_pool.rb +5 -5
- data/lib/temporalio/worker/tuner.rb +38 -0
- data/lib/temporalio/worker/workflow_executor/thread_pool.rb +13 -8
- data/lib/temporalio/worker/workflow_executor.rb +1 -1
- data/lib/temporalio/worker/workflow_replayer.rb +350 -0
- data/lib/temporalio/worker.rb +58 -52
- data/lib/temporalio/workflow/definition.rb +40 -8
- data/lib/temporalio/workflow/future.rb +2 -2
- data/lib/temporalio/workflow/info.rb +22 -0
- data/lib/temporalio/workflow.rb +60 -8
- data/lib/temporalio/workflow_history.rb +26 -1
- data/temporalio.gemspec +2 -1
- metadata +26 -5
data/lib/temporalio/client.rb
CHANGED
@@ -8,10 +8,13 @@ require 'temporalio/client/connection'
|
|
8
8
|
require 'temporalio/client/interceptor'
|
9
9
|
require 'temporalio/client/schedule'
|
10
10
|
require 'temporalio/client/schedule_handle'
|
11
|
+
require 'temporalio/client/with_start_workflow_operation'
|
11
12
|
require 'temporalio/client/workflow_execution'
|
12
13
|
require 'temporalio/client/workflow_execution_count'
|
13
14
|
require 'temporalio/client/workflow_handle'
|
14
15
|
require 'temporalio/client/workflow_query_reject_condition'
|
16
|
+
require 'temporalio/client/workflow_update_handle'
|
17
|
+
require 'temporalio/client/workflow_update_wait_stage'
|
15
18
|
require 'temporalio/common_enums'
|
16
19
|
require 'temporalio/converters'
|
17
20
|
require 'temporalio/error'
|
@@ -155,7 +158,7 @@ module Temporalio
|
|
155
158
|
default_workflow_query_reject_condition:
|
156
159
|
).freeze
|
157
160
|
# Initialize interceptors
|
158
|
-
@impl = interceptors.reverse_each.reduce(Internal::Client::Implementation.new(self)) do |acc, int|
|
161
|
+
@impl = interceptors.reverse_each.reduce(Internal::Client::Implementation.new(self)) do |acc, int| # steep:ignore
|
159
162
|
int.intercept_client(acc)
|
160
163
|
end
|
161
164
|
end
|
@@ -191,6 +194,12 @@ module Temporalio
|
|
191
194
|
# @param args [Array<Object>] Arguments to the workflow.
|
192
195
|
# @param id [String] Unique identifier for the workflow execution.
|
193
196
|
# @param task_queue [String] Task queue to run the workflow on.
|
197
|
+
# @param static_summary [String, nil] Fixed single-line summary for this workflow execution that may appear in
|
198
|
+
# CLI/UI. This can be in single-line Temporal markdown format. This is currently experimental.
|
199
|
+
# @param static_details [String, nil] Fixed details for this workflow execution that may appear in CLI/UI. This can
|
200
|
+
# be in Temporal markdown format and can be multiple lines. This is a fixed value on the workflow that cannot be
|
201
|
+
# updated. For details that can be updated, use {Workflow.current_details=} within the workflow. This is currently
|
202
|
+
# experimental.
|
194
203
|
# @param execution_timeout [Float, nil] Total workflow execution timeout in seconds including retries and continue
|
195
204
|
# as new.
|
196
205
|
# @param run_timeout [Float, nil] Timeout of a single workflow run in seconds.
|
@@ -217,6 +226,8 @@ module Temporalio
|
|
217
226
|
*args,
|
218
227
|
id:,
|
219
228
|
task_queue:,
|
229
|
+
static_summary: nil,
|
230
|
+
static_details: nil,
|
220
231
|
execution_timeout: nil,
|
221
232
|
run_timeout: nil,
|
222
233
|
task_timeout: nil,
|
@@ -231,10 +242,12 @@ module Temporalio
|
|
231
242
|
rpc_options: nil
|
232
243
|
)
|
233
244
|
@impl.start_workflow(Interceptor::StartWorkflowInput.new(
|
234
|
-
workflow
|
245
|
+
workflow: Workflow::Definition._workflow_type_from_workflow_parameter(workflow),
|
235
246
|
args:,
|
236
247
|
workflow_id: id,
|
237
248
|
task_queue:,
|
249
|
+
static_summary:,
|
250
|
+
static_details:,
|
238
251
|
execution_timeout:,
|
239
252
|
run_timeout:,
|
240
253
|
task_timeout:,
|
@@ -257,6 +270,12 @@ module Temporalio
|
|
257
270
|
# @param args [Array<Object>] Arguments to the workflow.
|
258
271
|
# @param id [String] Unique identifier for the workflow execution.
|
259
272
|
# @param task_queue [String] Task queue to run the workflow on.
|
273
|
+
# @param static_summary [String, nil] Fixed single-line summary for this workflow execution that may appear in
|
274
|
+
# CLI/UI. This can be in single-line Temporal markdown format. This is currently experimental.
|
275
|
+
# @param static_details [String, nil] Fixed details for this workflow execution that may appear in CLI/UI. This can
|
276
|
+
# be in Temporal markdown format and can be multiple lines. This is a fixed value on the workflow that cannot be
|
277
|
+
# updated. For details that can be updated, use {Workflow.current_details=} within the workflow. This is currently
|
278
|
+
# experimental.
|
260
279
|
# @param execution_timeout [Float, nil] Total workflow execution timeout in seconds including retries and continue
|
261
280
|
# as new.
|
262
281
|
# @param run_timeout [Float, nil] Timeout of a single workflow run in seconds.
|
@@ -284,6 +303,8 @@ module Temporalio
|
|
284
303
|
*args,
|
285
304
|
id:,
|
286
305
|
task_queue:,
|
306
|
+
static_summary: nil,
|
307
|
+
static_details: nil,
|
287
308
|
execution_timeout: nil,
|
288
309
|
run_timeout: nil,
|
289
310
|
task_timeout: nil,
|
@@ -302,6 +323,8 @@ module Temporalio
|
|
302
323
|
*args,
|
303
324
|
id:,
|
304
325
|
task_queue:,
|
326
|
+
static_summary:,
|
327
|
+
static_details:,
|
305
328
|
execution_timeout:,
|
306
329
|
run_timeout:,
|
307
330
|
task_timeout:,
|
@@ -334,6 +357,106 @@ module Temporalio
|
|
334
357
|
WorkflowHandle.new(client: self, id: workflow_id, run_id:, result_run_id: run_id, first_execution_run_id:)
|
335
358
|
end
|
336
359
|
|
360
|
+
# Start an update, possibly starting the workflow at the same time if it doesn't exist (depending upon ID conflict
|
361
|
+
# policy). Note that in some cases this may fail but the workflow will still be started, and the handle can then be
|
362
|
+
# retrieved on the start workflow operation.
|
363
|
+
#
|
364
|
+
# @param update [Workflow::Definition::Update, Symbol, String] Update definition or name.
|
365
|
+
# @param args [Array<Object>] Update arguments.
|
366
|
+
# @param start_workflow_operation [WithStartWorkflowOperation] Required with-start workflow operation. This must
|
367
|
+
# have an `id_conflict_policy` set.
|
368
|
+
# @param wait_for_stage [WorkflowUpdateWaitStage] Required stage to wait until returning. ADMITTED is not
|
369
|
+
# currently supported. See https://docs.temporal.io/workflows#update for more details.
|
370
|
+
# @param id [String] ID of the update.
|
371
|
+
# @param rpc_options [RPCOptions, nil] Advanced RPC options.
|
372
|
+
#
|
373
|
+
# @return [WorkflowUpdateHandle] The update handle.
|
374
|
+
# @raise [Error::WorkflowAlreadyStartedError] Workflow already exists and conflict/reuse policy does not allow.
|
375
|
+
# @raise [Error::WorkflowUpdateRPCTimeoutOrCanceledError] This update call timed out or was canceled. This doesn't
|
376
|
+
# mean the update itself was timed out or canceled, and this doesn't mean the workflow did not start.
|
377
|
+
# @raise [Error::RPCError] RPC error from call.
|
378
|
+
def start_update_with_start_workflow(
|
379
|
+
update,
|
380
|
+
*args,
|
381
|
+
start_workflow_operation:,
|
382
|
+
wait_for_stage:,
|
383
|
+
id: SecureRandom.uuid,
|
384
|
+
rpc_options: nil
|
385
|
+
)
|
386
|
+
@impl.start_update_with_start_workflow(
|
387
|
+
Interceptor::StartUpdateWithStartWorkflowInput.new(
|
388
|
+
update_id: id,
|
389
|
+
update: Workflow::Definition::Update._name_from_parameter(update),
|
390
|
+
args:,
|
391
|
+
wait_for_stage:,
|
392
|
+
start_workflow_operation:,
|
393
|
+
headers: {},
|
394
|
+
rpc_options:
|
395
|
+
)
|
396
|
+
)
|
397
|
+
end
|
398
|
+
|
399
|
+
# Start an update, possibly starting the workflow at the same time if it doesn't exist (depending upon ID conflict
|
400
|
+
# policy), and wait for update result. This is a shortcut for {start_update_with_start_workflow} +
|
401
|
+
# {WorkflowUpdateHandle.result}.
|
402
|
+
#
|
403
|
+
# @param update [Workflow::Definition::Update, Symbol, String] Update definition or name.
|
404
|
+
# @param args [Array<Object>] Update arguments.
|
405
|
+
# @param start_workflow_operation [WithStartWorkflowOperation] Required with-start workflow operation. This must
|
406
|
+
# have an `id_conflict_policy` set.
|
407
|
+
# @param id [String] ID of the update.
|
408
|
+
# @param rpc_options [RPCOptions, nil] Advanced RPC options.
|
409
|
+
#
|
410
|
+
# @return [Object] Successful update result.
|
411
|
+
# @raise [Error::WorkflowUpdateFailedError] If the update failed.
|
412
|
+
# @raise [Error::WorkflowAlreadyStartedError] Workflow already exists and conflict/reuse policy does not allow.
|
413
|
+
# @raise [Error::WorkflowUpdateRPCTimeoutOrCanceledError] This update call timed out or was canceled. This doesn't
|
414
|
+
# mean the update itself was timed out or canceled, and this doesn't mean the workflow did not start.
|
415
|
+
# @raise [Error::RPCError] RPC error from call.
|
416
|
+
def execute_update_with_start_workflow(
|
417
|
+
update,
|
418
|
+
*args,
|
419
|
+
start_workflow_operation:,
|
420
|
+
id: SecureRandom.uuid,
|
421
|
+
rpc_options: nil
|
422
|
+
)
|
423
|
+
start_update_with_start_workflow(
|
424
|
+
update,
|
425
|
+
*args,
|
426
|
+
start_workflow_operation:,
|
427
|
+
wait_for_stage: WorkflowUpdateWaitStage::COMPLETED,
|
428
|
+
id:,
|
429
|
+
rpc_options:
|
430
|
+
).result
|
431
|
+
end
|
432
|
+
|
433
|
+
# Send a signal, possibly starting the workflow at the same time if it doesn't exist.
|
434
|
+
#
|
435
|
+
# @param signal [Workflow::Definition::Signal, Symbol, String] Signal definition or name.
|
436
|
+
# @param args [Array<Object>] Signal arguments.
|
437
|
+
# @param start_workflow_operation [WithStartWorkflowOperation] Required with-start workflow operation. This may not
|
438
|
+
# support all `id_conflict_policy` options.
|
439
|
+
# @param rpc_options [RPCOptions, nil] Advanced RPC options.
|
440
|
+
#
|
441
|
+
# @return [WorkflowHandle] A workflow handle to the workflow.
|
442
|
+
# @raise [Error::WorkflowAlreadyStartedError] Workflow already exists and conflict/reuse policy does not allow.
|
443
|
+
# @raise [Error::RPCError] RPC error from call.
|
444
|
+
def signal_with_start_workflow(
|
445
|
+
signal,
|
446
|
+
*args,
|
447
|
+
start_workflow_operation:,
|
448
|
+
rpc_options: nil
|
449
|
+
)
|
450
|
+
@impl.signal_with_start_workflow(
|
451
|
+
Interceptor::SignalWithStartWorkflowInput.new(
|
452
|
+
signal: Workflow::Definition::Signal._name_from_parameter(signal),
|
453
|
+
args:,
|
454
|
+
start_workflow_operation:,
|
455
|
+
rpc_options:
|
456
|
+
)
|
457
|
+
)
|
458
|
+
end
|
459
|
+
|
337
460
|
# List workflows.
|
338
461
|
#
|
339
462
|
# @param query [String, nil] A Temporal visibility list filter.
|
@@ -0,0 +1,470 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
require 'opentelemetry' # This import will intentionally fail if the user does not have OTel gem available
|
5
|
+
require 'temporalio/client/interceptor'
|
6
|
+
require 'temporalio/converters/payload_converter'
|
7
|
+
require 'temporalio/worker/interceptor'
|
8
|
+
|
9
|
+
module Temporalio
|
10
|
+
module Contrib
|
11
|
+
module OpenTelemetry
|
12
|
+
# Tracing interceptor to add OpenTelemetry traces to clients, activities, and workflows.
|
13
|
+
class TracingInterceptor
|
14
|
+
include Client::Interceptor
|
15
|
+
include Worker::Interceptor::Activity
|
16
|
+
include Worker::Interceptor::Workflow
|
17
|
+
|
18
|
+
# @return [OpenTelemetry::Trace::Tracer] Tracer in use.
|
19
|
+
attr_reader :tracer
|
20
|
+
|
21
|
+
# Create interceptor.
|
22
|
+
#
|
23
|
+
# @param tracer [OpenTelemetry::Trace::Tracer] Tracer to use.
|
24
|
+
# @param header_key [String] Temporal header name to serialize spans to/from. Most users should not change this.
|
25
|
+
# @param propagator [Object] Propagator to use. Most users should not change this.
|
26
|
+
# @param always_create_workflow_spans [Boolean] When false, the default, spans are only created in workflows
|
27
|
+
# when an overarching span from the client is present. In cases of starting a workflow elsewhere, e.g. CLI or
|
28
|
+
# schedules, a client-created span is not present and workflow spans will not be created. Setting this to true
|
29
|
+
# will create spans in workflows no matter what, but there is a risk of them being orphans since they may not
|
30
|
+
# have a parent span after replaying.
|
31
|
+
def initialize(
|
32
|
+
tracer,
|
33
|
+
header_key: '_tracer-data',
|
34
|
+
propagator: ::OpenTelemetry::Context::Propagation::CompositeTextMapPropagator.compose_propagators(
|
35
|
+
[
|
36
|
+
::OpenTelemetry::Trace::Propagation::TraceContext::TextMapPropagator.new,
|
37
|
+
::OpenTelemetry::Baggage::Propagation::TextMapPropagator.new
|
38
|
+
]
|
39
|
+
),
|
40
|
+
always_create_workflow_spans: false
|
41
|
+
)
|
42
|
+
@tracer = tracer
|
43
|
+
@header_key = header_key
|
44
|
+
@propagator = propagator
|
45
|
+
@always_create_workflow_spans = always_create_workflow_spans
|
46
|
+
end
|
47
|
+
|
48
|
+
# @!visibility private
|
49
|
+
def intercept_client(next_interceptor)
|
50
|
+
ClientOutbound.new(self, next_interceptor)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!visibility private
|
54
|
+
def intercept_activity(next_interceptor)
|
55
|
+
ActivityInbound.new(self, next_interceptor)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @!visibility private
|
59
|
+
def intercept_workflow(next_interceptor)
|
60
|
+
WorkflowInbound.new(self, next_interceptor)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @!visibility private
|
64
|
+
def _apply_context_to_headers(headers, context: ::OpenTelemetry::Context.current)
|
65
|
+
carrier = {}
|
66
|
+
@propagator.inject(carrier, context:)
|
67
|
+
headers[@header_key] = carrier unless carrier.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
# @!visibility private
|
71
|
+
def _attach_context(headers)
|
72
|
+
context = _context_from_headers(headers)
|
73
|
+
::OpenTelemetry::Context.attach(context) if context
|
74
|
+
end
|
75
|
+
|
76
|
+
# @!visibility private
|
77
|
+
def _context_from_headers(headers)
|
78
|
+
carrier = headers[@header_key]
|
79
|
+
@propagator.extract(carrier) if carrier.is_a?(Hash) && !carrier.empty?
|
80
|
+
end
|
81
|
+
|
82
|
+
# @!visibility private
|
83
|
+
def _with_started_span(
|
84
|
+
name:,
|
85
|
+
kind:,
|
86
|
+
attributes: nil,
|
87
|
+
outbound_input: nil
|
88
|
+
)
|
89
|
+
tracer.in_span(name, attributes:, kind:) do
|
90
|
+
_apply_context_to_headers(outbound_input.headers) if outbound_input
|
91
|
+
yield
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# @!visibility private
|
96
|
+
def _always_create_workflow_spans
|
97
|
+
@always_create_workflow_spans
|
98
|
+
end
|
99
|
+
|
100
|
+
# @!visibility private
|
101
|
+
class ClientOutbound < Client::Interceptor::Outbound
|
102
|
+
def initialize(root, next_interceptor)
|
103
|
+
super(next_interceptor)
|
104
|
+
@root = root
|
105
|
+
end
|
106
|
+
|
107
|
+
# @!visibility private
|
108
|
+
def start_workflow(input)
|
109
|
+
@root._with_started_span(
|
110
|
+
name: "StartWorkflow:#{input.workflow}",
|
111
|
+
kind: :client,
|
112
|
+
attributes: { 'temporalWorkflowID' => input.workflow_id },
|
113
|
+
outbound_input: input
|
114
|
+
) { super }
|
115
|
+
end
|
116
|
+
|
117
|
+
# @!visibility private
|
118
|
+
def start_update_with_start_workflow(input)
|
119
|
+
@root._with_started_span(
|
120
|
+
name: "UpdateWithStartWorkflow:#{input.update}",
|
121
|
+
kind: :client,
|
122
|
+
attributes: { 'temporalWorkflowID' => input.start_workflow_operation.options.id,
|
123
|
+
'temporalUpdateID' => input.update_id },
|
124
|
+
outbound_input: input
|
125
|
+
) do
|
126
|
+
# Also add to start headers
|
127
|
+
if input.headers[@header_key]
|
128
|
+
input.start_workflow_operation.options.headers[@header_key] = input.headers[@header_key]
|
129
|
+
end
|
130
|
+
super
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# @!visibility private
|
135
|
+
def signal_with_start_workflow(input)
|
136
|
+
@root._with_started_span(
|
137
|
+
name: "SignalWithStartWorkflow:#{input.workflow}",
|
138
|
+
kind: :client,
|
139
|
+
attributes: { 'temporalWorkflowID' => input.start_workflow_operation.options.id },
|
140
|
+
outbound_input: input
|
141
|
+
) do
|
142
|
+
# Also add to start headers
|
143
|
+
if input.headers[@header_key]
|
144
|
+
input.start_workflow_operation.options.headers[@header_key] = input.headers[@header_key]
|
145
|
+
end
|
146
|
+
super
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# @!visibility private
|
151
|
+
def signal_workflow(input)
|
152
|
+
@root._with_started_span(
|
153
|
+
name: "SignalWorkflow:#{input.signal}",
|
154
|
+
kind: :client,
|
155
|
+
attributes: { 'temporalWorkflowID' => input.workflow_id },
|
156
|
+
outbound_input: input
|
157
|
+
) { super }
|
158
|
+
end
|
159
|
+
|
160
|
+
# @!visibility private
|
161
|
+
def query_workflow(input)
|
162
|
+
@root._with_started_span(
|
163
|
+
name: "QueryWorkflow:#{input.query}",
|
164
|
+
kind: :client,
|
165
|
+
attributes: { 'temporalWorkflowID' => input.workflow_id },
|
166
|
+
outbound_input: input
|
167
|
+
) { super }
|
168
|
+
end
|
169
|
+
|
170
|
+
# @!visibility private
|
171
|
+
def start_workflow_update(input)
|
172
|
+
@root._with_started_span(
|
173
|
+
name: "StartWorkflowUpdate:#{input.update}",
|
174
|
+
kind: :client,
|
175
|
+
attributes: { 'temporalWorkflowID' => input.workflow_id, 'temporalUpdateID' => input.update_id },
|
176
|
+
outbound_input: input
|
177
|
+
) { super }
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# @!visibility private
|
182
|
+
class ActivityInbound < Worker::Interceptor::Activity::Inbound
|
183
|
+
def initialize(root, next_interceptor)
|
184
|
+
super(next_interceptor)
|
185
|
+
@root = root
|
186
|
+
end
|
187
|
+
|
188
|
+
# @!visibility private
|
189
|
+
def execute(input)
|
190
|
+
@root._attach_context(input.headers)
|
191
|
+
info = Activity::Context.current.info
|
192
|
+
@root._with_started_span(
|
193
|
+
name: "RunActivity:#{info.activity_type}",
|
194
|
+
kind: :server,
|
195
|
+
attributes: {
|
196
|
+
'temporalWorkflowID' => info.workflow_id,
|
197
|
+
'temporalRunID' => info.workflow_run_id,
|
198
|
+
'temporalActivityID' => info.activity_id
|
199
|
+
}
|
200
|
+
) { super }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# @!visibility private
|
205
|
+
class WorkflowInbound < Worker::Interceptor::Workflow::Inbound
|
206
|
+
def initialize(root, next_interceptor)
|
207
|
+
super(next_interceptor)
|
208
|
+
@root = root
|
209
|
+
end
|
210
|
+
|
211
|
+
# @!visibility private
|
212
|
+
def init(outbound)
|
213
|
+
# Set root on storage
|
214
|
+
Temporalio::Workflow.storage[:__temporal_opentelemetry_tracing_interceptor] = @root
|
215
|
+
super(WorkflowOutbound.new(@root, outbound))
|
216
|
+
end
|
217
|
+
|
218
|
+
# @!visibility private
|
219
|
+
def execute(input)
|
220
|
+
@root._attach_context(Temporalio::Workflow.info.headers)
|
221
|
+
Workflow.with_completed_span("RunWorkflow:#{Temporalio::Workflow.info.workflow_type}", kind: :server) do
|
222
|
+
super
|
223
|
+
ensure
|
224
|
+
Workflow.completed_span(
|
225
|
+
"CompleteWorkflow:#{Temporalio::Workflow.info.workflow_type}",
|
226
|
+
kind: :internal,
|
227
|
+
exception: $ERROR_INFO # steep:ignore
|
228
|
+
)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# @!visibility private
|
233
|
+
def handle_signal(input)
|
234
|
+
@root._attach_context(Temporalio::Workflow.info.headers)
|
235
|
+
Workflow.with_completed_span(
|
236
|
+
"HandleSignal:#{input.signal}",
|
237
|
+
links: _links_from_headers(input.headers),
|
238
|
+
kind: :server
|
239
|
+
) do
|
240
|
+
super
|
241
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
242
|
+
Workflow.completed_span("FailHandleSignal:#{input.signal}", kind: :internal, exception: e)
|
243
|
+
raise
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# @!visibility private
|
248
|
+
def handle_query(input)
|
249
|
+
@root._attach_context(Temporalio::Workflow.info.headers)
|
250
|
+
Workflow.with_completed_span(
|
251
|
+
"HandleQuery:#{input.query}",
|
252
|
+
links: _links_from_headers(input.headers),
|
253
|
+
kind: :server,
|
254
|
+
even_during_replay: true
|
255
|
+
) do
|
256
|
+
super
|
257
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
258
|
+
Workflow.completed_span(
|
259
|
+
"FailHandleQuery:#{input.query}",
|
260
|
+
kind: :internal,
|
261
|
+
exception: e,
|
262
|
+
even_during_replay: true
|
263
|
+
)
|
264
|
+
raise
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# @!visibility private
|
269
|
+
def validate_update(input)
|
270
|
+
@root._attach_context(Temporalio::Workflow.info.headers)
|
271
|
+
Workflow.with_completed_span(
|
272
|
+
"ValidateUpdate:#{input.update}",
|
273
|
+
attributes: { 'temporalUpdateID' => input.id },
|
274
|
+
links: _links_from_headers(input.headers),
|
275
|
+
kind: :server,
|
276
|
+
even_during_replay: true
|
277
|
+
) do
|
278
|
+
super
|
279
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
280
|
+
Workflow.completed_span(
|
281
|
+
"FailValidateUpdate:#{input.update}",
|
282
|
+
attributes: { 'temporalUpdateID' => input.id },
|
283
|
+
kind: :internal,
|
284
|
+
exception: e,
|
285
|
+
even_during_replay: true
|
286
|
+
)
|
287
|
+
raise
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# @!visibility private
|
292
|
+
def handle_update(input)
|
293
|
+
@root._attach_context(Temporalio::Workflow.info.headers)
|
294
|
+
Workflow.with_completed_span(
|
295
|
+
"HandleUpdate:#{input.update}",
|
296
|
+
attributes: { 'temporalUpdateID' => input.id },
|
297
|
+
links: _links_from_headers(input.headers),
|
298
|
+
kind: :server
|
299
|
+
) do
|
300
|
+
super
|
301
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
302
|
+
Workflow.completed_span(
|
303
|
+
"FailHandleUpdate:#{input.update}",
|
304
|
+
attributes: { 'temporalUpdateID' => input.id },
|
305
|
+
kind: :internal,
|
306
|
+
exception: e
|
307
|
+
)
|
308
|
+
raise
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# @!visibility private
|
313
|
+
def _links_from_headers(headers)
|
314
|
+
context = @root._context_from_headers(headers)
|
315
|
+
span = ::OpenTelemetry::Trace.current_span(context) if context
|
316
|
+
if span && span != ::OpenTelemetry::Trace::Span::INVALID
|
317
|
+
[::OpenTelemetry::Trace::Link.new(span.context)]
|
318
|
+
else
|
319
|
+
[]
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# @!visibility private
|
325
|
+
class WorkflowOutbound < Worker::Interceptor::Workflow::Outbound
|
326
|
+
def initialize(root, next_interceptor)
|
327
|
+
super(next_interceptor)
|
328
|
+
@root = root
|
329
|
+
end
|
330
|
+
|
331
|
+
# @!visibility private
|
332
|
+
def execute_activity(input)
|
333
|
+
_apply_span_to_headers(input.headers,
|
334
|
+
Workflow.completed_span("StartActivity:#{input.activity}", kind: :client))
|
335
|
+
super
|
336
|
+
end
|
337
|
+
|
338
|
+
# @!visibility private
|
339
|
+
def execute_local_activity(input)
|
340
|
+
_apply_span_to_headers(input.headers,
|
341
|
+
Workflow.completed_span("StartActivity:#{input.activity}", kind: :client))
|
342
|
+
super
|
343
|
+
end
|
344
|
+
|
345
|
+
# @!visibility private
|
346
|
+
def initialize_continue_as_new_error(input)
|
347
|
+
# Just apply the current context to headers
|
348
|
+
@root._apply_context_to_headers(input.error.headers)
|
349
|
+
super
|
350
|
+
end
|
351
|
+
|
352
|
+
# @!visibility private
|
353
|
+
def signal_child_workflow(input)
|
354
|
+
_apply_span_to_headers(input.headers,
|
355
|
+
Workflow.completed_span("SignalChildWorkflow:#{input.signal}", kind: :client))
|
356
|
+
super
|
357
|
+
end
|
358
|
+
|
359
|
+
# @!visibility private
|
360
|
+
def signal_external_workflow(input)
|
361
|
+
_apply_span_to_headers(input.headers,
|
362
|
+
Workflow.completed_span("SignalExternalWorkflow:#{input.signal}", kind: :client))
|
363
|
+
super
|
364
|
+
end
|
365
|
+
|
366
|
+
# @!visibility private
|
367
|
+
def start_child_workflow(input)
|
368
|
+
_apply_span_to_headers(input.headers,
|
369
|
+
Workflow.completed_span("StartChildWorkflow:#{input.workflow}", kind: :client))
|
370
|
+
super
|
371
|
+
end
|
372
|
+
|
373
|
+
# @!visibility private
|
374
|
+
def _apply_span_to_headers(headers, span)
|
375
|
+
@root._apply_context_to_headers(headers, context: ::OpenTelemetry::Trace.context_with_span(span)) if span
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
private_constant :ClientOutbound
|
380
|
+
private_constant :ActivityInbound
|
381
|
+
private_constant :WorkflowInbound
|
382
|
+
private_constant :WorkflowOutbound
|
383
|
+
end
|
384
|
+
|
385
|
+
# Contains workflow methods that can be used for OpenTelemetry.
|
386
|
+
module Workflow
|
387
|
+
# Create a completed span and execute block with the span set on the context.
|
388
|
+
#
|
389
|
+
# @param name [String] Span name.
|
390
|
+
# @param attributes [Hash] Span attributes. These will have workflow and run ID automatically added.
|
391
|
+
# @param links [Array, nil] Span links.
|
392
|
+
# @param kind [Symbol, nil] Span kind.
|
393
|
+
# @param exception [Exception, nil] Exception to record on the span.
|
394
|
+
# @param even_during_replay [Boolean] Set to true to record this span even during replay. Most users should
|
395
|
+
# never set this.
|
396
|
+
# @yield Block to call. It is UNSAFE to expect any parameters in this block.
|
397
|
+
# @return [Object] Result of the block.
|
398
|
+
def self.with_completed_span(
|
399
|
+
name,
|
400
|
+
attributes: {},
|
401
|
+
links: nil,
|
402
|
+
kind: nil,
|
403
|
+
exception: nil,
|
404
|
+
even_during_replay: false
|
405
|
+
)
|
406
|
+
span = completed_span(name, attributes:, links:, kind:, exception:, even_during_replay:)
|
407
|
+
if span
|
408
|
+
::OpenTelemetry::Trace.with_span(span) do # rubocop:disable Style/ExplicitBlockArgument
|
409
|
+
# Yield with no parameters
|
410
|
+
yield
|
411
|
+
end
|
412
|
+
else
|
413
|
+
yield
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# Create a completed span only if not replaying (or `even_during_replay` is true).
|
418
|
+
#
|
419
|
+
# @note WARNING: It is UNSAFE to rely on the result of this method as it may be different/absent on replay.
|
420
|
+
#
|
421
|
+
# @param name [String] Span name.
|
422
|
+
# @param attributes [Hash] Span attributes. These will have workflow and run ID automatically added.
|
423
|
+
# @param links [Array, nil] Span links.
|
424
|
+
# @param kind [Symbol, nil] Span kind.
|
425
|
+
# @param exception [Exception, nil] Exception to record on the span.
|
426
|
+
# @param even_during_replay [Boolean] Set to true to record this span even during replay. Most users should
|
427
|
+
# never set this.
|
428
|
+
# @return [OpenTelemetry::Trace::Span, nil] Span if one was created. WARNING: It is UNSAFE to use this value.
|
429
|
+
def self.completed_span(
|
430
|
+
name,
|
431
|
+
attributes: {},
|
432
|
+
links: nil,
|
433
|
+
kind: nil,
|
434
|
+
exception: nil,
|
435
|
+
even_during_replay: false
|
436
|
+
)
|
437
|
+
# Get root interceptor, which also checks if in workflow
|
438
|
+
root = Temporalio::Workflow.storage[:__temporal_opentelemetry_tracing_interceptor] #: TracingInterceptor?
|
439
|
+
raise 'Tracing interceptor not configured' unless root
|
440
|
+
|
441
|
+
# Do nothing if replaying and not wanted during replay
|
442
|
+
return nil if !even_during_replay && Temporalio::Workflow::Unsafe.replaying?
|
443
|
+
|
444
|
+
# If there is no span on the context and the user hasn't opted in to always creating, do not create. This
|
445
|
+
# prevents orphans if there was no span originally created from the client start-workflow call.
|
446
|
+
if ::OpenTelemetry::Trace.current_span == ::OpenTelemetry::Trace::Span::INVALID &&
|
447
|
+
!root._always_create_workflow_spans
|
448
|
+
return nil
|
449
|
+
end
|
450
|
+
|
451
|
+
# Create attributes, adding user-defined ones
|
452
|
+
attributes = { 'temporalWorkflowID' => Temporalio::Workflow.info.workflow_id,
|
453
|
+
'temporalRunID' => Temporalio::Workflow.info.run_id }.merge(attributes)
|
454
|
+
|
455
|
+
# Create span, which has to be done with illegal call disabling because OTel asks for full exception message
|
456
|
+
# which uses error highlighting and such which accesses File#path
|
457
|
+
Temporalio::Workflow::Unsafe.illegal_call_tracing_disabled do
|
458
|
+
time = Temporalio::Workflow.now
|
459
|
+
timestamp = (time.to_i * 1_000_000_000) + time.nsec
|
460
|
+
span = root.tracer.start_span(name, attributes:, links:, start_timestamp: timestamp, kind:) # steep:ignore
|
461
|
+
# Record exception if present
|
462
|
+
span.record_exception(exception) if exception
|
463
|
+
# Finish the span (returns self)
|
464
|
+
span.finish(end_timestamp: timestamp)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
data/lib/temporalio/error.rb
CHANGED
@@ -124,6 +124,7 @@ module Temporalio
|
|
124
124
|
|
125
125
|
def create_grpc_status
|
126
126
|
return Api::Common::V1::GrpcStatus.new(code: @code) unless @raw_grpc_status
|
127
|
+
return @raw_grpc_status if @raw_grpc_status.is_a?(Api::Common::V1::GrpcStatus)
|
127
128
|
|
128
129
|
Api::Common::V1::GrpcStatus.decode(@raw_grpc_status)
|
129
130
|
end
|
@@ -10,7 +10,7 @@ require 'temporalio/api/common/v1/message'
|
|
10
10
|
require 'temporalio/internal/bridge/api/common/common'
|
11
11
|
|
12
12
|
|
13
|
-
descriptor_data = "\n3temporal/sdk/core/activity_task/activity_task.proto\x12\x15\x63oresdk.activity_task\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$temporal/api/common/v1/message.proto\x1a%temporal/sdk/core/common/common.proto\"\x8d\x01\n\x0c\x41\x63tivityTask\x12\x12\n\ntask_token\x18\x01 \x01(\x0c\x12-\n\x05start\x18\x03 \x01(\x0b\x32\x1c.coresdk.activity_task.StartH\x00\x12/\n\x06\x63\x61ncel\x18\x04 \x01(\x0b\x32\x1d.coresdk.activity_task.CancelH\x00\x42\t\n\x07variant\"\
|
13
|
+
descriptor_data = "\n3temporal/sdk/core/activity_task/activity_task.proto\x12\x15\x63oresdk.activity_task\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$temporal/api/common/v1/message.proto\x1a%temporal/sdk/core/common/common.proto\"\x8d\x01\n\x0c\x41\x63tivityTask\x12\x12\n\ntask_token\x18\x01 \x01(\x0c\x12-\n\x05start\x18\x03 \x01(\x0b\x32\x1c.coresdk.activity_task.StartH\x00\x12/\n\x06\x63\x61ncel\x18\x04 \x01(\x0b\x32\x1d.coresdk.activity_task.CancelH\x00\x42\t\n\x07variant\"\xa1\x07\n\x05Start\x12\x1a\n\x12workflow_namespace\x18\x01 \x01(\t\x12\x15\n\rworkflow_type\x18\x02 \x01(\t\x12\x45\n\x12workflow_execution\x18\x03 \x01(\x0b\x32).temporal.api.common.v1.WorkflowExecution\x12\x13\n\x0b\x61\x63tivity_id\x18\x04 \x01(\t\x12\x15\n\ractivity_type\x18\x05 \x01(\t\x12\x45\n\rheader_fields\x18\x06 \x03(\x0b\x32..coresdk.activity_task.Start.HeaderFieldsEntry\x12.\n\x05input\x18\x07 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12:\n\x11heartbeat_details\x18\x08 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12\x32\n\x0escheduled_time\x18\t \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x42\n\x1e\x63urrent_attempt_scheduled_time\x18\n \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x30\n\x0cstarted_time\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07\x61ttempt\x18\x0c \x01(\r\x12<\n\x19schedule_to_close_timeout\x18\r \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x39\n\x16start_to_close_timeout\x18\x0e \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x34\n\x11heartbeat_timeout\x18\x0f \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x39\n\x0cretry_policy\x18\x10 \x01(\x0b\x32#.temporal.api.common.v1.RetryPolicy\x12\x32\n\x08priority\x18\x12 \x01(\x0b\x32 .temporal.api.common.v1.Priority\x12\x10\n\x08is_local\x18\x11 \x01(\x08\x1aT\n\x11HeaderFieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01\"E\n\x06\x43\x61ncel\x12;\n\x06reason\x18\x01 \x01(\x0e\x32+.coresdk.activity_task.ActivityCancelReason*X\n\x14\x41\x63tivityCancelReason\x12\r\n\tNOT_FOUND\x10\x00\x12\r\n\tCANCELLED\x10\x01\x12\r\n\tTIMED_OUT\x10\x02\x12\x13\n\x0fWORKER_SHUTDOWN\x10\x03\x42\x32\xea\x02/Temporalio::Internal::Bridge::Api::ActivityTaskb\x06proto3"
|
14
14
|
|
15
15
|
pool = Google::Protobuf::DescriptorPool.generated_pool
|
16
16
|
pool.add_serialized_file(descriptor_data)
|