temporalio 0.3.0-arm64-darwin → 0.5.0-arm64-darwin
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/.yardopts +1 -1
- data/Gemfile +4 -0
- data/Rakefile +1 -1
- data/lib/temporalio/activity/cancellation_details.rb +58 -0
- data/lib/temporalio/activity/context.rb +23 -1
- data/lib/temporalio/activity/definition.rb +63 -8
- data/lib/temporalio/activity/info.rb +28 -4
- data/lib/temporalio/activity.rb +2 -0
- data/lib/temporalio/api/activity/v1/message.rb +1 -1
- data/lib/temporalio/api/batch/v1/message.rb +9 -2
- data/lib/temporalio/api/cloud/account/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +11 -2
- data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +2 -2
- data/lib/temporalio/api/cloud/identity/v1/message.rb +7 -2
- data/lib/temporalio/api/cloud/namespace/v1/message.rb +6 -2
- data/lib/temporalio/api/cloud/nexus/v1/message.rb +3 -2
- data/lib/temporalio/api/cloud/operation/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/region/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/resource/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/sink/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/usage/v1/message.rb +1 -1
- data/lib/temporalio/api/command/v1/message.rb +2 -2
- data/lib/temporalio/api/common/v1/grpc_status.rb +1 -1
- data/lib/temporalio/api/common/v1/message.rb +4 -2
- data/lib/temporalio/api/deployment/v1/message.rb +39 -0
- data/lib/temporalio/api/enums/v1/batch_operation.rb +2 -2
- data/lib/temporalio/api/enums/v1/command_type.rb +1 -1
- data/lib/temporalio/api/enums/v1/common.rb +5 -2
- data/lib/temporalio/api/enums/v1/deployment.rb +24 -0
- data/lib/temporalio/api/enums/v1/event_type.rb +2 -2
- data/lib/temporalio/api/enums/v1/failed_cause.rb +2 -2
- data/lib/temporalio/api/enums/v1/namespace.rb +1 -1
- data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
- data/lib/temporalio/api/enums/v1/query.rb +1 -1
- data/lib/temporalio/api/enums/v1/reset.rb +2 -2
- data/lib/temporalio/api/enums/v1/schedule.rb +1 -1
- data/lib/temporalio/api/enums/v1/task_queue.rb +1 -1
- data/lib/temporalio/api/enums/v1/update.rb +1 -1
- data/lib/temporalio/api/enums/v1/workflow.rb +3 -2
- data/lib/temporalio/api/errordetails/v1/message.rb +4 -2
- data/lib/temporalio/api/export/v1/message.rb +1 -1
- data/lib/temporalio/api/failure/v1/message.rb +5 -2
- data/lib/temporalio/api/filter/v1/message.rb +1 -1
- data/lib/temporalio/api/history/v1/message.rb +6 -2
- data/lib/temporalio/api/namespace/v1/message.rb +1 -1
- data/lib/temporalio/api/nexus/v1/message.rb +3 -2
- data/lib/temporalio/api/operatorservice/v1/request_response.rb +1 -1
- data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
- data/lib/temporalio/api/payload_visitor.rb +162 -7
- data/lib/temporalio/api/protocol/v1/message.rb +1 -1
- data/lib/temporalio/api/query/v1/message.rb +3 -2
- data/lib/temporalio/api/replication/v1/message.rb +1 -1
- data/lib/temporalio/api/rules/v1/message.rb +27 -0
- data/lib/temporalio/api/schedule/v1/message.rb +2 -2
- data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +1 -1
- data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +1 -1
- data/lib/temporalio/api/sdk/v1/user_metadata.rb +1 -1
- data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +1 -1
- data/lib/temporalio/api/taskqueue/v1/message.rb +5 -2
- data/lib/temporalio/api/testservice/v1/request_response.rb +1 -1
- data/lib/temporalio/api/testservice/v1/service.rb +1 -1
- data/lib/temporalio/api/update/v1/message.rb +1 -1
- data/lib/temporalio/api/version/v1/message.rb +1 -1
- data/lib/temporalio/api/worker/v1/message.rb +30 -0
- data/lib/temporalio/api/workflow/v1/message.rb +22 -2
- data/lib/temporalio/api/workflowservice/v1/request_response.rb +58 -12
- data/lib/temporalio/api/workflowservice/v1/service.rb +2 -2
- data/lib/temporalio/api.rb +1 -0
- data/lib/temporalio/client/async_activity_handle.rb +12 -4
- data/lib/temporalio/client/connection/cloud_service.rb +60 -0
- data/lib/temporalio/client/connection/workflow_service.rb +343 -28
- data/lib/temporalio/client/interceptor.rb +64 -7
- data/lib/temporalio/client/schedule.rb +35 -3
- data/lib/temporalio/client/with_start_workflow_operation.rb +123 -0
- data/lib/temporalio/client/workflow_execution.rb +19 -0
- data/lib/temporalio/client/workflow_handle.rb +47 -7
- data/lib/temporalio/client/workflow_update_handle.rb +9 -3
- data/lib/temporalio/client.rb +231 -4
- data/lib/temporalio/common_enums.rb +14 -0
- data/lib/temporalio/contrib/open_telemetry.rb +474 -0
- data/lib/temporalio/converters/data_converter.rb +18 -8
- data/lib/temporalio/converters/failure_converter.rb +6 -3
- data/lib/temporalio/converters/payload_converter/binary_null.rb +2 -2
- data/lib/temporalio/converters/payload_converter/binary_plain.rb +2 -2
- data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +2 -2
- data/lib/temporalio/converters/payload_converter/composite.rb +6 -4
- data/lib/temporalio/converters/payload_converter/encoding.rb +4 -2
- data/lib/temporalio/converters/payload_converter/json_plain.rb +2 -2
- data/lib/temporalio/converters/payload_converter/json_protobuf.rb +2 -2
- data/lib/temporalio/converters/payload_converter.rb +16 -6
- data/lib/temporalio/error/failure.rb +19 -1
- data/lib/temporalio/error.rb +2 -1
- data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/3.4/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +1 -1
- data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +3 -2
- data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +1 -1
- data/lib/temporalio/internal/bridge/api/common/common.rb +3 -2
- data/lib/temporalio/internal/bridge/api/core_interface.rb +1 -1
- data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +1 -1
- data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +3 -2
- data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +2 -2
- data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +3 -2
- data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +3 -2
- data/lib/temporalio/internal/bridge/runtime.rb +3 -0
- data/lib/temporalio/internal/bridge/testing.rb +3 -0
- data/lib/temporalio/internal/bridge/worker.rb +28 -4
- data/lib/temporalio/internal/bridge.rb +1 -1
- data/lib/temporalio/internal/client/implementation.rb +281 -51
- data/lib/temporalio/internal/proto_utils.rb +38 -6
- data/lib/temporalio/internal/worker/activity_worker.rb +112 -27
- data/lib/temporalio/internal/worker/multi_runner.rb +2 -2
- data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +8 -6
- data/lib/temporalio/internal/worker/workflow_instance/context.rb +100 -5
- data/lib/temporalio/internal/worker/workflow_instance/details.rb +7 -2
- data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +2 -2
- data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +64 -18
- data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +39 -40
- data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +22 -2
- data/lib/temporalio/internal/worker/workflow_instance.rb +134 -55
- data/lib/temporalio/internal/worker/workflow_worker.rb +74 -21
- data/lib/temporalio/priority.rb +59 -0
- 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 +59 -16
- data/lib/temporalio/testing/workflow_environment.rb +29 -6
- data/lib/temporalio/version.rb +1 -1
- data/lib/temporalio/versioning_override.rb +56 -0
- data/lib/temporalio/worker/deployment_options.rb +45 -0
- data/lib/temporalio/worker/illegal_workflow_call_validator.rb +64 -0
- data/lib/temporalio/worker/interceptor.rb +16 -1
- data/lib/temporalio/worker/poller_behavior.rb +61 -0
- data/lib/temporalio/worker/thread_pool.rb +6 -6
- data/lib/temporalio/worker/tuner.rb +38 -0
- data/lib/temporalio/worker/workflow_executor/thread_pool.rb +14 -8
- data/lib/temporalio/worker/workflow_executor.rb +1 -1
- data/lib/temporalio/worker/workflow_replayer.rb +349 -0
- data/lib/temporalio/worker.rb +117 -75
- data/lib/temporalio/worker_deployment_version.rb +67 -0
- data/lib/temporalio/workflow/child_workflow_handle.rb +10 -2
- data/lib/temporalio/workflow/definition.rb +217 -35
- data/lib/temporalio/workflow/external_workflow_handle.rb +3 -1
- data/lib/temporalio/workflow/future.rb +2 -2
- data/lib/temporalio/workflow/info.rb +26 -1
- data/lib/temporalio/workflow.rb +119 -15
- data/lib/temporalio/workflow_history.rb +26 -1
- data/lib/temporalio.rb +1 -0
- data/temporalio.gemspec +3 -1
- metadata +34 -4
@@ -0,0 +1,474 @@
|
|
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
|
+
time = Temporalio::Workflow.now.dup
|
456
|
+
# Disable illegal call tracing because OTel asks for full exception message which uses error highlighting and
|
457
|
+
# such which accesses File#path, and they also use loggers accessing current time
|
458
|
+
Temporalio::Workflow::Unsafe.illegal_call_tracing_disabled do
|
459
|
+
# Disable durable scheduler because 1) synchronous/non-batch span processors in OTel use network (though
|
460
|
+
# could have just used Unafe.io_enabled for this if not for the next point) and 2) OTel uses Ruby Timeout
|
461
|
+
# which we don't want to use durable timers.
|
462
|
+
Temporalio::Workflow::Unsafe.durable_scheduler_disabled do
|
463
|
+
span = root.tracer.start_span(name, attributes:, links:, start_timestamp: time, kind:) # steep:ignore
|
464
|
+
# Record exception if present
|
465
|
+
span.record_exception(exception) if exception
|
466
|
+
# Finish the span (returns self)
|
467
|
+
span.finish(end_timestamp: time)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
@@ -40,9 +40,10 @@ module Temporalio
|
|
40
40
|
# Convert a Ruby value to a payload and encode it.
|
41
41
|
#
|
42
42
|
# @param value [Object] Ruby value.
|
43
|
+
# @param hint [Object, nil] Hint, if any, to assist conversion.
|
43
44
|
# @return [Api::Common::V1::Payload] Converted and encoded payload.
|
44
|
-
def to_payload(value)
|
45
|
-
payload = payload_converter.to_payload(value)
|
45
|
+
def to_payload(value, hint: nil)
|
46
|
+
payload = payload_converter.to_payload(value, hint:)
|
46
47
|
payload = payload_codec.encode([payload]).first if payload_codec
|
47
48
|
payload
|
48
49
|
end
|
@@ -50,9 +51,13 @@ module Temporalio
|
|
50
51
|
# Convert multiple Ruby values to a payload set and encode it.
|
51
52
|
#
|
52
53
|
# @param values [Object] Ruby values, converted to array via {::Array}.
|
54
|
+
# @param hints [Array<Object>, nil] Hints, if any, to assist conversion. Note, when using the default converter
|
55
|
+
# that converts a payload at a time, hints for each value are taken from the array at that value's index. So if
|
56
|
+
# there are fewer hints than values, some values will not have a hint. Similarly if there are more hints than
|
57
|
+
# values, the trailing hints are not used.
|
53
58
|
# @return [Api::Common::V1::Payloads] Converted and encoded payload set.
|
54
|
-
def to_payloads(values)
|
55
|
-
payloads = payload_converter.to_payloads(values)
|
59
|
+
def to_payloads(values, hints: nil)
|
60
|
+
payloads = payload_converter.to_payloads(values, hints:)
|
56
61
|
payloads.payloads.replace(payload_codec.encode(payloads.payloads)) if payload_codec && !payloads.payloads.empty?
|
57
62
|
payloads
|
58
63
|
end
|
@@ -60,23 +65,28 @@ module Temporalio
|
|
60
65
|
# Decode and convert a payload to a Ruby value.
|
61
66
|
#
|
62
67
|
# @param payload [Api::Common::V1::Payload] Encoded payload.
|
68
|
+
# @param hint [Object, nil] Hint, if any, to assist conversion.
|
63
69
|
# @return [Object] Decoded and converted Ruby value.
|
64
|
-
def from_payload(payload)
|
70
|
+
def from_payload(payload, hint: nil)
|
65
71
|
payload = payload_codec.decode([payload]).first if payload_codec
|
66
|
-
payload_converter.from_payload(payload)
|
72
|
+
payload_converter.from_payload(payload, hint:)
|
67
73
|
end
|
68
74
|
|
69
75
|
# Decode and convert a payload set to Ruby values.
|
70
76
|
#
|
71
77
|
# @param payloads [Api::Common::V1::Payloads, nil] Encoded payload set.
|
78
|
+
# @param hints [Array<Object>, nil] Hints, if any, to assist conversion. Note, when using the default converter
|
79
|
+
# that converts a value at a time, hints for each payload are taken from the array at that payload's index. So
|
80
|
+
# if there are fewer hints than payloads, some payloads will not have a hint. Similarly if there are more hints
|
81
|
+
# than payloads, the trailing hints are not used.
|
72
82
|
# @return [Array<Object>] Decoded and converted Ruby values.
|
73
|
-
def from_payloads(payloads)
|
83
|
+
def from_payloads(payloads, hints: nil)
|
74
84
|
return [] unless payloads && !payloads.payloads.empty?
|
75
85
|
|
76
86
|
if payload_codec && !payloads.payloads.empty?
|
77
87
|
payloads = Api::Common::V1::Payloads.new(payloads: payload_codec.decode(payloads.payloads))
|
78
88
|
end
|
79
|
-
payload_converter.from_payloads(payloads)
|
89
|
+
payload_converter.from_payloads(payloads, hints:)
|
80
90
|
end
|
81
91
|
|
82
92
|
# Convert a Ruby error to a Temporal failure and encode it.
|
@@ -45,7 +45,8 @@ module Temporalio
|
|
45
45
|
type: error.type,
|
46
46
|
non_retryable: error.non_retryable,
|
47
47
|
details: converter.to_payloads(error.details),
|
48
|
-
next_retry_delay: Internal::ProtoUtils.seconds_to_duration(error.next_retry_delay)
|
48
|
+
next_retry_delay: Internal::ProtoUtils.seconds_to_duration(error.next_retry_delay),
|
49
|
+
category: error.category
|
49
50
|
)
|
50
51
|
when Error::TimeoutError
|
51
52
|
failure.timeout_failure_info = Api::Failure::V1::TimeoutFailureInfo.new(
|
@@ -85,7 +86,7 @@ module Temporalio
|
|
85
86
|
)
|
86
87
|
else
|
87
88
|
failure.application_failure_info = Api::Failure::V1::ApplicationFailureInfo.new(
|
88
|
-
type: error.class.name
|
89
|
+
type: error.class.name.to_s.split('::').last
|
89
90
|
)
|
90
91
|
end
|
91
92
|
|
@@ -132,7 +133,9 @@ module Temporalio
|
|
132
133
|
non_retryable: failure.application_failure_info.non_retryable,
|
133
134
|
next_retry_delay: Internal::ProtoUtils.duration_to_seconds(
|
134
135
|
failure.application_failure_info.next_retry_delay
|
135
|
-
)
|
136
|
+
),
|
137
|
+
category: Internal::ProtoUtils.enum_to_int(Api::Enums::V1::ApplicationErrorCategory,
|
138
|
+
failure.application_failure_info.category)
|
136
139
|
)
|
137
140
|
elsif failure.timeout_failure_info
|
138
141
|
Error::TimeoutError.new(
|
@@ -16,7 +16,7 @@ module Temporalio
|
|
16
16
|
end
|
17
17
|
|
18
18
|
# (see Encoding.to_payload)
|
19
|
-
def to_payload(value)
|
19
|
+
def to_payload(value, hint: nil) # rubocop:disable Lint/UnusedMethodArgument
|
20
20
|
return nil unless value.nil?
|
21
21
|
|
22
22
|
Api::Common::V1::Payload.new(
|
@@ -25,7 +25,7 @@ module Temporalio
|
|
25
25
|
end
|
26
26
|
|
27
27
|
# (see Encoding.from_payload)
|
28
|
-
def from_payload(payload) # rubocop:disable Lint/UnusedMethodArgument
|
28
|
+
def from_payload(payload, hint: nil) # rubocop:disable Lint/UnusedMethodArgument
|
29
29
|
nil
|
30
30
|
end
|
31
31
|
end
|
@@ -16,7 +16,7 @@ module Temporalio
|
|
16
16
|
end
|
17
17
|
|
18
18
|
# (see Encoding.to_payload)
|
19
|
-
def to_payload(value)
|
19
|
+
def to_payload(value, hint: nil) # rubocop:disable Lint/UnusedMethodArgument
|
20
20
|
return nil unless value.is_a?(String) && value.encoding == ::Encoding::ASCII_8BIT
|
21
21
|
|
22
22
|
Temporalio::Api::Common::V1::Payload.new(
|
@@ -26,7 +26,7 @@ module Temporalio
|
|
26
26
|
end
|
27
27
|
|
28
28
|
# (see Encoding.from_payload)
|
29
|
-
def from_payload(payload)
|
29
|
+
def from_payload(payload, hint: nil) # rubocop:disable Lint/UnusedMethodArgument
|
30
30
|
payload.data
|
31
31
|
end
|
32
32
|
end
|
@@ -17,7 +17,7 @@ module Temporalio
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# (see Encoding.to_payload)
|
20
|
-
def to_payload(value)
|
20
|
+
def to_payload(value, hint: nil) # rubocop:disable Lint/UnusedMethodArgument
|
21
21
|
return nil unless value.is_a?(Google::Protobuf::MessageExts)
|
22
22
|
|
23
23
|
# @type var value: Google::Protobuf::MessageExts
|
@@ -28,7 +28,7 @@ module Temporalio
|
|
28
28
|
end
|
29
29
|
|
30
30
|
# (see Encoding.from_payload)
|
31
|
-
def from_payload(payload)
|
31
|
+
def from_payload(payload, hint: nil) # rubocop:disable Lint/UnusedMethodArgument
|
32
32
|
type = payload.metadata['messageType']
|
33
33
|
# @type var desc: untyped
|
34
34
|
desc = Google::Protobuf::DescriptorPool.generated_pool.lookup(type)
|
@@ -32,14 +32,15 @@ module Temporalio
|
|
32
32
|
# Convert Ruby value to a payload by going over each encoding converter in order until one can convert.
|
33
33
|
#
|
34
34
|
# @param value [Object] Ruby value to convert.
|
35
|
+
# @param hint [Object, nil] Hint, if any, to assist conversion.
|
35
36
|
# @return [Api::Common::V1::Payload] Converted payload.
|
36
37
|
# @raise [ConverterNotFound] If no converters can process the value.
|
37
|
-
def to_payload(value)
|
38
|
+
def to_payload(value, hint: nil)
|
38
39
|
# As a special case, raw values just return the payload within
|
39
40
|
return value.payload if value.is_a?(RawValue)
|
40
41
|
|
41
42
|
converters.each_value do |converter|
|
42
|
-
payload = converter.to_payload(value)
|
43
|
+
payload = converter.to_payload(value, hint:)
|
43
44
|
return payload unless payload.nil?
|
44
45
|
end
|
45
46
|
raise ConverterNotFound, "Value of type #{value} has no known converter"
|
@@ -48,17 +49,18 @@ module Temporalio
|
|
48
49
|
# Convert payload to Ruby value based on its +encoding+ metadata on the payload.
|
49
50
|
#
|
50
51
|
# @param payload [Api::Common::V1::Payload] Payload to convert.
|
52
|
+
# @param hint [Object, nil] Hint, if any, to assist conversion.
|
51
53
|
# @return [Object] Converted Ruby value.
|
52
54
|
# @raise [EncodingNotSet] If encoding not set on the metadata.
|
53
55
|
# @raise [ConverterNotFound] If no converter found for the encoding.
|
54
|
-
def from_payload(payload)
|
56
|
+
def from_payload(payload, hint: nil)
|
55
57
|
encoding = payload.metadata['encoding']
|
56
58
|
raise EncodingNotSet, 'Missing payload encoding' unless encoding
|
57
59
|
|
58
60
|
converter = converters[encoding]
|
59
61
|
raise ConverterNotFound, "No converter for encoding #{encoding}" unless converter
|
60
62
|
|
61
|
-
converter.from_payload(payload)
|
63
|
+
converter.from_payload(payload, hint:)
|
62
64
|
end
|
63
65
|
end
|
64
66
|
end
|
@@ -16,8 +16,9 @@ module Temporalio
|
|
16
16
|
# handle it, the resulting payload must have +encoding+ metadata on the payload set to the value of {encoding}.
|
17
17
|
#
|
18
18
|
# @param value [Object] Ruby value to possibly convert.
|
19
|
+
# @param hint [Object, nil] Hint, if any, to assist conversion.
|
19
20
|
# @return [Api::Common::V1::Payload, nil] Converted payload if it can handle it, +nil+ otherwise.
|
20
|
-
def to_payload(value)
|
21
|
+
def to_payload(value, hint: nil)
|
21
22
|
raise NotImplementedError
|
22
23
|
end
|
23
24
|
|
@@ -25,8 +26,9 @@ module Temporalio
|
|
25
26
|
# will error if it cannot convert.
|
26
27
|
#
|
27
28
|
# @param payload [Api::Common::V1::Payload] Payload to convert.
|
29
|
+
# @param hint [Object, nil] Hint, if any, to assist conversion.
|
28
30
|
# @return [Object] Converted Ruby value.
|
29
|
-
def from_payload(payload)
|
31
|
+
def from_payload(payload, hint: nil)
|
30
32
|
raise NotImplementedError
|
31
33
|
end
|
32
34
|
end
|