temporalio 0.2.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.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/Cargo.lock +980 -583
  4. data/Cargo.toml +2 -2
  5. data/Gemfile +7 -3
  6. data/README.md +769 -54
  7. data/Rakefile +10 -296
  8. data/ext/Cargo.toml +2 -0
  9. data/lib/temporalio/activity/complete_async_error.rb +1 -1
  10. data/lib/temporalio/activity/context.rb +18 -2
  11. data/lib/temporalio/activity/definition.rb +180 -65
  12. data/lib/temporalio/activity/info.rb +25 -21
  13. data/lib/temporalio/activity.rb +2 -59
  14. data/lib/temporalio/api/activity/v1/message.rb +25 -0
  15. data/lib/temporalio/api/batch/v1/message.rb +6 -1
  16. data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
  17. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +34 -1
  18. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +1 -1
  19. data/lib/temporalio/api/cloud/identity/v1/message.rb +6 -1
  20. data/lib/temporalio/api/cloud/namespace/v1/message.rb +8 -1
  21. data/lib/temporalio/api/cloud/nexus/v1/message.rb +31 -0
  22. data/lib/temporalio/api/cloud/operation/v1/message.rb +2 -1
  23. data/lib/temporalio/api/cloud/region/v1/message.rb +2 -1
  24. data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
  25. data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
  26. data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
  27. data/lib/temporalio/api/command/v1/message.rb +1 -1
  28. data/lib/temporalio/api/common/v1/message.rb +8 -1
  29. data/lib/temporalio/api/deployment/v1/message.rb +38 -0
  30. data/lib/temporalio/api/enums/v1/batch_operation.rb +1 -1
  31. data/lib/temporalio/api/enums/v1/common.rb +1 -1
  32. data/lib/temporalio/api/enums/v1/deployment.rb +23 -0
  33. data/lib/temporalio/api/enums/v1/event_type.rb +1 -1
  34. data/lib/temporalio/api/enums/v1/failed_cause.rb +1 -1
  35. data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
  36. data/lib/temporalio/api/enums/v1/reset.rb +1 -1
  37. data/lib/temporalio/api/enums/v1/workflow.rb +2 -1
  38. data/lib/temporalio/api/errordetails/v1/message.rb +3 -1
  39. data/lib/temporalio/api/failure/v1/message.rb +3 -1
  40. data/lib/temporalio/api/history/v1/message.rb +3 -1
  41. data/lib/temporalio/api/nexus/v1/message.rb +3 -2
  42. data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
  43. data/lib/temporalio/api/payload_visitor.rb +1581 -0
  44. data/lib/temporalio/api/query/v1/message.rb +2 -1
  45. data/lib/temporalio/api/schedule/v1/message.rb +2 -1
  46. data/lib/temporalio/api/taskqueue/v1/message.rb +4 -1
  47. data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
  48. data/lib/temporalio/api/testservice/v1/service.rb +23 -0
  49. data/lib/temporalio/api/workflow/v1/message.rb +9 -1
  50. data/lib/temporalio/api/workflowservice/v1/request_response.rb +46 -2
  51. data/lib/temporalio/api/workflowservice/v1/service.rb +1 -1
  52. data/lib/temporalio/api.rb +2 -0
  53. data/lib/temporalio/cancellation.rb +34 -14
  54. data/lib/temporalio/client/async_activity_handle.rb +12 -37
  55. data/lib/temporalio/client/connection/cloud_service.rb +309 -231
  56. data/lib/temporalio/client/connection/operator_service.rb +36 -84
  57. data/lib/temporalio/client/connection/service.rb +6 -5
  58. data/lib/temporalio/client/connection/test_service.rb +111 -0
  59. data/lib/temporalio/client/connection/workflow_service.rb +474 -441
  60. data/lib/temporalio/client/connection.rb +90 -44
  61. data/lib/temporalio/client/interceptor.rb +199 -60
  62. data/lib/temporalio/client/schedule.rb +991 -0
  63. data/lib/temporalio/client/schedule_handle.rb +126 -0
  64. data/lib/temporalio/client/with_start_workflow_operation.rb +115 -0
  65. data/lib/temporalio/client/workflow_execution.rb +26 -10
  66. data/lib/temporalio/client/workflow_handle.rb +41 -98
  67. data/lib/temporalio/client/workflow_update_handle.rb +3 -5
  68. data/lib/temporalio/client.rb +247 -44
  69. data/lib/temporalio/common_enums.rb +17 -0
  70. data/lib/temporalio/contrib/open_telemetry.rb +470 -0
  71. data/lib/temporalio/converters/data_converter.rb +4 -7
  72. data/lib/temporalio/converters/failure_converter.rb +5 -3
  73. data/lib/temporalio/converters/payload_converter/composite.rb +4 -0
  74. data/lib/temporalio/converters/payload_converter.rb +6 -8
  75. data/lib/temporalio/converters/raw_value.rb +20 -0
  76. data/lib/temporalio/error/failure.rb +1 -1
  77. data/lib/temporalio/error.rb +11 -2
  78. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +1 -1
  79. data/lib/temporalio/internal/bridge/api/common/common.rb +2 -1
  80. data/lib/temporalio/internal/bridge/api/core_interface.rb +5 -1
  81. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +33 -0
  82. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +5 -1
  83. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +4 -1
  84. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +2 -1
  85. data/lib/temporalio/internal/bridge/client.rb +11 -6
  86. data/lib/temporalio/internal/bridge/runtime.rb +3 -0
  87. data/lib/temporalio/internal/bridge/testing.rb +23 -0
  88. data/lib/temporalio/internal/bridge/worker.rb +2 -0
  89. data/lib/temporalio/internal/bridge.rb +1 -1
  90. data/lib/temporalio/internal/client/implementation.rb +468 -71
  91. data/lib/temporalio/internal/metric.rb +122 -0
  92. data/lib/temporalio/internal/proto_utils.rb +118 -7
  93. data/lib/temporalio/internal/worker/activity_worker.rb +69 -29
  94. data/lib/temporalio/internal/worker/multi_runner.rb +53 -9
  95. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
  96. data/lib/temporalio/internal/worker/workflow_instance/context.rb +383 -0
  97. data/lib/temporalio/internal/worker/workflow_instance/details.rb +46 -0
  98. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
  99. data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
  100. data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
  101. data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
  102. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
  103. data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
  104. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +400 -0
  105. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
  106. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
  107. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +183 -0
  108. data/lib/temporalio/internal/worker/workflow_instance.rb +774 -0
  109. data/lib/temporalio/internal/worker/workflow_worker.rb +239 -0
  110. data/lib/temporalio/metric.rb +109 -0
  111. data/lib/temporalio/retry_policy.rb +37 -14
  112. data/lib/temporalio/runtime/metric_buffer.rb +94 -0
  113. data/lib/temporalio/runtime.rb +160 -79
  114. data/lib/temporalio/search_attributes.rb +93 -37
  115. data/lib/temporalio/testing/activity_environment.rb +44 -16
  116. data/lib/temporalio/testing/workflow_environment.rb +276 -7
  117. data/lib/temporalio/version.rb +1 -1
  118. data/lib/temporalio/worker/activity_executor/thread_pool.rb +9 -217
  119. data/lib/temporalio/worker/activity_executor.rb +3 -3
  120. data/lib/temporalio/worker/interceptor.rb +343 -66
  121. data/lib/temporalio/worker/thread_pool.rb +237 -0
  122. data/lib/temporalio/worker/tuner.rb +38 -0
  123. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +235 -0
  124. data/lib/temporalio/worker/workflow_executor.rb +26 -0
  125. data/lib/temporalio/worker/workflow_replayer.rb +350 -0
  126. data/lib/temporalio/worker.rb +235 -58
  127. data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
  128. data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
  129. data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
  130. data/lib/temporalio/workflow/definition.rb +598 -0
  131. data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
  132. data/lib/temporalio/workflow/future.rb +151 -0
  133. data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
  134. data/lib/temporalio/workflow/info.rb +104 -0
  135. data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
  136. data/lib/temporalio/workflow/update_info.rb +20 -0
  137. data/lib/temporalio/workflow.rb +575 -0
  138. data/lib/temporalio/workflow_history.rb +26 -1
  139. data/lib/temporalio.rb +4 -0
  140. data/temporalio.gemspec +4 -3
  141. metadata +73 -10
@@ -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
@@ -8,10 +8,10 @@ module Temporalio
8
8
  module Converters
9
9
  # Data converter for converting/encoding payloads to/from Ruby values.
10
10
  class DataConverter
11
- # @return [PayloadConverter] Payload converter. This must be Ractor shareable.
11
+ # @return [PayloadConverter] Payload converter.
12
12
  attr_reader :payload_converter
13
13
 
14
- # @return [FailureConverter] Failure converter. This must be Ractor shareable.
14
+ # @return [FailureConverter] Failure converter.
15
15
  attr_reader :failure_converter
16
16
 
17
17
  # @return [PayloadCodec, nil] Optional codec for encoding/decoding payload bytes such as for encryption.
@@ -24,17 +24,14 @@ module Temporalio
24
24
 
25
25
  # Create data converter.
26
26
  #
27
- # @param payload_converter [PayloadConverter] Payload converter to use. This must be Ractor shareable.
28
- # @param failure_converter [FailureConverter] Failure converter to use. This must be Ractor shareable.
27
+ # @param payload_converter [PayloadConverter] Payload converter to use.
28
+ # @param failure_converter [FailureConverter] Failure converter to use.
29
29
  # @param payload_codec [PayloadCodec, nil] Payload codec to use.
30
30
  def initialize(
31
31
  payload_converter: PayloadConverter.default,
32
32
  failure_converter: FailureConverter.default,
33
33
  payload_codec: nil
34
34
  )
35
- raise 'Payload converter not shareable' unless Ractor.shareable?(payload_converter)
36
- raise 'Failure converter not shareable' unless Ractor.shareable?(failure_converter)
37
-
38
35
  @payload_converter = payload_converter
39
36
  @failure_converter = failure_converter
40
37
  @payload_codec = payload_codec
@@ -10,7 +10,7 @@ module Temporalio
10
10
  class FailureConverter
11
11
  # @return [FailureConverter] Default failure converter.
12
12
  def self.default
13
- @default ||= Ractor.make_shareable(FailureConverter.new)
13
+ @default ||= FailureConverter.new
14
14
  end
15
15
 
16
16
  # @return [Boolean] If +true+, the message and stack trace of the failure will be moved into the encoded attribute
@@ -85,7 +85,7 @@ module Temporalio
85
85
  )
86
86
  else
87
87
  failure.application_failure_info = Api::Failure::V1::ApplicationFailureInfo.new(
88
- type: error.class.name.split('::').last
88
+ type: error.class.name
89
89
  )
90
90
  end
91
91
 
@@ -130,7 +130,9 @@ module Temporalio
130
130
  *converter.from_payloads(failure.application_failure_info.details),
131
131
  type: Internal::ProtoUtils.string_or(failure.application_failure_info.type),
132
132
  non_retryable: failure.application_failure_info.non_retryable,
133
- next_retry_delay: failure.application_failure_info.next_retry_delay&.to_f
133
+ next_retry_delay: Internal::ProtoUtils.duration_to_seconds(
134
+ failure.application_failure_info.next_retry_delay
135
+ )
134
136
  )
135
137
  elsif failure.timeout_failure_info
136
138
  Error::TimeoutError.new(
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'temporalio/api'
4
4
  require 'temporalio/converters/payload_converter'
5
+ require 'temporalio/converters/raw_value'
5
6
 
6
7
  module Temporalio
7
8
  module Converters
@@ -34,6 +35,9 @@ module Temporalio
34
35
  # @return [Api::Common::V1::Payload] Converted payload.
35
36
  # @raise [ConverterNotFound] If no converters can process the value.
36
37
  def to_payload(value)
38
+ # As a special case, raw values just return the payload within
39
+ return value.payload if value.is_a?(RawValue)
40
+
37
41
  converters.each_value do |converter|
38
42
  payload = converter.to_payload(value)
39
43
  return payload unless payload.nil?
@@ -22,14 +22,12 @@ module Temporalio
22
22
  # @param json_generate_options [Hash] Options for {::JSON.generate}.
23
23
  # @return [PayloadConverter::Composite] Created payload converter.
24
24
  def self.new_with_defaults(json_parse_options: { create_additions: true }, json_generate_options: {})
25
- Ractor.make_shareable(
26
- PayloadConverter::Composite.new(
27
- PayloadConverter::BinaryNull.new,
28
- PayloadConverter::BinaryPlain.new,
29
- PayloadConverter::JSONProtobuf.new,
30
- PayloadConverter::BinaryProtobuf.new,
31
- PayloadConverter::JSONPlain.new(parse_options: json_parse_options, generate_options: json_generate_options)
32
- )
25
+ PayloadConverter::Composite.new(
26
+ PayloadConverter::BinaryNull.new,
27
+ PayloadConverter::BinaryPlain.new,
28
+ PayloadConverter::JSONProtobuf.new,
29
+ PayloadConverter::BinaryProtobuf.new,
30
+ PayloadConverter::JSONPlain.new(parse_options: json_parse_options, generate_options: json_generate_options)
33
31
  )
34
32
  end
35
33
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Temporalio
4
+ module Converters
5
+ # Raw value wrapper that has the raw payload. When raw args are configured at implementation time, the inbound
6
+ # arguments will be instances of this class. When instances of this class are sent outbound or returned from
7
+ # inbound calls, the raw payload will be serialized instead of applying traditional conversion.
8
+ class RawValue
9
+ # @return [Api::Common::V1::Payload] Payload.
10
+ attr_reader :payload
11
+
12
+ # Create a raw value.
13
+ #
14
+ # @param payload [Api::Common::V1::Payload] Payload.
15
+ def initialize(payload)
16
+ @payload = payload
17
+ end
18
+ end
19
+ end
20
+ end
@@ -17,7 +17,7 @@ module Temporalio
17
17
  # @return [String] Workflow type name of the already-started workflow.
18
18
  attr_reader :workflow_type
19
19
 
20
- # @return [String] Run ID of the already-started workflow if this was raised by the client.
20
+ # @return [String, nil] Run ID of the already-started workflow if this was raised by the client.
21
21
  attr_reader :run_id
22
22
 
23
23
  # @!visibility private
@@ -35,8 +35,8 @@ module Temporalio
35
35
  # Error that is returned from when a workflow is unsuccessful.
36
36
  class WorkflowFailedError < Error
37
37
  # @!visibility private
38
- def initialize
39
- super('Workflow failed')
38
+ def initialize(message = 'Workflow execution failed')
39
+ super
40
40
  end
41
41
  end
42
42
 
@@ -87,6 +87,14 @@ module Temporalio
87
87
  end
88
88
  end
89
89
 
90
+ # Error when a schedule is already running.
91
+ class ScheduleAlreadyRunningError < Error
92
+ # @!visibility private
93
+ def initialize
94
+ super('Schedule already running')
95
+ end
96
+ end
97
+
90
98
  # Error that occurs when an async activity handle tries to heartbeat and the activity is marked as canceled.
91
99
  class AsyncActivityCanceledError < Error
92
100
  # @!visibility private
@@ -116,6 +124,7 @@ module Temporalio
116
124
 
117
125
  def create_grpc_status
118
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)
119
128
 
120
129
  Api::Common::V1::GrpcStatus.decode(@raw_grpc_status)
121
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\"\xed\x06\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\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"
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)
@@ -7,7 +7,7 @@ require 'google/protobuf'
7
7
  require 'google/protobuf/duration_pb'
8
8
 
9
9
 
10
- descriptor_data = "\n%temporal/sdk/core/common/common.proto\x12\x0e\x63oresdk.common\x1a\x1egoogle/protobuf/duration.proto\"U\n\x1bNamespacedWorkflowExecution\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\x12\x0e\n\x06run_id\x18\x03 \x01(\t*@\n\x10VersioningIntent\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x0e\n\nCOMPATIBLE\x10\x01\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x02\x42,\xea\x02)Temporalio::Internal::Bridge::Api::Commonb\x06proto3"
10
+ descriptor_data = "\n%temporal/sdk/core/common/common.proto\x12\x0e\x63oresdk.common\x1a\x1egoogle/protobuf/duration.proto\"U\n\x1bNamespacedWorkflowExecution\x12\x11\n\tnamespace\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\x12\x0e\n\x06run_id\x18\x03 \x01(\t\"D\n\x17WorkerDeploymentVersion\x12\x17\n\x0f\x64\x65ployment_name\x18\x01 \x01(\t\x12\x10\n\x08\x62uild_id\x18\x02 \x01(\t*@\n\x10VersioningIntent\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x0e\n\nCOMPATIBLE\x10\x01\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x02\x42,\xea\x02)Temporalio::Internal::Bridge::Api::Commonb\x06proto3"
11
11
 
12
12
  pool = Google::Protobuf::DescriptorPool.generated_pool
13
13
  pool.add_serialized_file(descriptor_data)
@@ -18,6 +18,7 @@ module Temporalio
18
18
  module Api
19
19
  module Common
20
20
  NamespacedWorkflowExecution = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("coresdk.common.NamespacedWorkflowExecution").msgclass
21
+ WorkerDeploymentVersion = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("coresdk.common.WorkerDeploymentVersion").msgclass
21
22
  VersioningIntent = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("coresdk.common.VersioningIntent").enummodule
22
23
  end
23
24
  end