temporalio 0.3.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.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/Cargo.lock +1019 -669
- data/Cargo.toml +5 -5
- data/Gemfile +4 -0
- data/README.md +281 -44
- data/Rakefile +1 -1
- data/ext/Cargo.toml +4 -3
- data/lib/temporalio/activity/cancellation_details.rb +58 -0
- data/lib/temporalio/activity/context.rb +23 -1
- data/lib/temporalio/activity/definition.rb +63 -8
- data/lib/temporalio/activity/info.rb +28 -4
- data/lib/temporalio/activity.rb +2 -0
- data/lib/temporalio/api/activity/v1/message.rb +1 -1
- data/lib/temporalio/api/batch/v1/message.rb +9 -2
- data/lib/temporalio/api/cloud/account/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +11 -2
- data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +2 -2
- data/lib/temporalio/api/cloud/identity/v1/message.rb +7 -2
- data/lib/temporalio/api/cloud/namespace/v1/message.rb +6 -2
- data/lib/temporalio/api/cloud/nexus/v1/message.rb +3 -2
- data/lib/temporalio/api/cloud/operation/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/region/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/resource/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/sink/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/usage/v1/message.rb +1 -1
- data/lib/temporalio/api/command/v1/message.rb +2 -2
- data/lib/temporalio/api/common/v1/grpc_status.rb +1 -1
- data/lib/temporalio/api/common/v1/message.rb +4 -2
- data/lib/temporalio/api/deployment/v1/message.rb +39 -0
- data/lib/temporalio/api/enums/v1/batch_operation.rb +2 -2
- data/lib/temporalio/api/enums/v1/command_type.rb +1 -1
- data/lib/temporalio/api/enums/v1/common.rb +5 -2
- data/lib/temporalio/api/enums/v1/deployment.rb +24 -0
- data/lib/temporalio/api/enums/v1/event_type.rb +2 -2
- data/lib/temporalio/api/enums/v1/failed_cause.rb +2 -2
- data/lib/temporalio/api/enums/v1/namespace.rb +1 -1
- data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
- data/lib/temporalio/api/enums/v1/query.rb +1 -1
- data/lib/temporalio/api/enums/v1/reset.rb +2 -2
- data/lib/temporalio/api/enums/v1/schedule.rb +1 -1
- data/lib/temporalio/api/enums/v1/task_queue.rb +1 -1
- data/lib/temporalio/api/enums/v1/update.rb +1 -1
- data/lib/temporalio/api/enums/v1/workflow.rb +3 -2
- data/lib/temporalio/api/errordetails/v1/message.rb +4 -2
- data/lib/temporalio/api/export/v1/message.rb +1 -1
- data/lib/temporalio/api/failure/v1/message.rb +5 -2
- data/lib/temporalio/api/filter/v1/message.rb +1 -1
- data/lib/temporalio/api/history/v1/message.rb +6 -2
- data/lib/temporalio/api/namespace/v1/message.rb +1 -1
- data/lib/temporalio/api/nexus/v1/message.rb +3 -2
- data/lib/temporalio/api/operatorservice/v1/request_response.rb +1 -1
- data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
- data/lib/temporalio/api/payload_visitor.rb +162 -7
- data/lib/temporalio/api/protocol/v1/message.rb +1 -1
- data/lib/temporalio/api/query/v1/message.rb +3 -2
- data/lib/temporalio/api/replication/v1/message.rb +1 -1
- data/lib/temporalio/api/rules/v1/message.rb +27 -0
- data/lib/temporalio/api/schedule/v1/message.rb +2 -2
- data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +1 -1
- data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +1 -1
- data/lib/temporalio/api/sdk/v1/user_metadata.rb +1 -1
- data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +1 -1
- data/lib/temporalio/api/taskqueue/v1/message.rb +5 -2
- data/lib/temporalio/api/testservice/v1/request_response.rb +1 -1
- data/lib/temporalio/api/testservice/v1/service.rb +1 -1
- data/lib/temporalio/api/update/v1/message.rb +1 -1
- data/lib/temporalio/api/version/v1/message.rb +1 -1
- data/lib/temporalio/api/worker/v1/message.rb +30 -0
- data/lib/temporalio/api/workflow/v1/message.rb +22 -2
- data/lib/temporalio/api/workflowservice/v1/request_response.rb +58 -12
- data/lib/temporalio/api/workflowservice/v1/service.rb +2 -2
- data/lib/temporalio/api.rb +1 -0
- data/lib/temporalio/client/async_activity_handle.rb +12 -4
- data/lib/temporalio/client/connection/cloud_service.rb +60 -0
- data/lib/temporalio/client/connection/workflow_service.rb +343 -28
- data/lib/temporalio/client/interceptor.rb +64 -7
- data/lib/temporalio/client/schedule.rb +35 -3
- data/lib/temporalio/client/with_start_workflow_operation.rb +123 -0
- data/lib/temporalio/client/workflow_execution.rb +19 -0
- data/lib/temporalio/client/workflow_handle.rb +47 -7
- data/lib/temporalio/client/workflow_update_handle.rb +9 -3
- data/lib/temporalio/client.rb +231 -4
- data/lib/temporalio/common_enums.rb +14 -0
- data/lib/temporalio/contrib/open_telemetry.rb +474 -0
- data/lib/temporalio/converters/data_converter.rb +18 -8
- data/lib/temporalio/converters/failure_converter.rb +6 -3
- data/lib/temporalio/converters/payload_converter/binary_null.rb +2 -2
- data/lib/temporalio/converters/payload_converter/binary_plain.rb +2 -2
- data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +2 -2
- data/lib/temporalio/converters/payload_converter/composite.rb +6 -4
- data/lib/temporalio/converters/payload_converter/encoding.rb +4 -2
- data/lib/temporalio/converters/payload_converter/json_plain.rb +2 -2
- data/lib/temporalio/converters/payload_converter/json_protobuf.rb +2 -2
- data/lib/temporalio/converters/payload_converter.rb +16 -6
- data/lib/temporalio/error/failure.rb +19 -1
- data/lib/temporalio/error.rb +2 -1
- data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +1 -1
- data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +3 -2
- data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +1 -1
- data/lib/temporalio/internal/bridge/api/common/common.rb +3 -2
- data/lib/temporalio/internal/bridge/api/core_interface.rb +1 -1
- data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +1 -1
- data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +3 -2
- data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +2 -2
- data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +3 -2
- data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +3 -2
- data/lib/temporalio/internal/bridge/runtime.rb +3 -0
- data/lib/temporalio/internal/bridge/testing.rb +3 -0
- data/lib/temporalio/internal/bridge/worker.rb +28 -4
- data/lib/temporalio/internal/bridge.rb +1 -1
- data/lib/temporalio/internal/client/implementation.rb +281 -51
- data/lib/temporalio/internal/proto_utils.rb +38 -6
- data/lib/temporalio/internal/worker/activity_worker.rb +112 -27
- data/lib/temporalio/internal/worker/multi_runner.rb +2 -2
- data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +8 -6
- data/lib/temporalio/internal/worker/workflow_instance/context.rb +100 -5
- data/lib/temporalio/internal/worker/workflow_instance/details.rb +7 -2
- data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +2 -2
- data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +64 -18
- data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +39 -40
- data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +22 -2
- data/lib/temporalio/internal/worker/workflow_instance.rb +134 -55
- data/lib/temporalio/internal/worker/workflow_worker.rb +74 -21
- data/lib/temporalio/priority.rb +59 -0
- data/lib/temporalio/runtime/metric_buffer.rb +94 -0
- data/lib/temporalio/runtime.rb +48 -10
- data/lib/temporalio/search_attributes.rb +13 -0
- data/lib/temporalio/testing/activity_environment.rb +59 -16
- data/lib/temporalio/testing/workflow_environment.rb +29 -6
- data/lib/temporalio/version.rb +1 -1
- data/lib/temporalio/versioning_override.rb +56 -0
- data/lib/temporalio/worker/deployment_options.rb +45 -0
- data/lib/temporalio/worker/illegal_workflow_call_validator.rb +64 -0
- data/lib/temporalio/worker/interceptor.rb +16 -1
- data/lib/temporalio/worker/poller_behavior.rb +61 -0
- data/lib/temporalio/worker/thread_pool.rb +6 -6
- data/lib/temporalio/worker/tuner.rb +38 -0
- data/lib/temporalio/worker/workflow_executor/thread_pool.rb +14 -8
- data/lib/temporalio/worker/workflow_executor.rb +1 -1
- data/lib/temporalio/worker/workflow_replayer.rb +349 -0
- data/lib/temporalio/worker.rb +117 -75
- data/lib/temporalio/worker_deployment_version.rb +67 -0
- data/lib/temporalio/workflow/child_workflow_handle.rb +10 -2
- data/lib/temporalio/workflow/definition.rb +217 -35
- data/lib/temporalio/workflow/external_workflow_handle.rb +3 -1
- data/lib/temporalio/workflow/future.rb +2 -2
- data/lib/temporalio/workflow/info.rb +26 -1
- data/lib/temporalio/workflow.rb +119 -15
- data/lib/temporalio/workflow_history.rb +26 -1
- data/lib/temporalio.rb +1 -0
- data/temporalio.gemspec +3 -1
- metadata +36 -6
@@ -4,6 +4,7 @@ require 'json'
|
|
4
4
|
require 'temporalio'
|
5
5
|
require 'temporalio/activity/definition'
|
6
6
|
require 'temporalio/api'
|
7
|
+
require 'temporalio/converters/payload_converter'
|
7
8
|
require 'temporalio/converters/raw_value'
|
8
9
|
require 'temporalio/error'
|
9
10
|
require 'temporalio/internal/bridge/api'
|
@@ -23,6 +24,7 @@ require 'temporalio/internal/worker/workflow_instance/scheduler'
|
|
23
24
|
require 'temporalio/retry_policy'
|
24
25
|
require 'temporalio/scoped_logger'
|
25
26
|
require 'temporalio/worker/interceptor'
|
27
|
+
require 'temporalio/worker_deployment_version'
|
26
28
|
require 'temporalio/workflow/info'
|
27
29
|
require 'temporalio/workflow/update_info'
|
28
30
|
require 'timeout'
|
@@ -53,9 +55,10 @@ module Temporalio
|
|
53
55
|
attr_reader :context, :logger, :info, :scheduler, :disable_eager_activity_execution, :pending_activities,
|
54
56
|
:pending_timers, :pending_child_workflow_starts, :pending_child_workflows,
|
55
57
|
:pending_external_signals, :pending_external_cancels, :in_progress_handlers, :payload_converter,
|
56
|
-
:failure_converter, :cancellation, :continue_as_new_suggested, :
|
57
|
-
:current_history_size, :replaying, :random,
|
58
|
-
:context_frozen
|
58
|
+
:failure_converter, :cancellation, :continue_as_new_suggested, :current_deployment_version,
|
59
|
+
:current_history_length, :current_history_size, :replaying, :random,
|
60
|
+
:signal_handlers, :query_handlers, :update_handlers, :context_frozen, :assert_valid_local_activity
|
61
|
+
attr_accessor :io_enabled, :current_details
|
59
62
|
|
60
63
|
def initialize(details)
|
61
64
|
# Initialize general state
|
@@ -66,6 +69,7 @@ module Temporalio
|
|
66
69
|
@logger = ReplaySafeLogger.new(logger: details.logger, instance: self)
|
67
70
|
@logger.scoped_values_getter = proc { scoped_logger_info }
|
68
71
|
@runtime_metric_meter = details.metric_meter
|
72
|
+
@io_enabled = details.unsafe_workflow_io_enabled
|
69
73
|
@scheduler = Scheduler.new(self)
|
70
74
|
@payload_converter = details.payload_converter
|
71
75
|
@failure_converter = details.failure_converter
|
@@ -87,7 +91,7 @@ module Temporalio
|
|
87
91
|
@current_history_length = 0
|
88
92
|
@current_history_size = 0
|
89
93
|
@replaying = false
|
90
|
-
@
|
94
|
+
@workflow_failure_exception_types = details.workflow_failure_exception_types
|
91
95
|
@signal_handlers = HandlerHash.new(
|
92
96
|
details.definition.signals,
|
93
97
|
Workflow::Definition::Signal
|
@@ -104,6 +108,12 @@ module Temporalio
|
|
104
108
|
end
|
105
109
|
@query_handlers = HandlerHash.new(details.definition.queries, Workflow::Definition::Query)
|
106
110
|
@update_handlers = HandlerHash.new(details.definition.updates, Workflow::Definition::Update)
|
111
|
+
@definition_options = Workflow::DefinitionOptions.new(
|
112
|
+
failure_exception_types: details.definition.failure_exception_types,
|
113
|
+
versioning_behavior: details.definition.versioning_behavior
|
114
|
+
)
|
115
|
+
|
116
|
+
@assert_valid_local_activity = details.assert_valid_local_activity
|
107
117
|
|
108
118
|
# Create all things needed from initial job
|
109
119
|
@init_job = details.initial_activation.jobs.find { |j| !j.initialize_workflow.nil? }&.initialize_workflow
|
@@ -115,6 +125,7 @@ module Temporalio
|
|
115
125
|
continued_run_id: ProtoUtils.string_or(@init_job.continued_from_execution_run_id),
|
116
126
|
cron_schedule: ProtoUtils.string_or(@init_job.cron_schedule),
|
117
127
|
execution_timeout: ProtoUtils.duration_to_seconds(@init_job.workflow_execution_timeout),
|
128
|
+
headers: ProtoUtils.headers_from_proto_map(@init_job.headers, @payload_converter) || {},
|
118
129
|
last_failure: if @init_job.continued_failure
|
119
130
|
@failure_converter.from_failure(@init_job.continued_failure, @payload_converter)
|
120
131
|
end,
|
@@ -129,10 +140,17 @@ module Temporalio
|
|
129
140
|
workflow_id: @init_job.parent_workflow_info.workflow_id
|
130
141
|
)
|
131
142
|
end,
|
143
|
+
priority: Priority._from_proto(@init_job.priority),
|
132
144
|
retry_policy: (RetryPolicy._from_proto(@init_job.retry_policy) if @init_job.retry_policy),
|
145
|
+
root: if @init_job.root_workflow
|
146
|
+
Workflow::Info::RootInfo.new(
|
147
|
+
run_id: @init_job.root_workflow.run_id,
|
148
|
+
workflow_id: @init_job.root_workflow.workflow_id
|
149
|
+
)
|
150
|
+
end,
|
133
151
|
run_id: details.initial_activation.run_id,
|
134
152
|
run_timeout: ProtoUtils.duration_to_seconds(@init_job.workflow_run_timeout),
|
135
|
-
start_time: ProtoUtils.timestamp_to_time(
|
153
|
+
start_time: ProtoUtils.timestamp_to_time(@init_job.start_time) || raise,
|
136
154
|
task_queue: details.task_queue,
|
137
155
|
task_timeout: ProtoUtils.duration_to_seconds(@init_job.workflow_task_timeout) || raise,
|
138
156
|
workflow_id: @init_job.workflow_id,
|
@@ -228,6 +246,9 @@ module Temporalio
|
|
228
246
|
@commands = []
|
229
247
|
@current_activation_error = nil
|
230
248
|
@continue_as_new_suggested = activation.continue_as_new_suggested
|
249
|
+
@current_deployment_version = WorkerDeploymentVersion._from_bridge(
|
250
|
+
activation.deployment_version_for_current_task
|
251
|
+
)
|
231
252
|
@current_history_length = activation.history_length
|
232
253
|
@current_history_size = activation.history_size_bytes
|
233
254
|
@replaying = activation.is_replaying
|
@@ -277,7 +298,9 @@ module Temporalio
|
|
277
298
|
else
|
278
299
|
Bridge::Api::WorkflowCompletion::WorkflowActivationCompletion.new(
|
279
300
|
run_id: activation.run_id,
|
280
|
-
successful: Bridge::Api::WorkflowCompletion::Success.new(
|
301
|
+
successful: Bridge::Api::WorkflowCompletion::Success.new(
|
302
|
+
commands: @commands, versioning_behavior: @definition_options.versioning_behavior
|
303
|
+
)
|
281
304
|
)
|
282
305
|
end
|
283
306
|
ensure
|
@@ -289,7 +312,8 @@ module Temporalio
|
|
289
312
|
# Convert workflow arguments
|
290
313
|
@workflow_arguments = convert_args(payload_array: @init_job.arguments,
|
291
314
|
method_name: :execute,
|
292
|
-
raw_args: @definition.raw_args
|
315
|
+
raw_args: @definition.raw_args,
|
316
|
+
arg_hints: @definition.arg_hints)
|
293
317
|
|
294
318
|
# Initialize interceptors
|
295
319
|
@inbound = @interceptors.reverse_each.reduce(InboundImplementation.new(self)) do |acc, int|
|
@@ -298,11 +322,24 @@ module Temporalio
|
|
298
322
|
@inbound.init(OutboundImplementation.new(self))
|
299
323
|
|
300
324
|
# Create the user instance
|
301
|
-
if @definition.init
|
302
|
-
|
303
|
-
|
304
|
-
|
325
|
+
instance = if @definition.init
|
326
|
+
@definition.workflow_class.new(*@workflow_arguments)
|
327
|
+
else
|
328
|
+
@definition.workflow_class.new
|
329
|
+
end
|
330
|
+
|
331
|
+
# Run Dynamic config getter
|
332
|
+
if @definition.dynamic_options_method
|
333
|
+
dynamic_options = instance.send(@definition.dynamic_options_method)
|
334
|
+
if dynamic_options&.versioning_behavior
|
335
|
+
@definition_options.versioning_behavior = dynamic_options.versioning_behavior
|
336
|
+
end
|
337
|
+
if dynamic_options&.failure_exception_types
|
338
|
+
@definition_options.failure_exception_types = dynamic_options.failure_exception_types
|
339
|
+
end
|
305
340
|
end
|
341
|
+
|
342
|
+
instance
|
306
343
|
end
|
307
344
|
|
308
345
|
def apply(job)
|
@@ -348,7 +385,10 @@ module Temporalio
|
|
348
385
|
end
|
349
386
|
|
350
387
|
def apply_signal(job)
|
351
|
-
|
388
|
+
# Get signal definition, falling back to dynamic if not present and not reserved
|
389
|
+
defn = signal_handlers[job.signal_name]
|
390
|
+
defn = signal_handlers[nil] if !defn && !Internal::ProtoUtils.reserved_name?(job.signal_name)
|
391
|
+
|
352
392
|
handler_exec =
|
353
393
|
if defn
|
354
394
|
HandlerExecution.new(name: job.signal_name, update_id: nil, unfinished_policy: defn.unfinished_policy)
|
@@ -381,43 +421,49 @@ module Temporalio
|
|
381
421
|
end
|
382
422
|
|
383
423
|
def apply_query(job)
|
384
|
-
|
385
|
-
defn = case job.query_type
|
386
|
-
when '__stack_trace'
|
387
|
-
Workflow::Definition::Query.new(
|
388
|
-
name: '__stack_trace',
|
389
|
-
to_invoke: proc { scheduler.stack_trace }
|
390
|
-
)
|
391
|
-
else
|
392
|
-
query_handlers[job.query_type] || query_handlers[nil]
|
393
|
-
end
|
424
|
+
result_hint = nil
|
394
425
|
schedule do
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
426
|
+
# If it's a built-in, run it without interceptors, otherwise do normal behavior
|
427
|
+
result = if job.query_type == '__stack_trace'
|
428
|
+
# Use raw value built from default converter because we don't want to use user-conversion
|
429
|
+
Converters::RawValue.new(Converters::PayloadConverter.default.to_payload(scheduler.stack_trace))
|
430
|
+
elsif job.query_type == '__temporal_workflow_metadata'
|
431
|
+
# Use raw value built from default converter because we don't want to use user-conversion
|
432
|
+
Converters::RawValue.new(Converters::PayloadConverter.default.to_payload(workflow_metadata))
|
433
|
+
else
|
434
|
+
# Get query definition, falling back to dynamic if not present and not reserved
|
435
|
+
defn = query_handlers[job.query_type]
|
436
|
+
defn = query_handlers[nil] if !defn && !Internal::ProtoUtils.reserved_name?(job.query_type)
|
437
|
+
|
438
|
+
unless defn
|
439
|
+
raise "Query handler for #{job.query_type} expected but not found, " \
|
440
|
+
"known queries: [#{query_handlers.keys.compact.sort.join(', ')}]"
|
441
|
+
end
|
442
|
+
result_hint = defn.result_hint
|
443
|
+
|
444
|
+
with_context_frozen do
|
445
|
+
@inbound.handle_query(
|
446
|
+
Temporalio::Worker::Interceptor::Workflow::HandleQueryInput.new(
|
447
|
+
id: job.query_id,
|
448
|
+
query: job.query_type,
|
449
|
+
args: begin
|
450
|
+
convert_handler_args(payload_array: job.arguments, defn:)
|
451
|
+
rescue StandardError => e
|
452
|
+
raise "Failed converting query input arguments: #{e}"
|
453
|
+
end,
|
454
|
+
definition: defn,
|
455
|
+
headers: ProtoUtils.headers_from_proto_map(job.headers, @payload_converter) || {}
|
456
|
+
)
|
457
|
+
)
|
458
|
+
end
|
459
|
+
end
|
399
460
|
|
400
|
-
result = with_context_frozen do
|
401
|
-
@inbound.handle_query(
|
402
|
-
Temporalio::Worker::Interceptor::Workflow::HandleQueryInput.new(
|
403
|
-
id: job.query_id,
|
404
|
-
query: job.query_type,
|
405
|
-
args: begin
|
406
|
-
convert_handler_args(payload_array: job.arguments, defn:)
|
407
|
-
rescue StandardError => e
|
408
|
-
raise "Failed converting query input arguments: #{e}"
|
409
|
-
end,
|
410
|
-
definition: defn,
|
411
|
-
headers: ProtoUtils.headers_from_proto_map(job.headers, @payload_converter) || {}
|
412
|
-
)
|
413
|
-
)
|
414
|
-
end
|
415
461
|
add_command(
|
416
462
|
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
417
463
|
respond_to_query: Bridge::Api::WorkflowCommands::QueryResult.new(
|
418
464
|
query_id: job.query_id,
|
419
465
|
succeeded: Bridge::Api::WorkflowCommands::QuerySuccess.new(
|
420
|
-
response: @payload_converter.to_payload(result)
|
466
|
+
response: @payload_converter.to_payload(result, hint: result_hint)
|
421
467
|
)
|
422
468
|
)
|
423
469
|
)
|
@@ -435,7 +481,10 @@ module Temporalio
|
|
435
481
|
end
|
436
482
|
|
437
483
|
def apply_update(job)
|
438
|
-
|
484
|
+
# Get update definition, falling back to dynamic if not present and not reserved
|
485
|
+
defn = update_handlers[job.name]
|
486
|
+
defn = update_handlers[nil] if !defn && !Internal::ProtoUtils.reserved_name?(job.name)
|
487
|
+
|
439
488
|
handler_exec =
|
440
489
|
(HandlerExecution.new(name: job.name, update_id: job.id, unfinished_policy: defn.unfinished_policy) if defn)
|
441
490
|
schedule(handler_exec:) do
|
@@ -503,7 +552,7 @@ module Temporalio
|
|
503
552
|
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
504
553
|
update_response: Bridge::Api::WorkflowCommands::UpdateResponse.new(
|
505
554
|
protocol_instance_id: job.protocol_instance_id,
|
506
|
-
completed: @payload_converter.to_payload(result)
|
555
|
+
completed: @payload_converter.to_payload(result, hint: defn.result_hint)
|
507
556
|
)
|
508
557
|
)
|
509
558
|
)
|
@@ -527,13 +576,13 @@ module Temporalio
|
|
527
576
|
result = @inbound.execute(
|
528
577
|
Temporalio::Worker::Interceptor::Workflow::ExecuteInput.new(
|
529
578
|
args: @workflow_arguments,
|
530
|
-
headers:
|
579
|
+
headers: @info.headers
|
531
580
|
)
|
532
581
|
)
|
533
582
|
add_command(
|
534
583
|
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
535
584
|
complete_workflow_execution: Bridge::Api::WorkflowCommands::CompleteWorkflowExecution.new(
|
536
|
-
result: @payload_converter.to_payload(result)
|
585
|
+
result: @payload_converter.to_payload(result, hint: @definition.result_hint)
|
537
586
|
)
|
538
587
|
)
|
539
588
|
)
|
@@ -561,14 +610,19 @@ module Temporalio
|
|
561
610
|
def on_top_level_exception(err)
|
562
611
|
if err.is_a?(Workflow::ContinueAsNewError)
|
563
612
|
@logger.debug('Workflow requested continue as new')
|
613
|
+
workflow_type, defn_arg_hints, =
|
614
|
+
if err.workflow
|
615
|
+
Workflow::Definition._workflow_type_and_hints_from_workflow_parameter(err.workflow)
|
616
|
+
else
|
617
|
+
[nil, @definition.arg_hints, nil]
|
618
|
+
end
|
564
619
|
add_command(
|
565
620
|
Bridge::Api::WorkflowCommands::WorkflowCommand.new(
|
566
621
|
continue_as_new_workflow_execution: Bridge::Api::WorkflowCommands::ContinueAsNewWorkflowExecution.new(
|
567
|
-
workflow_type
|
568
|
-
Workflow::Definition._workflow_type_from_workflow_parameter(err.workflow)
|
569
|
-
end,
|
622
|
+
workflow_type:,
|
570
623
|
task_queue: err.task_queue,
|
571
|
-
arguments: ProtoUtils.convert_to_payload_array(payload_converter, err.args
|
624
|
+
arguments: ProtoUtils.convert_to_payload_array(payload_converter, err.args,
|
625
|
+
hints: err.arg_hints || defn_arg_hints),
|
572
626
|
workflow_run_timeout: ProtoUtils.seconds_to_duration(err.run_timeout),
|
573
627
|
workflow_task_timeout: ProtoUtils.seconds_to_duration(err.task_timeout),
|
574
628
|
memo: ProtoUtils.memo_to_proto_hash(err.memo, payload_converter),
|
@@ -606,9 +660,9 @@ module Temporalio
|
|
606
660
|
end
|
607
661
|
|
608
662
|
def failure_exception?(err)
|
609
|
-
err.is_a?(Error::Failure) || err.is_a?(Timeout::Error) ||
|
610
|
-
err.is_a?(cls)
|
611
|
-
|
663
|
+
err.is_a?(Error::Failure) || err.is_a?(Timeout::Error) ||
|
664
|
+
@workflow_failure_exception_types&.any? { |cls| err.is_a?(cls) } ||
|
665
|
+
@definition_options.failure_exception_types&.any? { |cls| err.is_a?(cls) }
|
612
666
|
end
|
613
667
|
|
614
668
|
def with_context_frozen(&)
|
@@ -623,11 +677,12 @@ module Temporalio
|
|
623
677
|
payload_array:,
|
624
678
|
method_name: defn.to_invoke.is_a?(Symbol) ? defn.to_invoke : nil,
|
625
679
|
raw_args: defn.raw_args,
|
680
|
+
arg_hints: defn.arg_hints,
|
626
681
|
ignore_first_param: defn.name.nil? # Dynamic
|
627
682
|
)
|
628
683
|
end
|
629
684
|
|
630
|
-
def convert_args(payload_array:, method_name:, raw_args:, ignore_first_param: false)
|
685
|
+
def convert_args(payload_array:, method_name:, raw_args:, arg_hints:, ignore_first_param: false)
|
631
686
|
# Just in case it is not an array
|
632
687
|
payload_array = payload_array.to_ary
|
633
688
|
|
@@ -667,10 +722,34 @@ module Temporalio
|
|
667
722
|
if raw_args
|
668
723
|
payload_array.map { |p| Converters::RawValue.new(p) }
|
669
724
|
else
|
670
|
-
ProtoUtils.convert_from_payload_array(@payload_converter, payload_array)
|
725
|
+
ProtoUtils.convert_from_payload_array(@payload_converter, payload_array, hints: arg_hints)
|
671
726
|
end
|
672
727
|
end
|
673
728
|
|
729
|
+
def workflow_metadata
|
730
|
+
Temporalio::Api::Sdk::V1::WorkflowMetadata.new(
|
731
|
+
definition: Temporalio::Api::Sdk::V1::WorkflowDefinition.new(
|
732
|
+
type: info.workflow_type,
|
733
|
+
query_definitions: query_handlers.values.map do |defn|
|
734
|
+
Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new(
|
735
|
+
name: defn.name || '', description: defn.description || ''
|
736
|
+
)
|
737
|
+
end,
|
738
|
+
signal_definitions: signal_handlers.values.map do |defn|
|
739
|
+
Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new(
|
740
|
+
name: defn.name || '', description: defn.description || ''
|
741
|
+
)
|
742
|
+
end,
|
743
|
+
update_definitions: update_handlers.values.map do |defn|
|
744
|
+
Temporalio::Api::Sdk::V1::WorkflowInteractionDefinition.new(
|
745
|
+
name: defn.name || '', description: defn.description || ''
|
746
|
+
)
|
747
|
+
end
|
748
|
+
),
|
749
|
+
current_details: current_details || ''
|
750
|
+
)
|
751
|
+
end
|
752
|
+
|
674
753
|
def scoped_logger_info
|
675
754
|
@scoped_logger_info ||= {
|
676
755
|
attempt: info.attempt,
|
@@ -13,7 +13,7 @@ module Temporalio
|
|
13
13
|
module Worker
|
14
14
|
# Worker for handling workflow activations. Most activation work is delegated to the workflow executor.
|
15
15
|
class WorkflowWorker
|
16
|
-
def self.workflow_definitions(workflows)
|
16
|
+
def self.workflow_definitions(workflows, should_enforce_versioning_behavior:)
|
17
17
|
workflows.each_with_object({}) do |workflow, hash|
|
18
18
|
# Load definition
|
19
19
|
defn = begin
|
@@ -29,15 +29,58 @@ module Temporalio
|
|
29
29
|
# Confirm name not in use
|
30
30
|
raise ArgumentError, "Multiple workflows named #{defn.name || '<dynamic>'}" if hash.key?(defn.name)
|
31
31
|
|
32
|
+
# Enforce versioning behavior is set when versioning is on
|
33
|
+
if should_enforce_versioning_behavior &&
|
34
|
+
defn.versioning_behavior == VersioningBehavior::UNSPECIFIED && !defn.dynamic_options_method
|
35
|
+
raise ArgumentError, "Workflow #{defn.name} must specify a versioning behavior"
|
36
|
+
end
|
37
|
+
|
32
38
|
hash[defn.name] = defn
|
33
39
|
end
|
34
40
|
end
|
35
41
|
|
36
|
-
def
|
37
|
-
|
42
|
+
def self.bridge_workflow_failure_exception_type_options(
|
43
|
+
workflow_failure_exception_types:,
|
44
|
+
workflow_definitions:
|
45
|
+
)
|
46
|
+
as_fail = workflow_failure_exception_types.any? do |t|
|
47
|
+
t.is_a?(Class) && t >= Workflow::NondeterminismError
|
48
|
+
end
|
49
|
+
as_fail_for_types = workflow_definitions.values.map do |defn|
|
50
|
+
next unless defn.failure_exception_types.any? { |t| t.is_a?(Class) && t >= Workflow::NondeterminismError }
|
38
51
|
|
39
|
-
|
40
|
-
|
52
|
+
# If they tried to do this on a dynamic workflow and haven't already set worker-level option, warn
|
53
|
+
unless defn.name || as_fail
|
54
|
+
warn('Note, dynamic workflows cannot trap non-determinism errors, so worker-level ' \
|
55
|
+
'workflow_failure_exception_types should be set to capture that if that is the intention')
|
56
|
+
end
|
57
|
+
defn.name
|
58
|
+
end.compact
|
59
|
+
[as_fail, as_fail_for_types]
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(
|
63
|
+
bridge_worker:,
|
64
|
+
namespace:,
|
65
|
+
task_queue:,
|
66
|
+
workflow_definitions:,
|
67
|
+
workflow_executor:,
|
68
|
+
logger:,
|
69
|
+
data_converter:,
|
70
|
+
metric_meter:,
|
71
|
+
workflow_interceptors:,
|
72
|
+
disable_eager_activity_execution:,
|
73
|
+
illegal_workflow_calls:,
|
74
|
+
workflow_failure_exception_types:,
|
75
|
+
workflow_payload_codec_thread_pool:,
|
76
|
+
unsafe_workflow_io_enabled:,
|
77
|
+
debug_mode:,
|
78
|
+
assert_valid_local_activity:, on_eviction: nil
|
79
|
+
)
|
80
|
+
@executor = workflow_executor
|
81
|
+
|
82
|
+
payload_codec = data_converter.payload_codec
|
83
|
+
@workflow_payload_codec_thread_pool = workflow_payload_codec_thread_pool
|
41
84
|
if !Fiber.current_scheduler && payload_codec && !@workflow_payload_codec_thread_pool
|
42
85
|
raise ArgumentError, 'Must have workflow payload codec thread pool if providing codec and not using fibers'
|
43
86
|
end
|
@@ -55,29 +98,32 @@ module Temporalio
|
|
55
98
|
@state = State.new(
|
56
99
|
workflow_definitions:,
|
57
100
|
bridge_worker:,
|
58
|
-
logger
|
59
|
-
metric_meter
|
60
|
-
data_converter
|
61
|
-
deadlock_timeout:
|
101
|
+
logger:,
|
102
|
+
metric_meter:,
|
103
|
+
data_converter:,
|
104
|
+
deadlock_timeout: debug_mode ? nil : 2.0,
|
62
105
|
# TODO(cretz): Make this more performant for the default set?
|
63
106
|
illegal_calls: WorkflowInstance::IllegalCallTracer.frozen_validated_illegal_calls(
|
64
|
-
|
107
|
+
illegal_workflow_calls || {}
|
65
108
|
),
|
66
|
-
namespace
|
67
|
-
task_queue
|
68
|
-
disable_eager_activity_execution
|
69
|
-
workflow_interceptors
|
70
|
-
workflow_failure_exception_types:
|
71
|
-
unless t.is_a?(Class) && t
|
109
|
+
namespace:,
|
110
|
+
task_queue:,
|
111
|
+
disable_eager_activity_execution:,
|
112
|
+
workflow_interceptors:,
|
113
|
+
workflow_failure_exception_types: workflow_failure_exception_types.map do |t|
|
114
|
+
unless t.is_a?(Class) && t <= Exception
|
72
115
|
raise ArgumentError, 'All failure types must classes inheriting Exception'
|
73
116
|
end
|
74
117
|
|
75
118
|
t
|
76
|
-
end.freeze
|
119
|
+
end.freeze,
|
120
|
+
unsafe_workflow_io_enabled:,
|
121
|
+
assert_valid_local_activity:
|
77
122
|
)
|
123
|
+
@state.on_eviction = on_eviction if on_eviction
|
78
124
|
|
79
125
|
# Validate worker
|
80
|
-
@executor._validate_worker(
|
126
|
+
@executor._validate_worker(self, @state)
|
81
127
|
end
|
82
128
|
|
83
129
|
def handle_activation(runner:, activation:, decoded:)
|
@@ -147,12 +193,16 @@ module Temporalio
|
|
147
193
|
class State
|
148
194
|
attr_reader :workflow_definitions, :bridge_worker, :logger, :metric_meter, :data_converter, :deadlock_timeout,
|
149
195
|
:illegal_calls, :namespace, :task_queue, :disable_eager_activity_execution,
|
150
|
-
:workflow_interceptors, :workflow_failure_exception_types
|
196
|
+
:workflow_interceptors, :workflow_failure_exception_types, :unsafe_workflow_io_enabled,
|
197
|
+
:assert_valid_local_activity
|
198
|
+
|
199
|
+
attr_writer :on_eviction
|
151
200
|
|
152
201
|
def initialize(
|
153
202
|
workflow_definitions:, bridge_worker:, logger:, metric_meter:, data_converter:, deadlock_timeout:,
|
154
203
|
illegal_calls:, namespace:, task_queue:, disable_eager_activity_execution:,
|
155
|
-
workflow_interceptors:, workflow_failure_exception_types
|
204
|
+
workflow_interceptors:, workflow_failure_exception_types:, unsafe_workflow_io_enabled:,
|
205
|
+
assert_valid_local_activity:
|
156
206
|
)
|
157
207
|
@workflow_definitions = workflow_definitions
|
158
208
|
@bridge_worker = bridge_worker
|
@@ -166,6 +216,8 @@ module Temporalio
|
|
166
216
|
@disable_eager_activity_execution = disable_eager_activity_execution
|
167
217
|
@workflow_interceptors = workflow_interceptors
|
168
218
|
@workflow_failure_exception_types = workflow_failure_exception_types
|
219
|
+
@unsafe_workflow_io_enabled = unsafe_workflow_io_enabled
|
220
|
+
@assert_valid_local_activity = assert_valid_local_activity
|
169
221
|
|
170
222
|
@running_workflows = {}
|
171
223
|
@running_workflows_mutex = Mutex.new
|
@@ -182,8 +234,9 @@ module Temporalio
|
|
182
234
|
instance
|
183
235
|
end
|
184
236
|
|
185
|
-
def evict_running_workflow(run_id)
|
237
|
+
def evict_running_workflow(run_id, cache_remove_job)
|
186
238
|
@running_workflows_mutex.synchronize { @running_workflows.delete(run_id) }
|
239
|
+
@on_eviction&.call(run_id, cache_remove_job)
|
187
240
|
end
|
188
241
|
|
189
242
|
def evict_all
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'temporalio/api'
|
4
|
+
|
5
|
+
module Temporalio
|
6
|
+
Priority = Data.define(
|
7
|
+
:priority_key
|
8
|
+
)
|
9
|
+
|
10
|
+
# Priority contains metadata that controls relative ordering of task processing when tasks are
|
11
|
+
# backlogged in a queue. Initially, Priority will be used in activity and workflow task
|
12
|
+
# queues, which are typically where backlogs exist. Priority is (for now) attached to
|
13
|
+
# workflows and activities. Activities and child workflows inherit Priority from the workflow
|
14
|
+
# that created them, but may override fields when they are started or modified. For each field
|
15
|
+
# of a Priority on an activity/workflow, not present or equal to zero/empty string means to
|
16
|
+
# inherit the value from the calling workflow, or if there is no calling workflow, then use
|
17
|
+
# the default (documented on the field).
|
18
|
+
#
|
19
|
+
# The overall semantics of Priority are:
|
20
|
+
# 1. First, consider "priority_key": lower number goes first.
|
21
|
+
# (more will be added here later).
|
22
|
+
#
|
23
|
+
# @!attribute priority_key
|
24
|
+
# @return [Integer, nil] The priority key, which is a positive integer from 1 to n, where
|
25
|
+
# smaller integers correspond to higher priorities (tasks run sooner). In general, tasks in a
|
26
|
+
# queue should be processed in close to priority order, although small deviations are possible.
|
27
|
+
# The maximum priority value (minimum priority) is determined by server configuration, and
|
28
|
+
# defaults to 5.
|
29
|
+
#
|
30
|
+
# The default priority is (min+max)/2. With the default max of 5 and min of 1, that comes
|
31
|
+
# out to 3.
|
32
|
+
class Priority
|
33
|
+
# @!visibility private
|
34
|
+
def self._from_proto(priority)
|
35
|
+
return default if priority.nil?
|
36
|
+
|
37
|
+
new(priority_key: priority.priority_key.zero? ? nil : priority.priority_key)
|
38
|
+
end
|
39
|
+
|
40
|
+
# The default priority instance.
|
41
|
+
#
|
42
|
+
# @return [Priority] The default priority
|
43
|
+
def self.default
|
44
|
+
@default ||= new(priority_key: nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @!visibility private
|
48
|
+
def _to_proto
|
49
|
+
return nil if priority_key.nil?
|
50
|
+
|
51
|
+
Temporalio::Api::Common::V1::Priority.new(priority_key: priority_key || 0)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Boolean] True if this priority is empty/default
|
55
|
+
def empty?
|
56
|
+
priority_key.nil?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Temporalio
|
4
|
+
class Runtime
|
5
|
+
# Metric buffer for use with a runtime to capture metrics. Only one metric buffer can be associated with a runtime
|
6
|
+
# and {retrieve_updates} cannot be called before the runtime is created. Once runtime created, users should
|
7
|
+
# regularly call {retrieve_updates} to drain the buffer.
|
8
|
+
#
|
9
|
+
# @note WARNING: It is important that the buffer size is set to a high number and that {retrieve_updates} is called
|
10
|
+
# regularly to drain the buffer. If the buffer is full, metric updates will be dropped and an error will be
|
11
|
+
# logged.
|
12
|
+
class MetricBuffer
|
13
|
+
# Enumerates for the duration format.
|
14
|
+
module DurationFormat
|
15
|
+
# Durations are millisecond integers.
|
16
|
+
MILLISECONDS = :milliseconds
|
17
|
+
|
18
|
+
# Durations are second floats.
|
19
|
+
SECONDS = :seconds
|
20
|
+
end
|
21
|
+
|
22
|
+
Update = Data.define(:metric, :value, :attributes)
|
23
|
+
|
24
|
+
# Metric buffer update.
|
25
|
+
#
|
26
|
+
# @note WARNING: The constructor of this class should not be invoked by users and may change in incompatible ways
|
27
|
+
# in the future.
|
28
|
+
#
|
29
|
+
# @!attribute metric
|
30
|
+
# @return [Metric] Metric for this update. For performance reasons, this is created lazily on first use and is
|
31
|
+
# the same object each time an update on this metric exists.
|
32
|
+
# @!attribute value
|
33
|
+
# @return [Integer, Float] Metric value for this update.
|
34
|
+
# @!attribute attributes
|
35
|
+
# @return [Hash{String => String, Integer, Float, Boolean}] Attributes for this value as a frozen hash.
|
36
|
+
# For performance reasons this is sometimes the same hash if the attribute set is reused at a metric level.
|
37
|
+
class Update # rubocop:disable Lint/EmptyClass
|
38
|
+
# DEV NOTE: This class is instantiated inside Rust, be careful changing it.
|
39
|
+
end
|
40
|
+
|
41
|
+
Metric = Data.define(:name, :description, :unit, :kind)
|
42
|
+
|
43
|
+
# Metric definition present on an update.
|
44
|
+
#
|
45
|
+
# @!attribute name
|
46
|
+
# @return [String] Name of the metric.
|
47
|
+
# @!attribute description
|
48
|
+
# @return [String, nil] Description of the metric if any.
|
49
|
+
# @!attribute unit
|
50
|
+
# @return [String, nil] Unit of the metric if any.
|
51
|
+
# @!attribute kind
|
52
|
+
# @return [:counter, :histogram, :gauge] Kind of the metric.
|
53
|
+
class Metric # rubocop:disable Lint/EmptyClass
|
54
|
+
# DEV NOTE: This class is instantiated inside Rust, be careful changing it.
|
55
|
+
end
|
56
|
+
|
57
|
+
# Create a metric buffer with the given size.
|
58
|
+
#
|
59
|
+
# @note WARNING: It is important that the buffer size is set to a high number and is drained regularly. See
|
60
|
+
# {MetricBuffer} warning.
|
61
|
+
#
|
62
|
+
# @param buffer_size [Integer] Maximum size of the buffer before metrics will be dropped.
|
63
|
+
# @param duration_format [DurationFormat] How durations are represented.
|
64
|
+
def initialize(buffer_size, duration_format: DurationFormat::MILLISECONDS)
|
65
|
+
@buffer_size = buffer_size
|
66
|
+
@duration_format = duration_format
|
67
|
+
@runtime = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# Drain the buffer and return all metric updates.
|
71
|
+
#
|
72
|
+
# @note WARNING: It is important that this is called regularly. See {MetricBuffer} warning.
|
73
|
+
#
|
74
|
+
# @return [Array<Update>] Updates since last time this was called.
|
75
|
+
def retrieve_updates
|
76
|
+
raise 'Attempting to retrieve updates before runtime created' unless @runtime
|
77
|
+
|
78
|
+
@runtime._core_runtime.retrieve_buffered_metrics(@duration_format == DurationFormat::SECONDS)
|
79
|
+
end
|
80
|
+
|
81
|
+
# @!visibility private
|
82
|
+
def _buffer_size
|
83
|
+
@buffer_size
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!visibility private
|
87
|
+
def _set_runtime(runtime)
|
88
|
+
raise 'Metric buffer already attached to a runtime' if @runtime
|
89
|
+
|
90
|
+
@runtime = runtime
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|