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,383 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/cancellation'
4
+ require 'temporalio/error'
5
+ require 'temporalio/internal/bridge/api'
6
+ require 'temporalio/internal/proto_utils'
7
+ require 'temporalio/internal/worker/workflow_instance'
8
+ require 'temporalio/internal/worker/workflow_instance/external_workflow_handle'
9
+ require 'temporalio/worker/interceptor'
10
+ require 'temporalio/workflow'
11
+
12
+ module Temporalio
13
+ module Internal
14
+ module Worker
15
+ class WorkflowInstance
16
+ # Context for all workflow calls. All calls in the {Workflow} class should call a method on this class and then
17
+ # this class can delegate the call as needed to other parts of the workflow instance system.
18
+ class Context
19
+ def initialize(instance)
20
+ @instance = instance
21
+ end
22
+
23
+ def all_handlers_finished?
24
+ @instance.in_progress_handlers.empty?
25
+ end
26
+
27
+ def cancellation
28
+ @instance.cancellation
29
+ end
30
+
31
+ def continue_as_new_suggested
32
+ @instance.continue_as_new_suggested
33
+ end
34
+
35
+ def current_details
36
+ @instance.current_details || ''
37
+ end
38
+
39
+ def current_details=(details)
40
+ raise 'Details must be a String' unless details.nil? || details.is_a?(String)
41
+
42
+ @instance.current_details = (details || '')
43
+ end
44
+
45
+ def current_history_length
46
+ @instance.current_history_length
47
+ end
48
+
49
+ def current_history_size
50
+ @instance.current_history_size
51
+ end
52
+
53
+ def current_update_info
54
+ Fiber[:__temporal_update_info]
55
+ end
56
+
57
+ def deprecate_patch(patch_id)
58
+ @instance.patch(patch_id:, deprecated: true)
59
+ end
60
+
61
+ def execute_activity(
62
+ activity,
63
+ *args,
64
+ task_queue:,
65
+ summary:,
66
+ schedule_to_close_timeout:,
67
+ schedule_to_start_timeout:,
68
+ start_to_close_timeout:,
69
+ heartbeat_timeout:,
70
+ retry_policy:,
71
+ cancellation:,
72
+ cancellation_type:,
73
+ activity_id:,
74
+ disable_eager_execution:
75
+ )
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
84
+ raise 'Cannot invoke dynamic activities' unless activity
85
+
86
+ @outbound.execute_activity(
87
+ Temporalio::Worker::Interceptor::Workflow::ExecuteActivityInput.new(
88
+ activity:,
89
+ args:,
90
+ task_queue: task_queue || info.task_queue,
91
+ summary:,
92
+ schedule_to_close_timeout:,
93
+ schedule_to_start_timeout:,
94
+ start_to_close_timeout:,
95
+ heartbeat_timeout:,
96
+ retry_policy:,
97
+ cancellation:,
98
+ cancellation_type:,
99
+ activity_id:,
100
+ disable_eager_execution: disable_eager_execution || @instance.disable_eager_activity_execution,
101
+ headers: {}
102
+ )
103
+ )
104
+ end
105
+
106
+ def execute_local_activity(
107
+ activity,
108
+ *args,
109
+ schedule_to_close_timeout:,
110
+ schedule_to_start_timeout:,
111
+ start_to_close_timeout:,
112
+ retry_policy:,
113
+ local_retry_threshold:,
114
+ cancellation:,
115
+ cancellation_type:,
116
+ activity_id:
117
+ )
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
126
+ raise 'Cannot invoke dynamic activities' unless activity
127
+
128
+ @outbound.execute_local_activity(
129
+ Temporalio::Worker::Interceptor::Workflow::ExecuteLocalActivityInput.new(
130
+ activity:,
131
+ args:,
132
+ schedule_to_close_timeout:,
133
+ schedule_to_start_timeout:,
134
+ start_to_close_timeout:,
135
+ retry_policy:,
136
+ local_retry_threshold:,
137
+ cancellation:,
138
+ cancellation_type:,
139
+ activity_id:,
140
+ headers: {}
141
+ )
142
+ )
143
+ end
144
+
145
+ def external_workflow_handle(workflow_id, run_id: nil)
146
+ ExternalWorkflowHandle.new(id: workflow_id, run_id:, instance: @instance)
147
+ end
148
+
149
+ def illegal_call_tracing_disabled(&)
150
+ @instance.illegal_call_tracing_disabled(&)
151
+ end
152
+
153
+ def info
154
+ @instance.info
155
+ end
156
+
157
+ def instance
158
+ @instance.instance
159
+ end
160
+
161
+ def initialize_continue_as_new_error(error)
162
+ @outbound.initialize_continue_as_new_error(
163
+ Temporalio::Worker::Interceptor::Workflow::InitializeContinueAsNewErrorInput.new(error:)
164
+ )
165
+ end
166
+
167
+ def io_enabled(&)
168
+ prev = @instance.io_enabled
169
+ @instance.io_enabled = true
170
+ begin
171
+ yield
172
+ ensure
173
+ @instance.io_enabled = prev
174
+ end
175
+ end
176
+
177
+ def logger
178
+ @instance.logger
179
+ end
180
+
181
+ def memo
182
+ @instance.memo
183
+ end
184
+
185
+ def metric_meter
186
+ @instance.metric_meter
187
+ end
188
+
189
+ def now
190
+ @instance.now
191
+ end
192
+
193
+ def patched(patch_id)
194
+ @instance.patch(patch_id:, deprecated: false)
195
+ end
196
+
197
+ def payload_converter
198
+ @instance.payload_converter
199
+ end
200
+
201
+ def query_handlers
202
+ @instance.query_handlers
203
+ end
204
+
205
+ def random
206
+ @instance.random
207
+ end
208
+
209
+ def replaying?
210
+ @instance.replaying
211
+ end
212
+
213
+ def search_attributes
214
+ @instance.search_attributes
215
+ end
216
+
217
+ def signal_handlers
218
+ @instance.signal_handlers
219
+ end
220
+
221
+ def sleep(duration, summary:, cancellation:)
222
+ @outbound.sleep(
223
+ Temporalio::Worker::Interceptor::Workflow::SleepInput.new(
224
+ duration:,
225
+ summary:,
226
+ cancellation:
227
+ )
228
+ )
229
+ end
230
+
231
+ def start_child_workflow(
232
+ workflow,
233
+ *args,
234
+ id:,
235
+ task_queue:,
236
+ static_summary:,
237
+ static_details:,
238
+ cancellation:,
239
+ cancellation_type:,
240
+ parent_close_policy:,
241
+ execution_timeout:,
242
+ run_timeout:,
243
+ task_timeout:,
244
+ id_reuse_policy:,
245
+ retry_policy:,
246
+ cron_schedule:,
247
+ memo:,
248
+ search_attributes:
249
+ )
250
+ @outbound.start_child_workflow(
251
+ Temporalio::Worker::Interceptor::Workflow::StartChildWorkflowInput.new(
252
+ workflow: Workflow::Definition._workflow_type_from_workflow_parameter(workflow),
253
+ args:,
254
+ id:,
255
+ task_queue:,
256
+ static_summary:,
257
+ static_details:,
258
+ cancellation:,
259
+ cancellation_type:,
260
+ parent_close_policy:,
261
+ execution_timeout:,
262
+ run_timeout:,
263
+ task_timeout:,
264
+ id_reuse_policy:,
265
+ retry_policy:,
266
+ cron_schedule:,
267
+ memo:,
268
+ search_attributes:,
269
+ headers: {}
270
+ )
271
+ )
272
+ end
273
+
274
+ def storage
275
+ @storage ||= {}
276
+ end
277
+
278
+ def timeout(duration, exception_class, *exception_args, summary:, &)
279
+ raise 'Block required for timeout' unless block_given?
280
+
281
+ # Run timer in background and block in foreground. This gives better stack traces than a future any-of race.
282
+ # We make a detached cancellation because we don't want to link to workflow cancellation.
283
+ sleep_cancel, sleep_cancel_proc = Cancellation.new
284
+ fiber = Fiber.current
285
+ Workflow::Future.new do
286
+ Workflow.sleep(duration, summary:, cancellation: sleep_cancel)
287
+ fiber.raise(exception_class, *exception_args) if fiber.alive? # steep:ignore
288
+ rescue Exception => e # rubocop:disable Lint/RescueException
289
+ # Re-raise in fiber
290
+ fiber.raise(e) if fiber.alive?
291
+ end
292
+
293
+ begin
294
+ yield
295
+ ensure
296
+ sleep_cancel_proc.call
297
+ end
298
+ end
299
+
300
+ def update_handlers
301
+ @instance.update_handlers
302
+ end
303
+
304
+ def upsert_memo(hash)
305
+ # Convert to memo, apply updates, then add the command (so command adding is post validation)
306
+ upserted_memo = ProtoUtils.memo_to_proto(hash, payload_converter)
307
+ memo._update do |new_hash|
308
+ hash.each do |key, val|
309
+ # Nil means delete
310
+ if val.nil?
311
+ new_hash.delete(key.to_s)
312
+ else
313
+ new_hash[key.to_s] = val
314
+ end
315
+ end
316
+ end
317
+ @instance.add_command(
318
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
319
+ modify_workflow_properties: Bridge::Api::WorkflowCommands::ModifyWorkflowProperties.new(
320
+ upserted_memo:
321
+ )
322
+ )
323
+ )
324
+ end
325
+
326
+ def upsert_search_attributes(*updates)
327
+ # Apply updates then add the command (so command adding is post validation)
328
+ search_attributes._disable_mutations = false
329
+ search_attributes.update!(*updates)
330
+ @instance.add_command(
331
+ Bridge::Api::WorkflowCommands::WorkflowCommand.new(
332
+ upsert_workflow_search_attributes: Bridge::Api::WorkflowCommands::UpsertWorkflowSearchAttributes.new(
333
+ search_attributes: updates.to_h(&:_to_proto_pair)
334
+ )
335
+ )
336
+ )
337
+ ensure
338
+ search_attributes._disable_mutations = true
339
+ end
340
+
341
+ def wait_condition(cancellation:, &)
342
+ @instance.scheduler.wait_condition(cancellation:, &)
343
+ end
344
+
345
+ def _cancel_external_workflow(id:, run_id:)
346
+ @outbound.cancel_external_workflow(
347
+ Temporalio::Worker::Interceptor::Workflow::CancelExternalWorkflowInput.new(id:, run_id:)
348
+ )
349
+ end
350
+
351
+ def _outbound=(outbound)
352
+ @outbound = outbound
353
+ end
354
+
355
+ def _signal_child_workflow(id:, signal:, args:, cancellation:)
356
+ @outbound.signal_child_workflow(
357
+ Temporalio::Worker::Interceptor::Workflow::SignalChildWorkflowInput.new(
358
+ id:,
359
+ signal: Workflow::Definition::Signal._name_from_parameter(signal),
360
+ args:,
361
+ cancellation:,
362
+ headers: {}
363
+ )
364
+ )
365
+ end
366
+
367
+ def _signal_external_workflow(id:, run_id:, signal:, args:, cancellation:)
368
+ @outbound.signal_external_workflow(
369
+ Temporalio::Worker::Interceptor::Workflow::SignalExternalWorkflowInput.new(
370
+ id:,
371
+ run_id:,
372
+ signal: Workflow::Definition::Signal._name_from_parameter(signal),
373
+ args:,
374
+ cancellation:,
375
+ headers: {}
376
+ )
377
+ )
378
+ end
379
+ end
380
+ end
381
+ end
382
+ end
383
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Temporalio
4
+ module Internal
5
+ module Worker
6
+ class WorkflowInstance
7
+ # Details needed to instantiate a {WorkflowInstance}.
8
+ class Details
9
+ attr_reader :namespace, :task_queue, :definition, :initial_activation, :logger, :metric_meter,
10
+ :payload_converter, :failure_converter, :interceptors, :disable_eager_activity_execution,
11
+ :illegal_calls, :workflow_failure_exception_types, :unsafe_workflow_io_enabled
12
+
13
+ def initialize(
14
+ namespace:,
15
+ task_queue:,
16
+ definition:,
17
+ initial_activation:,
18
+ logger:,
19
+ metric_meter:,
20
+ payload_converter:,
21
+ failure_converter:,
22
+ interceptors:,
23
+ disable_eager_activity_execution:,
24
+ illegal_calls:,
25
+ workflow_failure_exception_types:,
26
+ unsafe_workflow_io_enabled:
27
+ )
28
+ @namespace = namespace
29
+ @task_queue = task_queue
30
+ @definition = definition
31
+ @initial_activation = initial_activation
32
+ @logger = logger
33
+ @metric_meter = metric_meter
34
+ @payload_converter = payload_converter
35
+ @failure_converter = failure_converter
36
+ @interceptors = interceptors
37
+ @disable_eager_activity_execution = disable_eager_activity_execution
38
+ @illegal_calls = illegal_calls
39
+ @workflow_failure_exception_types = workflow_failure_exception_types
40
+ @unsafe_workflow_io_enabled = unsafe_workflow_io_enabled
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/cancellation'
4
+ require 'temporalio/workflow'
5
+ require 'temporalio/workflow/external_workflow_handle'
6
+
7
+ module Temporalio
8
+ module Internal
9
+ module Worker
10
+ class WorkflowInstance
11
+ # Implementation of the external workflow handle.
12
+ class ExternalWorkflowHandle < Workflow::ExternalWorkflowHandle
13
+ attr_reader :id, :run_id
14
+
15
+ def initialize(id:, run_id:, instance:) # rubocop:disable Lint/MissingSuper
16
+ @id = id
17
+ @run_id = run_id
18
+ @instance = instance
19
+ end
20
+
21
+ def signal(signal, *args, cancellation: Workflow.cancellation)
22
+ @instance.context._signal_external_workflow(id:, run_id:, signal:, args:, cancellation:)
23
+ end
24
+
25
+ def cancel
26
+ @instance.context._cancel_external_workflow(id:, run_id:)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Temporalio
4
+ module Internal
5
+ module Worker
6
+ class WorkflowInstance
7
+ # Delegator to a hash that does not allow external mutations. Used for memo.
8
+ class ExternallyImmutableHash < SimpleDelegator
9
+ def initialize(initial_hash)
10
+ super(initial_hash.freeze)
11
+ end
12
+
13
+ def _update(&)
14
+ new_hash = __getobj__.dup
15
+ yield new_hash
16
+ __setobj__(new_hash.freeze)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Temporalio
4
+ module Internal
5
+ module Worker
6
+ class WorkflowInstance
7
+ # Representation of a currently-executing handler. Used to track whether any handlers are still running and warn
8
+ # on workflow complete as needed.
9
+ class HandlerExecution
10
+ attr_reader :name, :update_id, :unfinished_policy
11
+
12
+ def initialize(
13
+ name:,
14
+ update_id:,
15
+ unfinished_policy:
16
+ )
17
+ @name = name
18
+ @update_id = update_id
19
+ @unfinished_policy = unfinished_policy
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Temporalio
4
+ module Internal
5
+ module Worker
6
+ class WorkflowInstance
7
+ # Hash for handlers that notifies when one is added. Only `[]=` and `store` can be used to mutate it.
8
+ class HandlerHash < SimpleDelegator
9
+ def initialize(initial_frozen_hash, definition_class, &on_new_definition)
10
+ super(initial_frozen_hash)
11
+ @definition_class = definition_class
12
+ @on_new_definition = on_new_definition
13
+ end
14
+
15
+ def []=(name, definition)
16
+ store(name, definition)
17
+ end
18
+
19
+ # steep:ignore:start
20
+ def store(name, definition)
21
+ raise ArgumentError, 'Name must be a string or nil' unless name.nil? || name.is_a?(String)
22
+
23
+ unless definition.nil? || definition.is_a?(@definition_class)
24
+ raise ArgumentError,
25
+ "Value must be a #{@definition_class.name} or nil"
26
+ end
27
+ raise ArgumentError, 'Name does not match one in definition' if definition && name != definition.name
28
+
29
+ # Do a copy-on-write op on the underlying frozen hash
30
+ new_hash = __getobj__.dup
31
+ new_hash[name] = definition
32
+ __setobj__(new_hash.freeze)
33
+ @on_new_definition&.call(definition) unless definition.nil?
34
+ definition
35
+ end
36
+ # steep:ignore:end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/workflow'
4
+
5
+ module Temporalio
6
+ module Internal
7
+ module Worker
8
+ class WorkflowInstance
9
+ # Class that installs {::TracePoint} to disallow illegal calls.
10
+ class IllegalCallTracer
11
+ def self.frozen_validated_illegal_calls(illegal_calls)
12
+ illegal_calls.to_h do |key, val|
13
+ raise TypeError, 'Invalid illegal call map, top-level key must be a String' unless key.is_a?(String)
14
+
15
+ # @type var fixed_val: :all | Hash[Symbol, bool]
16
+ fixed_val = case val
17
+ when Array
18
+ val.to_h do |sub_val|
19
+ unless sub_val.is_a?(Symbol)
20
+ raise TypeError,
21
+ 'Invalid illegal call map, each value must be a Symbol'
22
+ end
23
+
24
+ [sub_val, true]
25
+ end.freeze
26
+ when :all
27
+ :all
28
+ else
29
+ raise TypeError, 'Invalid illegal call map, top-level value must be an Array or :all'
30
+ end
31
+
32
+ [key.frozen? ? key : key.dup.freeze, fixed_val]
33
+ end.freeze
34
+ end
35
+
36
+ # Illegal calls are Hash[String, :all | Hash[Symbol, Bool]]
37
+ def initialize(illegal_calls)
38
+ @tracepoint = TracePoint.new(:call, :c_call) do |tp|
39
+ # Manual check for proper thread since we have seen issues in Ruby 3.2 where it leaks
40
+ next unless Thread.current == @enabled_thread
41
+
42
+ cls = tp.defined_class
43
+ next unless cls.is_a?(Module)
44
+
45
+ # Extract the class name from the defined class. This is more difficult than it seems because you have to
46
+ # resolve the attached object of the singleton class. But in older Ruby (at least <= 3.1), the singleton
47
+ # class of things like `Date` does not have `attached_object` so you have to fall back in these rare cases
48
+ # to parsing the string output. Reaching the string parsing component is rare, so this should not have
49
+ # 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)
54
+
55
+ cls.name.to_s
56
+ else
57
+ cls.to_s.delete_prefix('#<Class:').delete_suffix('>')
58
+ end
59
+ else
60
+ cls.name.to_s
61
+ end
62
+
63
+ # Check if the call is considered illegal
64
+ vals = illegal_calls[cls_name]
65
+ if vals == :all || vals&.[](tp.callee_id) # steep:ignore
66
+ 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 ' \
69
+ 'a Temporalio::Workflow::Unsafe.illegal_call_tracing_disabled block.'
70
+ end
71
+ end
72
+ end
73
+
74
+ def enable(&block)
75
+ # We've seen leaking issues in Ruby 3.2 where the TracePoint inadvertently remains enabled even for threads
76
+ # that it was not started on. So we will check the thread ourselves.
77
+ @enabled_thread = Thread.current
78
+ @tracepoint.enable do
79
+ block.call
80
+ ensure
81
+ @enabled_thread = nil
82
+ end
83
+ end
84
+
85
+ def disable(&block)
86
+ previous_thread = @enabled_thread
87
+ @tracepoint.disable do
88
+ block.call
89
+ ensure
90
+ @enabled_thread = previous_thread
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end