temporalio 0.4.0 → 0.5.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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/Cargo.lock +679 -437
  4. data/Cargo.toml +5 -5
  5. data/README.md +98 -34
  6. data/ext/Cargo.toml +3 -3
  7. data/lib/temporalio/activity/cancellation_details.rb +58 -0
  8. data/lib/temporalio/activity/context.rb +10 -1
  9. data/lib/temporalio/activity/definition.rb +41 -3
  10. data/lib/temporalio/activity/info.rb +25 -4
  11. data/lib/temporalio/activity.rb +2 -0
  12. data/lib/temporalio/api/activity/v1/message.rb +1 -1
  13. data/lib/temporalio/api/batch/v1/message.rb +4 -2
  14. data/lib/temporalio/api/cloud/account/v1/message.rb +1 -1
  15. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +11 -2
  16. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +2 -2
  17. data/lib/temporalio/api/cloud/identity/v1/message.rb +7 -2
  18. data/lib/temporalio/api/cloud/namespace/v1/message.rb +6 -2
  19. data/lib/temporalio/api/cloud/nexus/v1/message.rb +3 -2
  20. data/lib/temporalio/api/cloud/operation/v1/message.rb +1 -1
  21. data/lib/temporalio/api/cloud/region/v1/message.rb +1 -1
  22. data/lib/temporalio/api/cloud/resource/v1/message.rb +1 -1
  23. data/lib/temporalio/api/cloud/sink/v1/message.rb +1 -1
  24. data/lib/temporalio/api/cloud/usage/v1/message.rb +1 -1
  25. data/lib/temporalio/api/command/v1/message.rb +2 -2
  26. data/lib/temporalio/api/common/v1/grpc_status.rb +1 -1
  27. data/lib/temporalio/api/common/v1/message.rb +3 -2
  28. data/lib/temporalio/api/deployment/v1/message.rb +3 -2
  29. data/lib/temporalio/api/enums/v1/batch_operation.rb +1 -1
  30. data/lib/temporalio/api/enums/v1/command_type.rb +1 -1
  31. data/lib/temporalio/api/enums/v1/common.rb +5 -2
  32. data/lib/temporalio/api/enums/v1/deployment.rb +3 -2
  33. data/lib/temporalio/api/enums/v1/event_type.rb +2 -2
  34. data/lib/temporalio/api/enums/v1/failed_cause.rb +2 -2
  35. data/lib/temporalio/api/enums/v1/namespace.rb +1 -1
  36. data/lib/temporalio/api/enums/v1/nexus.rb +1 -1
  37. data/lib/temporalio/api/enums/v1/query.rb +1 -1
  38. data/lib/temporalio/api/enums/v1/reset.rb +1 -1
  39. data/lib/temporalio/api/enums/v1/schedule.rb +1 -1
  40. data/lib/temporalio/api/enums/v1/task_queue.rb +1 -1
  41. data/lib/temporalio/api/enums/v1/update.rb +1 -1
  42. data/lib/temporalio/api/enums/v1/workflow.rb +2 -2
  43. data/lib/temporalio/api/errordetails/v1/message.rb +1 -1
  44. data/lib/temporalio/api/export/v1/message.rb +1 -1
  45. data/lib/temporalio/api/failure/v1/message.rb +3 -2
  46. data/lib/temporalio/api/filter/v1/message.rb +1 -1
  47. data/lib/temporalio/api/history/v1/message.rb +4 -2
  48. data/lib/temporalio/api/namespace/v1/message.rb +1 -1
  49. data/lib/temporalio/api/nexus/v1/message.rb +2 -2
  50. data/lib/temporalio/api/operatorservice/v1/request_response.rb +1 -1
  51. data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
  52. data/lib/temporalio/api/payload_visitor.rb +87 -0
  53. data/lib/temporalio/api/protocol/v1/message.rb +1 -1
  54. data/lib/temporalio/api/query/v1/message.rb +1 -1
  55. data/lib/temporalio/api/replication/v1/message.rb +1 -1
  56. data/lib/temporalio/api/rules/v1/message.rb +27 -0
  57. data/lib/temporalio/api/schedule/v1/message.rb +2 -2
  58. data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +1 -1
  59. data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +1 -1
  60. data/lib/temporalio/api/sdk/v1/user_metadata.rb +1 -1
  61. data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +1 -1
  62. data/lib/temporalio/api/taskqueue/v1/message.rb +2 -2
  63. data/lib/temporalio/api/testservice/v1/request_response.rb +1 -1
  64. data/lib/temporalio/api/testservice/v1/service.rb +1 -1
  65. data/lib/temporalio/api/update/v1/message.rb +1 -1
  66. data/lib/temporalio/api/version/v1/message.rb +1 -1
  67. data/lib/temporalio/api/worker/v1/message.rb +30 -0
  68. data/lib/temporalio/api/workflow/v1/message.rb +14 -2
  69. data/lib/temporalio/api/workflowservice/v1/request_response.rb +19 -2
  70. data/lib/temporalio/api/workflowservice/v1/service.rb +2 -2
  71. data/lib/temporalio/client/async_activity_handle.rb +12 -4
  72. data/lib/temporalio/client/connection/cloud_service.rb +60 -0
  73. data/lib/temporalio/client/connection/workflow_service.rb +105 -0
  74. data/lib/temporalio/client/interceptor.rb +25 -7
  75. data/lib/temporalio/client/schedule.rb +10 -2
  76. data/lib/temporalio/client/with_start_workflow_operation.rb +9 -1
  77. data/lib/temporalio/client/workflow_handle.rb +50 -10
  78. data/lib/temporalio/client/workflow_update_handle.rb +9 -3
  79. data/lib/temporalio/client.rb +110 -6
  80. data/lib/temporalio/common_enums.rb +14 -0
  81. data/lib/temporalio/contrib/open_telemetry.rb +13 -9
  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 +1 -1
  94. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +1 -1
  95. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +3 -2
  96. data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +1 -1
  97. data/lib/temporalio/internal/bridge/api/common/common.rb +1 -1
  98. data/lib/temporalio/internal/bridge/api/core_interface.rb +1 -1
  99. data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +1 -1
  100. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +3 -2
  101. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +2 -2
  102. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +3 -2
  103. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +1 -1
  104. data/lib/temporalio/internal/bridge/worker.rb +28 -4
  105. data/lib/temporalio/internal/bridge.rb +1 -1
  106. data/lib/temporalio/internal/client/implementation.rb +60 -52
  107. data/lib/temporalio/internal/proto_utils.rb +4 -4
  108. data/lib/temporalio/internal/worker/activity_worker.rb +93 -20
  109. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +8 -6
  110. data/lib/temporalio/internal/worker/workflow_instance/context.rb +65 -24
  111. data/lib/temporalio/internal/worker/workflow_instance/details.rb +5 -2
  112. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +2 -2
  113. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +64 -18
  114. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +28 -14
  115. data/lib/temporalio/internal/worker/workflow_instance.rb +58 -23
  116. data/lib/temporalio/internal/worker/workflow_worker.rb +16 -6
  117. data/lib/temporalio/priority.rb +59 -0
  118. data/lib/temporalio/testing/activity_environment.rb +17 -2
  119. data/lib/temporalio/testing/workflow_environment.rb +3 -3
  120. data/lib/temporalio/version.rb +1 -1
  121. data/lib/temporalio/versioning_override.rb +56 -0
  122. data/lib/temporalio/worker/deployment_options.rb +45 -0
  123. data/lib/temporalio/worker/illegal_workflow_call_validator.rb +64 -0
  124. data/lib/temporalio/worker/interceptor.rb +13 -1
  125. data/lib/temporalio/worker/poller_behavior.rb +61 -0
  126. data/lib/temporalio/worker/thread_pool.rb +1 -1
  127. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +2 -1
  128. data/lib/temporalio/worker/workflow_replayer.rb +12 -13
  129. data/lib/temporalio/worker.rb +63 -27
  130. data/lib/temporalio/worker_deployment_version.rb +67 -0
  131. data/lib/temporalio/workflow/child_workflow_handle.rb +10 -2
  132. data/lib/temporalio/workflow/definition.rb +183 -33
  133. data/lib/temporalio/workflow/external_workflow_handle.rb +3 -1
  134. data/lib/temporalio/workflow/info.rb +4 -1
  135. data/lib/temporalio/workflow.rb +61 -9
  136. data/lib/temporalio.rb +1 -0
  137. data/temporalio.gemspec +1 -0
  138. metadata +12 -3
@@ -94,16 +94,16 @@ module Temporalio
94
94
  enum_val
95
95
  end
96
96
 
97
- def self.convert_from_payload_array(converter, payloads)
97
+ def self.convert_from_payload_array(converter, payloads, hints:)
98
98
  return [] if payloads.empty?
99
99
 
100
- converter.from_payloads(Api::Common::V1::Payloads.new(payloads:))
100
+ converter.from_payloads(Api::Common::V1::Payloads.new(payloads:), hints:)
101
101
  end
102
102
 
103
- def self.convert_to_payload_array(converter, values)
103
+ def self.convert_to_payload_array(converter, values, hints:)
104
104
  return [] if values.empty?
105
105
 
106
- converter.to_payloads(values).payloads.to_ary
106
+ converter.to_payloads(values, hints:).payloads.to_ary
107
107
  end
108
108
 
109
109
  def self.assert_non_reserved_name(name)
@@ -148,10 +148,18 @@ module Temporalio
148
148
  @scoped_logger.warn("Cannot find activity to cancel for token #{task_token}")
149
149
  return
150
150
  end
151
- activity._server_requested_cancel = true
152
- _, cancel_proc = activity.cancellation
153
151
  begin
154
- cancel_proc.call(reason: cancel.reason.to_s)
152
+ activity._cancel(
153
+ reason: cancel.reason.to_s,
154
+ details: Activity::CancellationDetails.new(
155
+ gone_from_server: cancel.details.is_not_found,
156
+ cancel_requested: cancel.details.is_cancelled,
157
+ timed_out: cancel.details.is_timed_out,
158
+ worker_shutdown: cancel.details.is_worker_shutdown,
159
+ paused: cancel.details.is_paused,
160
+ reset: cancel.details.is_reset
161
+ )
162
+ )
155
163
  rescue StandardError => e
156
164
  @scoped_logger.warn("Failed cancelling activity #{activity.info.activity_type} \
157
165
  with ID #{activity.info.activity_id}")
@@ -168,12 +176,15 @@ module Temporalio
168
176
  current_attempt_scheduled_time: Internal::ProtoUtils.timestamp_to_time(
169
177
  start.current_attempt_scheduled_time
170
178
  ) || raise, # Never nil
171
- heartbeat_details: ProtoUtils.convert_from_payload_array(
172
- @worker.options.client.data_converter,
173
- start.heartbeat_details.to_ary
174
- ),
175
179
  heartbeat_timeout: Internal::ProtoUtils.duration_to_seconds(start.heartbeat_timeout),
176
180
  local?: start.is_local,
181
+ priority: Priority._from_proto(start.priority),
182
+ raw_heartbeat_details: begin
183
+ payloads = start.heartbeat_details.to_ary
184
+ codec = @worker.options.client.data_converter.payload_codec
185
+ payloads = codec.decode(payloads) if codec
186
+ payloads.map { |p| Temporalio::Converters::RawValue.new(p) }
187
+ end,
177
188
  schedule_to_close_timeout: Internal::ProtoUtils.duration_to_seconds(start.schedule_to_close_timeout),
178
189
  scheduled_time: Internal::ProtoUtils.timestamp_to_time(start.scheduled_time) || raise, # Never nil
179
190
  start_to_close_timeout: Internal::ProtoUtils.duration_to_seconds(start.start_to_close_timeout),
@@ -184,7 +195,7 @@ module Temporalio
184
195
  workflow_namespace: start.workflow_namespace,
185
196
  workflow_run_id: start.workflow_execution.run_id,
186
197
  workflow_type: start.workflow_type
187
- ).freeze
198
+ )
188
199
 
189
200
  # Build input
190
201
  input = Temporalio::Worker::Interceptor::Activity::ExecuteInput.new(
@@ -196,8 +207,13 @@ module Temporalio
196
207
  payloads = codec.decode(payloads) if codec
197
208
  payloads.map { |p| Temporalio::Converters::RawValue.new(p) }
198
209
  else
199
- ProtoUtils.convert_from_payload_array(@worker.options.client.data_converter, start.input.to_ary)
210
+ ProtoUtils.convert_from_payload_array(
211
+ @worker.options.client.data_converter,
212
+ start.input.to_ary,
213
+ hints: defn.arg_hints
214
+ )
200
215
  end,
216
+ result_hint: defn.result_hint,
201
217
  headers: ProtoUtils.headers_from_proto_map(start.header_fields, @worker.options.client.data_converter) || {}
202
218
  )
203
219
 
@@ -250,7 +266,7 @@ module Temporalio
250
266
  impl = @worker._activity_interceptors.reverse_each.reduce(impl) do |acc, int|
251
267
  int.intercept_activity(acc)
252
268
  end
253
- impl.init(OutboundImplementation.new(self))
269
+ impl.init(OutboundImplementation.new(self, activity.info.task_token))
254
270
 
255
271
  # Execute
256
272
  result = impl.execute(input)
@@ -258,7 +274,7 @@ module Temporalio
258
274
  # Success
259
275
  Bridge::Api::ActivityResult::ActivityExecutionResult.new(
260
276
  completed: Bridge::Api::ActivityResult::Success.new(
261
- result: @worker.options.client.data_converter.to_payload(result)
277
+ result: @worker.options.client.data_converter.to_payload(result, hint: input.result_hint)
262
278
  )
263
279
  )
264
280
  rescue Exception => e # rubocop:disable Lint/RescueException -- We are intending to catch everything here
@@ -268,6 +284,30 @@ module Temporalio
268
284
  Bridge::Api::ActivityResult::ActivityExecutionResult.new(
269
285
  will_complete_async: Bridge::Api::ActivityResult::WillCompleteAsync.new
270
286
  )
287
+ elsif e.is_a?(Error::CanceledError) && activity.cancellation_details&.paused?
288
+ # Server requested pause
289
+ @scoped_logger.debug('Completing activity as failed due to exception caused by pause')
290
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
291
+ failed: Bridge::Api::ActivityResult::Failure.new(
292
+ failure: @worker.options.client.data_converter.to_failure(
293
+ Error._with_backtrace_and_cause(
294
+ Error::ApplicationError.new('Activity paused', type: 'ActivityPause'), backtrace: nil, cause: e
295
+ )
296
+ )
297
+ )
298
+ )
299
+ elsif e.is_a?(Error::CanceledError) && activity.cancellation_details&.reset?
300
+ # Server requested reset
301
+ @scoped_logger.debug('Completing activity as failed due to exception caused by reset')
302
+ Bridge::Api::ActivityResult::ActivityExecutionResult.new(
303
+ failed: Bridge::Api::ActivityResult::Failure.new(
304
+ failure: @worker.options.client.data_converter.to_failure(
305
+ Error._with_backtrace_and_cause(
306
+ Error::ApplicationError.new('Activity reset', type: 'ActivityReset'), backtrace: nil, cause: e
307
+ )
308
+ )
309
+ )
310
+ )
271
311
  elsif e.is_a?(Error::CanceledError) && activity._server_requested_cancel
272
312
  # Server requested cancel
273
313
  @scoped_logger.debug('Completing activity as canceled')
@@ -278,8 +318,13 @@ module Temporalio
278
318
  )
279
319
  else
280
320
  # General failure
281
- @scoped_logger.warn('Completing activity as failed')
282
- @scoped_logger.warn(e)
321
+ log_level = if e.is_a?(Error::ApplicationError) && e.category == Error::ApplicationError::Category::BENIGN
322
+ Logger::DEBUG
323
+ else
324
+ Logger::WARN
325
+ end
326
+ @scoped_logger.add(log_level, 'Completing activity as failed')
327
+ @scoped_logger.add(log_level, e)
283
328
  Bridge::Api::ActivityResult::ActivityExecutionResult.new(
284
329
  failed: Bridge::Api::ActivityResult::Failure.new(
285
330
  failure: @worker.options.client.data_converter.to_failure(e)
@@ -297,9 +342,21 @@ module Temporalio
297
342
  )
298
343
  end
299
344
 
345
+ def assert_valid_activity(activity)
346
+ defn = @activities[activity]
347
+ defn = @activities[nil] if !defn && !Internal::ProtoUtils.reserved_name?(activity)
348
+
349
+ return unless defn.nil?
350
+
351
+ raise ArgumentError,
352
+ "Activity #{activity} " \
353
+ "is not registered on this worker, available activities: #{@activities.keys.sort.join(', ')}"
354
+ end
355
+
300
356
  class RunningActivity < Activity::Context
301
- attr_reader :info, :cancellation, :worker_shutdown_cancellation, :payload_converter, :logger
302
- attr_accessor :instance, :_outbound_impl, :_server_requested_cancel
357
+ attr_reader :info, :cancellation, :cancellation_details, :worker_shutdown_cancellation,
358
+ :payload_converter, :logger, :_server_requested_cancel
359
+ attr_accessor :instance, :_outbound_impl
303
360
 
304
361
  def initialize( # rubocop:disable Lint/MissingSuper
305
362
  worker:,
@@ -313,6 +370,7 @@ module Temporalio
313
370
  @worker = worker
314
371
  @info = info
315
372
  @cancellation = cancellation
373
+ @cancellation_details = nil
316
374
  @worker_shutdown_cancellation = worker_shutdown_cancellation
317
375
  @payload_converter = payload_converter
318
376
  @logger = logger
@@ -321,13 +379,15 @@ module Temporalio
321
379
  @_server_requested_cancel = false
322
380
  end
323
381
 
324
- def heartbeat(*details)
382
+ def heartbeat(*details, detail_hints: nil)
325
383
  raise 'Implementation not set yet' if _outbound_impl.nil?
326
384
 
327
385
  # No-op if local
328
386
  return if info.local?
329
387
 
330
- _outbound_impl.heartbeat(Temporalio::Worker::Interceptor::Activity::HeartbeatInput.new(details:))
388
+ _outbound_impl.heartbeat(
389
+ Temporalio::Worker::Interceptor::Activity::HeartbeatInput.new(details:, detail_hints:)
390
+ )
331
391
  end
332
392
 
333
393
  def metric_meter
@@ -343,6 +403,17 @@ module Temporalio
343
403
  def client
344
404
  @worker.client
345
405
  end
406
+
407
+ def _cancel(reason:, details:)
408
+ # Do not issue cancel if already canceled
409
+ return if @cancellation_details
410
+
411
+ @_server_requested_cancel = true
412
+ # Set the cancellation details _before_ issuing the cancel itself
413
+ @cancellation_details = details
414
+ _, cancel_proc = cancellation
415
+ cancel_proc.call(reason:)
416
+ end
346
417
  end
347
418
 
348
419
  class InboundImplementation < Temporalio::Worker::Interceptor::Activity::Inbound
@@ -364,17 +435,19 @@ module Temporalio
364
435
  end
365
436
 
366
437
  class OutboundImplementation < Temporalio::Worker::Interceptor::Activity::Outbound
367
- def initialize(worker)
438
+ def initialize(worker, task_token)
368
439
  super(nil) # steep:ignore
369
440
  @worker = worker
441
+ @task_token = task_token
370
442
  end
371
443
 
372
444
  def heartbeat(input)
373
445
  @worker.bridge_worker.record_activity_heartbeat(
374
446
  Bridge::Api::CoreInterface::ActivityHeartbeat.new(
375
- task_token: Activity::Context.current.info.task_token,
447
+ task_token: @task_token,
376
448
  details: ProtoUtils.convert_to_payload_array(@worker.worker.options.client.data_converter,
377
- input.details)
449
+ input.details,
450
+ hints: input.detail_hints)
378
451
  ).to_proto
379
452
  )
380
453
  end
@@ -10,18 +10,20 @@ module Temporalio
10
10
  class WorkflowInstance
11
11
  # Implementation of the child workflow handle.
12
12
  class ChildWorkflowHandle < Workflow::ChildWorkflowHandle
13
- attr_reader :id, :first_execution_run_id
13
+ attr_reader :id, :first_execution_run_id, :result_hint
14
14
 
15
- def initialize(id:, first_execution_run_id:, instance:, cancellation:, cancel_callback_key:) # rubocop:disable Lint/MissingSuper
15
+ def initialize(id:, first_execution_run_id:, instance:, # rubocop:disable Lint/MissingSuper
16
+ cancellation:, cancel_callback_key:, result_hint:)
16
17
  @id = id
17
18
  @first_execution_run_id = first_execution_run_id
18
19
  @instance = instance
19
20
  @cancellation = cancellation
20
21
  @cancel_callback_key = cancel_callback_key
22
+ @result_hint = result_hint
21
23
  @resolution = nil
22
24
  end
23
25
 
24
- def result
26
+ def result(result_hint: nil)
25
27
  # Notice that we actually provide a detached cancellation here instead of defaulting to workflow
26
28
  # cancellation because we don't want workflow cancellation (or a user-provided cancellation to this result
27
29
  # call) to be able to interrupt waiting on a child that may be processing the cancellation.
@@ -29,7 +31,7 @@ module Temporalio
29
31
 
30
32
  case @resolution.status
31
33
  when :completed
32
- @instance.payload_converter.from_payload(@resolution.completed.result)
34
+ @instance.payload_converter.from_payload(@resolution.completed.result, hint: result_hint || @result_hint)
33
35
  when :failed
34
36
  raise @instance.failure_converter.from_failure(@resolution.failed.failure, @instance.payload_converter)
35
37
  when :cancelled
@@ -44,8 +46,8 @@ module Temporalio
44
46
  @resolution = resolution
45
47
  end
46
48
 
47
- def signal(signal, *args, cancellation: Workflow.cancellation)
48
- @instance.context._signal_child_workflow(id:, signal:, args:, cancellation:)
49
+ def signal(signal, *args, cancellation: Workflow.cancellation, arg_hints: nil)
50
+ @instance.context._signal_child_workflow(id:, signal:, args:, cancellation:, arg_hints:)
49
51
  end
50
52
  end
51
53
  end
@@ -42,6 +42,10 @@ module Temporalio
42
42
  @instance.current_details = (details || '')
43
43
  end
44
44
 
45
+ def current_deployment_version
46
+ @instance.current_deployment_version
47
+ end
48
+
45
49
  def current_history_length
46
50
  @instance.current_history_length
47
51
  end
@@ -58,6 +62,16 @@ module Temporalio
58
62
  @instance.patch(patch_id:, deprecated: true)
59
63
  end
60
64
 
65
+ def durable_scheduler_disabled(&)
66
+ prev = Fiber.current_scheduler
67
+ illegal_call_tracing_disabled { Fiber.set_scheduler(nil) }
68
+ begin
69
+ yield
70
+ ensure
71
+ illegal_call_tracing_disabled { Fiber.set_scheduler(prev) }
72
+ end
73
+ end
74
+
61
75
  def execute_activity(
62
76
  activity,
63
77
  *args,
@@ -71,16 +85,22 @@ module Temporalio
71
85
  cancellation:,
72
86
  cancellation_type:,
73
87
  activity_id:,
74
- disable_eager_execution:
88
+ disable_eager_execution:,
89
+ priority:,
90
+ arg_hints:,
91
+ result_hint:
75
92
  )
76
- activity = case activity
77
- when Class
78
- Activity::Definition::Info.from_activity(activity).name&.to_s
79
- when Symbol, String
80
- activity.to_s
81
- else
82
- raise ArgumentError, 'Activity must be a definition class, or a symbol/string'
83
- end
93
+ activity, defn_arg_hints, defn_result_hint =
94
+ case activity
95
+ when Class
96
+ defn = Activity::Definition::Info.from_activity(activity)
97
+ [defn.name&.to_s, defn.arg_hints, defn.result_hint]
98
+ when Symbol, String
99
+ [activity.to_s, nil, nil]
100
+ else
101
+ raise ArgumentError,
102
+ 'Activity must be a definition class, or a symbol/string'
103
+ end
84
104
  raise 'Cannot invoke dynamic activities' unless activity
85
105
 
86
106
  @outbound.execute_activity(
@@ -98,6 +118,9 @@ module Temporalio
98
118
  cancellation_type:,
99
119
  activity_id:,
100
120
  disable_eager_execution: disable_eager_execution || @instance.disable_eager_activity_execution,
121
+ priority:,
122
+ arg_hints: arg_hints || defn_arg_hints,
123
+ result_hint: result_hint || defn_result_hint,
101
124
  headers: {}
102
125
  )
103
126
  )
@@ -113,16 +136,20 @@ module Temporalio
113
136
  local_retry_threshold:,
114
137
  cancellation:,
115
138
  cancellation_type:,
116
- activity_id:
139
+ activity_id:,
140
+ arg_hints:,
141
+ result_hint:
117
142
  )
118
- activity = case activity
119
- when Class
120
- Activity::Definition::Info.from_activity(activity).name&.to_s
121
- when Symbol, String
122
- activity.to_s
123
- else
124
- raise ArgumentError, 'Activity must be a definition class, or a symbol/string'
125
- end
143
+ activity, defn_arg_hints, defn_result_hint =
144
+ case activity
145
+ when Class
146
+ defn = Activity::Definition::Info.from_activity(activity)
147
+ [defn.name&.to_s, defn.arg_hints, defn.result_hint]
148
+ when Symbol, String
149
+ [activity.to_s, nil, nil]
150
+ else
151
+ raise ArgumentError, 'Activity must be a definition class, or a symbol/string'
152
+ end
126
153
  raise 'Cannot invoke dynamic activities' unless activity
127
154
 
128
155
  @outbound.execute_local_activity(
@@ -137,6 +164,8 @@ module Temporalio
137
164
  cancellation:,
138
165
  cancellation_type:,
139
166
  activity_id:,
167
+ arg_hints: arg_hints || defn_arg_hints,
168
+ result_hint: result_hint || defn_result_hint,
140
169
  headers: {}
141
170
  )
142
171
  )
@@ -245,11 +274,16 @@ module Temporalio
245
274
  retry_policy:,
246
275
  cron_schedule:,
247
276
  memo:,
248
- search_attributes:
277
+ search_attributes:,
278
+ priority:,
279
+ arg_hints:,
280
+ result_hint:
249
281
  )
282
+ workflow, defn_arg_hints, defn_result_hint =
283
+ Workflow::Definition._workflow_type_and_hints_from_workflow_parameter(workflow)
250
284
  @outbound.start_child_workflow(
251
285
  Temporalio::Worker::Interceptor::Workflow::StartChildWorkflowInput.new(
252
- workflow: Workflow::Definition._workflow_type_from_workflow_parameter(workflow),
286
+ workflow:,
253
287
  args:,
254
288
  id:,
255
289
  task_queue:,
@@ -266,6 +300,9 @@ module Temporalio
266
300
  cron_schedule:,
267
301
  memo:,
268
302
  search_attributes:,
303
+ priority:,
304
+ arg_hints: arg_hints || defn_arg_hints,
305
+ result_hint: result_hint || defn_result_hint,
269
306
  headers: {}
270
307
  )
271
308
  )
@@ -352,26 +389,30 @@ module Temporalio
352
389
  @outbound = outbound
353
390
  end
354
391
 
355
- def _signal_child_workflow(id:, signal:, args:, cancellation:)
392
+ def _signal_child_workflow(id:, signal:, args:, cancellation:, arg_hints:)
393
+ signal, defn_arg_hints = Workflow::Definition::Signal._name_and_hints_from_parameter(signal)
356
394
  @outbound.signal_child_workflow(
357
395
  Temporalio::Worker::Interceptor::Workflow::SignalChildWorkflowInput.new(
358
396
  id:,
359
- signal: Workflow::Definition::Signal._name_from_parameter(signal),
397
+ signal:,
360
398
  args:,
361
399
  cancellation:,
400
+ arg_hints: arg_hints || defn_arg_hints,
362
401
  headers: {}
363
402
  )
364
403
  )
365
404
  end
366
405
 
367
- def _signal_external_workflow(id:, run_id:, signal:, args:, cancellation:)
406
+ def _signal_external_workflow(id:, run_id:, signal:, args:, cancellation:, arg_hints:)
407
+ signal, defn_arg_hints = Workflow::Definition::Signal._name_and_hints_from_parameter(signal)
368
408
  @outbound.signal_external_workflow(
369
409
  Temporalio::Worker::Interceptor::Workflow::SignalExternalWorkflowInput.new(
370
410
  id:,
371
411
  run_id:,
372
- signal: Workflow::Definition::Signal._name_from_parameter(signal),
412
+ signal:,
373
413
  args:,
374
414
  cancellation:,
415
+ arg_hints: arg_hints || defn_arg_hints,
375
416
  headers: {}
376
417
  )
377
418
  )
@@ -8,7 +8,8 @@ module Temporalio
8
8
  class Details
9
9
  attr_reader :namespace, :task_queue, :definition, :initial_activation, :logger, :metric_meter,
10
10
  :payload_converter, :failure_converter, :interceptors, :disable_eager_activity_execution,
11
- :illegal_calls, :workflow_failure_exception_types, :unsafe_workflow_io_enabled
11
+ :illegal_calls, :workflow_failure_exception_types, :unsafe_workflow_io_enabled,
12
+ :assert_valid_local_activity
12
13
 
13
14
  def initialize(
14
15
  namespace:,
@@ -23,7 +24,8 @@ module Temporalio
23
24
  disable_eager_activity_execution:,
24
25
  illegal_calls:,
25
26
  workflow_failure_exception_types:,
26
- unsafe_workflow_io_enabled:
27
+ unsafe_workflow_io_enabled:,
28
+ assert_valid_local_activity:
27
29
  )
28
30
  @namespace = namespace
29
31
  @task_queue = task_queue
@@ -38,6 +40,7 @@ module Temporalio
38
40
  @illegal_calls = illegal_calls
39
41
  @workflow_failure_exception_types = workflow_failure_exception_types
40
42
  @unsafe_workflow_io_enabled = unsafe_workflow_io_enabled
43
+ @assert_valid_local_activity = assert_valid_local_activity
41
44
  end
42
45
  end
43
46
  end
@@ -18,8 +18,8 @@ module Temporalio
18
18
  @instance = instance
19
19
  end
20
20
 
21
- def signal(signal, *args, cancellation: Workflow.cancellation)
22
- @instance.context._signal_external_workflow(id:, run_id:, signal:, args:, cancellation:)
21
+ def signal(signal, *args, cancellation: Workflow.cancellation, arg_hints: nil)
22
+ @instance.context._signal_external_workflow(id:, run_id:, signal:, args:, cancellation:, arg_hints:)
23
23
  end
24
24
 
25
25
  def cancel
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'temporalio/worker/illegal_workflow_call_validator'
3
4
  require 'temporalio/workflow'
4
5
 
5
6
  module Temporalio
@@ -12,16 +13,32 @@ module Temporalio
12
13
  illegal_calls.to_h do |key, val|
13
14
  raise TypeError, 'Invalid illegal call map, top-level key must be a String' unless key.is_a?(String)
14
15
 
15
- # @type var fixed_val: :all | Hash[Symbol, bool]
16
+ # @type var fixed_val: :all | Worker::IllegalWorkflowCallValidator | Hash[Symbol, TrueClass | Worker::IllegalWorkflowCallValidator] # rubocop:disable Layout/LineLength
16
17
  fixed_val = case val
18
+ when Temporalio::Worker::IllegalWorkflowCallValidator
19
+ if sub_val.method_name
20
+ raise ArgumentError,
21
+ 'Top level IllegalWorkflowCallValidator instances cannot have method name'
22
+ end
23
+ val
17
24
  when Array
18
25
  val.to_h do |sub_val|
19
- unless sub_val.is_a?(Symbol)
26
+ case sub_val
27
+ when Symbol
28
+ [sub_val, true]
29
+ when Temporalio::Worker::IllegalWorkflowCallValidator
30
+ unless sub_val.method_name
31
+ raise ArgumentError,
32
+ 'IllegalWorkflowCallValidator instances in array for ' \
33
+ "#{key} must have a method name"
34
+ end
35
+
36
+ [sub_val.method_name, sub_val]
37
+ else
20
38
  raise TypeError,
21
- 'Invalid illegal call map, each value must be a Symbol'
39
+ 'Invalid illegal call array entry for ' \
40
+ "#{key}, each value must be a Symbol or an IllegalWorkflowCallValidator"
22
41
  end
23
-
24
- [sub_val, true]
25
42
  end.freeze
26
43
  when :all
27
44
  :all
@@ -47,25 +64,54 @@ module Temporalio
47
64
  # class of things like `Date` does not have `attached_object` so you have to fall back in these rare cases
48
65
  # to parsing the string output. Reaching the string parsing component is rare, so this should not have
49
66
  # significant performance impact.
50
- cls_name = if cls.singleton_class?
51
- if cls.respond_to?(:attached_object)
52
- cls = cls.attached_object # steep:ignore
53
- next unless cls.is_a?(Module)
67
+ class_name = if cls.singleton_class?
68
+ if cls.respond_to?(:attached_object)
69
+ cls = cls.attached_object # steep:ignore
70
+ next unless cls.is_a?(Module)
54
71
 
55
- cls.name.to_s
72
+ cls.name.to_s
73
+ else
74
+ cls.to_s.delete_prefix('#<Class:').delete_suffix('>')
75
+ end
56
76
  else
57
- cls.to_s.delete_prefix('#<Class:').delete_suffix('>')
77
+ cls.name.to_s
58
78
  end
59
- else
60
- cls.name.to_s
61
- end
62
79
 
63
80
  # Check if the call is considered illegal
64
- vals = illegal_calls[cls_name]
65
- if vals == :all || vals&.[](tp.callee_id) # steep:ignore
81
+ vals = illegal_calls[class_name]
82
+ invalid_suffix =
83
+ case vals
84
+ when :all
85
+ ''
86
+ when Temporalio::Worker::IllegalWorkflowCallValidator
87
+ disable do
88
+ vals.block.call(Temporalio::Worker::IllegalWorkflowCallValidator::CallInfo.new(
89
+ class_name:, method_name: tp.callee_id, trace_point: tp
90
+ ))
91
+ nil
92
+ rescue Exception => e # rubocop:disable Lint/RescueException
93
+ ", reason: #{e}"
94
+ end
95
+ else
96
+ per_method = vals&.[](tp.callee_id)
97
+ case per_method
98
+ when true
99
+ ''
100
+ when Temporalio::Worker::IllegalWorkflowCallValidator
101
+ disable do
102
+ per_method.block.call(Temporalio::Worker::IllegalWorkflowCallValidator::CallInfo.new(
103
+ class_name:, method_name: tp.callee_id, trace_point: tp
104
+ ))
105
+ nil
106
+ rescue Exception => e # rubocop:disable Lint/RescueException
107
+ ", reason: #{e}"
108
+ end
109
+ end
110
+ end
111
+ if invalid_suffix
66
112
  raise Workflow::NondeterminismError,
67
- "Cannot access #{cls_name} #{tp.callee_id} from inside a " \
68
- 'workflow. If this is known to be safe, the code can be run in ' \
113
+ "Cannot access #{class_name} #{tp.callee_id} from inside a " \
114
+ "workflow#{invalid_suffix}. If this is known to be safe, the code can be run in " \
69
115
  'a Temporalio::Workflow::Unsafe.illegal_call_tracing_disabled block.'
70
116
  end
71
117
  end