temporalio 0.3.0-x86_64-linux-musl → 0.5.0-x86_64-linux-musl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/Gemfile +4 -0
  4. data/Rakefile +1 -1
  5. data/lib/temporalio/activity/cancellation_details.rb +58 -0
  6. data/lib/temporalio/activity/context.rb +17 -1
  7. data/lib/temporalio/activity/definition.rb +45 -4
  8. data/lib/temporalio/activity/info.rb +28 -4
  9. data/lib/temporalio/activity.rb +2 -0
  10. data/lib/temporalio/api/activity/v1/message.rb +1 -1
  11. data/lib/temporalio/api/batch/v1/message.rb +9 -2
  12. data/lib/temporalio/api/cloud/account/v1/message.rb +1 -1
  13. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +11 -2
  14. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +2 -2
  15. data/lib/temporalio/api/cloud/identity/v1/message.rb +7 -2
  16. data/lib/temporalio/api/cloud/namespace/v1/message.rb +6 -2
  17. data/lib/temporalio/api/cloud/nexus/v1/message.rb +3 -2
  18. data/lib/temporalio/api/cloud/operation/v1/message.rb +1 -1
  19. data/lib/temporalio/api/cloud/region/v1/message.rb +1 -1
  20. data/lib/temporalio/api/cloud/resource/v1/message.rb +1 -1
  21. data/lib/temporalio/api/cloud/sink/v1/message.rb +1 -1
  22. data/lib/temporalio/api/cloud/usage/v1/message.rb +1 -1
  23. data/lib/temporalio/api/command/v1/message.rb +2 -2
  24. data/lib/temporalio/api/common/v1/grpc_status.rb +1 -1
  25. data/lib/temporalio/api/common/v1/message.rb +4 -2
  26. data/lib/temporalio/api/deployment/v1/message.rb +39 -0
  27. data/lib/temporalio/api/enums/v1/batch_operation.rb +2 -2
  28. data/lib/temporalio/api/enums/v1/command_type.rb +1 -1
  29. data/lib/temporalio/api/enums/v1/common.rb +5 -2
  30. data/lib/temporalio/api/enums/v1/deployment.rb +24 -0
  31. data/lib/temporalio/api/enums/v1/event_type.rb +2 -2
  32. data/lib/temporalio/api/enums/v1/failed_cause.rb +2 -2
  33. data/lib/temporalio/api/enums/v1/namespace.rb +1 -1
  34. data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
  35. data/lib/temporalio/api/enums/v1/query.rb +1 -1
  36. data/lib/temporalio/api/enums/v1/reset.rb +2 -2
  37. data/lib/temporalio/api/enums/v1/schedule.rb +1 -1
  38. data/lib/temporalio/api/enums/v1/task_queue.rb +1 -1
  39. data/lib/temporalio/api/enums/v1/update.rb +1 -1
  40. data/lib/temporalio/api/enums/v1/workflow.rb +3 -2
  41. data/lib/temporalio/api/errordetails/v1/message.rb +4 -2
  42. data/lib/temporalio/api/export/v1/message.rb +1 -1
  43. data/lib/temporalio/api/failure/v1/message.rb +5 -2
  44. data/lib/temporalio/api/filter/v1/message.rb +1 -1
  45. data/lib/temporalio/api/history/v1/message.rb +6 -2
  46. data/lib/temporalio/api/namespace/v1/message.rb +1 -1
  47. data/lib/temporalio/api/nexus/v1/message.rb +3 -2
  48. data/lib/temporalio/api/operatorservice/v1/request_response.rb +1 -1
  49. data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
  50. data/lib/temporalio/api/payload_visitor.rb +162 -7
  51. data/lib/temporalio/api/protocol/v1/message.rb +1 -1
  52. data/lib/temporalio/api/query/v1/message.rb +3 -2
  53. data/lib/temporalio/api/replication/v1/message.rb +1 -1
  54. data/lib/temporalio/api/rules/v1/message.rb +27 -0
  55. data/lib/temporalio/api/schedule/v1/message.rb +2 -2
  56. data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +1 -1
  57. data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +1 -1
  58. data/lib/temporalio/api/sdk/v1/user_metadata.rb +1 -1
  59. data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +1 -1
  60. data/lib/temporalio/api/taskqueue/v1/message.rb +5 -2
  61. data/lib/temporalio/api/testservice/v1/request_response.rb +1 -1
  62. data/lib/temporalio/api/testservice/v1/service.rb +1 -1
  63. data/lib/temporalio/api/update/v1/message.rb +1 -1
  64. data/lib/temporalio/api/version/v1/message.rb +1 -1
  65. data/lib/temporalio/api/worker/v1/message.rb +30 -0
  66. data/lib/temporalio/api/workflow/v1/message.rb +22 -2
  67. data/lib/temporalio/api/workflowservice/v1/request_response.rb +58 -12
  68. data/lib/temporalio/api/workflowservice/v1/service.rb +2 -2
  69. data/lib/temporalio/api.rb +1 -0
  70. data/lib/temporalio/client/async_activity_handle.rb +12 -4
  71. data/lib/temporalio/client/connection/cloud_service.rb +60 -0
  72. data/lib/temporalio/client/connection/workflow_service.rb +343 -28
  73. data/lib/temporalio/client/interceptor.rb +64 -7
  74. data/lib/temporalio/client/schedule.rb +35 -3
  75. data/lib/temporalio/client/with_start_workflow_operation.rb +123 -0
  76. data/lib/temporalio/client/workflow_execution.rb +19 -0
  77. data/lib/temporalio/client/workflow_handle.rb +47 -7
  78. data/lib/temporalio/client/workflow_update_handle.rb +9 -3
  79. data/lib/temporalio/client.rb +231 -4
  80. data/lib/temporalio/common_enums.rb +14 -0
  81. data/lib/temporalio/contrib/open_telemetry.rb +474 -0
  82. data/lib/temporalio/converters/data_converter.rb +18 -8
  83. data/lib/temporalio/converters/failure_converter.rb +6 -3
  84. data/lib/temporalio/converters/payload_converter/binary_null.rb +2 -2
  85. data/lib/temporalio/converters/payload_converter/binary_plain.rb +2 -2
  86. data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +2 -2
  87. data/lib/temporalio/converters/payload_converter/composite.rb +6 -4
  88. data/lib/temporalio/converters/payload_converter/encoding.rb +4 -2
  89. data/lib/temporalio/converters/payload_converter/json_plain.rb +2 -2
  90. data/lib/temporalio/converters/payload_converter/json_protobuf.rb +2 -2
  91. data/lib/temporalio/converters/payload_converter.rb +16 -6
  92. data/lib/temporalio/error/failure.rb +19 -1
  93. data/lib/temporalio/error.rb +2 -1
  94. data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.so +0 -0
  95. data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so +0 -0
  96. data/lib/temporalio/internal/bridge/3.4/temporalio_bridge.so +0 -0
  97. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +1 -1
  98. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +3 -2
  99. data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +1 -1
  100. data/lib/temporalio/internal/bridge/api/common/common.rb +3 -2
  101. data/lib/temporalio/internal/bridge/api/core_interface.rb +1 -1
  102. data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +1 -1
  103. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +3 -2
  104. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +2 -2
  105. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +3 -2
  106. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +3 -2
  107. data/lib/temporalio/internal/bridge/runtime.rb +3 -0
  108. data/lib/temporalio/internal/bridge/testing.rb +3 -0
  109. data/lib/temporalio/internal/bridge/worker.rb +28 -4
  110. data/lib/temporalio/internal/bridge.rb +1 -1
  111. data/lib/temporalio/internal/client/implementation.rb +281 -51
  112. data/lib/temporalio/internal/proto_utils.rb +38 -6
  113. data/lib/temporalio/internal/worker/activity_worker.rb +107 -25
  114. data/lib/temporalio/internal/worker/multi_runner.rb +2 -2
  115. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +8 -6
  116. data/lib/temporalio/internal/worker/workflow_instance/context.rb +96 -5
  117. data/lib/temporalio/internal/worker/workflow_instance/details.rb +7 -2
  118. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +2 -2
  119. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +64 -18
  120. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +39 -40
  121. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +22 -2
  122. data/lib/temporalio/internal/worker/workflow_instance.rb +134 -55
  123. data/lib/temporalio/internal/worker/workflow_worker.rb +19 -6
  124. data/lib/temporalio/priority.rb +59 -0
  125. data/lib/temporalio/runtime/metric_buffer.rb +94 -0
  126. data/lib/temporalio/runtime.rb +48 -10
  127. data/lib/temporalio/search_attributes.rb +13 -0
  128. data/lib/temporalio/testing/activity_environment.rb +49 -10
  129. data/lib/temporalio/testing/workflow_environment.rb +29 -6
  130. data/lib/temporalio/version.rb +1 -1
  131. data/lib/temporalio/versioning_override.rb +56 -0
  132. data/lib/temporalio/worker/deployment_options.rb +45 -0
  133. data/lib/temporalio/worker/illegal_workflow_call_validator.rb +64 -0
  134. data/lib/temporalio/worker/interceptor.rb +16 -1
  135. data/lib/temporalio/worker/poller_behavior.rb +61 -0
  136. data/lib/temporalio/worker/thread_pool.rb +6 -6
  137. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +9 -3
  138. data/lib/temporalio/worker/workflow_replayer.rb +19 -13
  139. data/lib/temporalio/worker.rb +97 -27
  140. data/lib/temporalio/worker_deployment_version.rb +67 -0
  141. data/lib/temporalio/workflow/child_workflow_handle.rb +10 -2
  142. data/lib/temporalio/workflow/definition.rb +217 -35
  143. data/lib/temporalio/workflow/external_workflow_handle.rb +3 -1
  144. data/lib/temporalio/workflow/future.rb +2 -2
  145. data/lib/temporalio/workflow/info.rb +26 -1
  146. data/lib/temporalio/workflow.rb +113 -15
  147. data/lib/temporalio.rb +1 -0
  148. data/temporalio.gemspec +3 -1
  149. metadata +33 -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