temporalio 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/Cargo.lock +659 -370
  4. data/Cargo.toml +2 -2
  5. data/Gemfile +3 -3
  6. data/README.md +589 -47
  7. data/Rakefile +10 -296
  8. data/ext/Cargo.toml +1 -0
  9. data/lib/temporalio/activity/complete_async_error.rb +1 -1
  10. data/lib/temporalio/activity/context.rb +5 -2
  11. data/lib/temporalio/activity/definition.rb +163 -65
  12. data/lib/temporalio/activity/info.rb +22 -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/cloud/account/v1/message.rb +28 -0
  16. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +34 -1
  17. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +1 -1
  18. data/lib/temporalio/api/cloud/identity/v1/message.rb +6 -1
  19. data/lib/temporalio/api/cloud/namespace/v1/message.rb +8 -1
  20. data/lib/temporalio/api/cloud/nexus/v1/message.rb +31 -0
  21. data/lib/temporalio/api/cloud/operation/v1/message.rb +2 -1
  22. data/lib/temporalio/api/cloud/region/v1/message.rb +2 -1
  23. data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
  24. data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
  25. data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
  26. data/lib/temporalio/api/common/v1/message.rb +7 -1
  27. data/lib/temporalio/api/enums/v1/event_type.rb +1 -1
  28. data/lib/temporalio/api/enums/v1/failed_cause.rb +1 -1
  29. data/lib/temporalio/api/enums/v1/reset.rb +1 -1
  30. data/lib/temporalio/api/history/v1/message.rb +1 -1
  31. data/lib/temporalio/api/nexus/v1/message.rb +2 -2
  32. data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
  33. data/lib/temporalio/api/payload_visitor.rb +1513 -0
  34. data/lib/temporalio/api/schedule/v1/message.rb +2 -1
  35. data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
  36. data/lib/temporalio/api/testservice/v1/service.rb +23 -0
  37. data/lib/temporalio/api/workflow/v1/message.rb +1 -1
  38. data/lib/temporalio/api/workflowservice/v1/request_response.rb +17 -2
  39. data/lib/temporalio/api/workflowservice/v1/service.rb +1 -1
  40. data/lib/temporalio/api.rb +1 -0
  41. data/lib/temporalio/cancellation.rb +34 -14
  42. data/lib/temporalio/client/async_activity_handle.rb +12 -37
  43. data/lib/temporalio/client/connection/cloud_service.rb +309 -231
  44. data/lib/temporalio/client/connection/operator_service.rb +36 -84
  45. data/lib/temporalio/client/connection/service.rb +6 -5
  46. data/lib/temporalio/client/connection/test_service.rb +111 -0
  47. data/lib/temporalio/client/connection/workflow_service.rb +264 -441
  48. data/lib/temporalio/client/connection.rb +90 -44
  49. data/lib/temporalio/client/interceptor.rb +160 -60
  50. data/lib/temporalio/client/schedule.rb +967 -0
  51. data/lib/temporalio/client/schedule_handle.rb +126 -0
  52. data/lib/temporalio/client/workflow_execution.rb +7 -10
  53. data/lib/temporalio/client/workflow_handle.rb +38 -95
  54. data/lib/temporalio/client/workflow_update_handle.rb +3 -5
  55. data/lib/temporalio/client.rb +122 -42
  56. data/lib/temporalio/common_enums.rb +17 -0
  57. data/lib/temporalio/converters/data_converter.rb +4 -7
  58. data/lib/temporalio/converters/failure_converter.rb +5 -3
  59. data/lib/temporalio/converters/payload_converter/composite.rb +4 -0
  60. data/lib/temporalio/converters/payload_converter.rb +6 -8
  61. data/lib/temporalio/converters/raw_value.rb +20 -0
  62. data/lib/temporalio/error/failure.rb +1 -1
  63. data/lib/temporalio/error.rb +10 -2
  64. data/lib/temporalio/internal/bridge/api/core_interface.rb +5 -1
  65. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +33 -0
  66. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +5 -1
  67. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +4 -1
  68. data/lib/temporalio/internal/bridge/client.rb +11 -6
  69. data/lib/temporalio/internal/bridge/testing.rb +20 -0
  70. data/lib/temporalio/internal/bridge/worker.rb +2 -0
  71. data/lib/temporalio/internal/bridge.rb +1 -1
  72. data/lib/temporalio/internal/client/implementation.rb +245 -70
  73. data/lib/temporalio/internal/metric.rb +122 -0
  74. data/lib/temporalio/internal/proto_utils.rb +86 -7
  75. data/lib/temporalio/internal/worker/activity_worker.rb +52 -24
  76. data/lib/temporalio/internal/worker/multi_runner.rb +51 -7
  77. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
  78. data/lib/temporalio/internal/worker/workflow_instance/context.rb +329 -0
  79. data/lib/temporalio/internal/worker/workflow_instance/details.rb +44 -0
  80. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
  81. data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
  82. data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
  83. data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
  84. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
  85. data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
  86. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +415 -0
  87. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
  88. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
  89. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +163 -0
  90. data/lib/temporalio/internal/worker/workflow_instance.rb +730 -0
  91. data/lib/temporalio/internal/worker/workflow_worker.rb +196 -0
  92. data/lib/temporalio/metric.rb +109 -0
  93. data/lib/temporalio/retry_policy.rb +37 -14
  94. data/lib/temporalio/runtime.rb +118 -75
  95. data/lib/temporalio/search_attributes.rb +80 -37
  96. data/lib/temporalio/testing/activity_environment.rb +2 -2
  97. data/lib/temporalio/testing/workflow_environment.rb +251 -5
  98. data/lib/temporalio/version.rb +1 -1
  99. data/lib/temporalio/worker/activity_executor/thread_pool.rb +9 -217
  100. data/lib/temporalio/worker/activity_executor.rb +3 -3
  101. data/lib/temporalio/worker/interceptor.rb +340 -66
  102. data/lib/temporalio/worker/thread_pool.rb +237 -0
  103. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +230 -0
  104. data/lib/temporalio/worker/workflow_executor.rb +26 -0
  105. data/lib/temporalio/worker.rb +201 -30
  106. data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
  107. data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
  108. data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
  109. data/lib/temporalio/workflow/definition.rb +566 -0
  110. data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
  111. data/lib/temporalio/workflow/future.rb +151 -0
  112. data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
  113. data/lib/temporalio/workflow/info.rb +82 -0
  114. data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
  115. data/lib/temporalio/workflow/update_info.rb +20 -0
  116. data/lib/temporalio/workflow.rb +523 -0
  117. data/lib/temporalio.rb +4 -0
  118. data/temporalio.gemspec +2 -2
  119. metadata +50 -8
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'temporalio/internal/bridge'
5
+ require 'temporalio/metric'
6
+
7
+ module Temporalio
8
+ module Internal
9
+ class Metric < Temporalio::Metric
10
+ attr_reader :metric_type, :name, :description, :unit, :value_type
11
+
12
+ def initialize(metric_type:, name:, description:, unit:, value_type:, bridge:, bridge_attrs:) # rubocop:disable Lint/MissingSuper
13
+ @metric_type = metric_type
14
+ @name = name
15
+ @description = description
16
+ @unit = unit
17
+ @value_type = value_type
18
+ @bridge = bridge
19
+ @bridge_attrs = bridge_attrs
20
+ end
21
+
22
+ def record(value, additional_attributes: nil)
23
+ bridge_attrs = @bridge_attrs
24
+ bridge_attrs = @bridge_attrs.with_additional(additional_attributes) if additional_attributes
25
+ @bridge.record_value(value, bridge_attrs)
26
+ end
27
+
28
+ def with_additional_attributes(additional_attributes)
29
+ Metric.new(
30
+ metric_type:,
31
+ name:,
32
+ description:,
33
+ unit:,
34
+ value_type:,
35
+ bridge: @bridge,
36
+ bridge_attrs: @bridge_attrs.with_additional(additional_attributes)
37
+ )
38
+ end
39
+
40
+ class Meter < Temporalio::Metric::Meter
41
+ def self.create_from_runtime(runtime)
42
+ bridge = Bridge::Metric::Meter.new(runtime._core_runtime)
43
+ return nil unless bridge
44
+
45
+ Meter.new(bridge, bridge.default_attributes)
46
+ end
47
+
48
+ def initialize(bridge, bridge_attrs) # rubocop:disable Lint/MissingSuper
49
+ @bridge = bridge
50
+ @bridge_attrs = bridge_attrs
51
+ end
52
+
53
+ def create_metric(
54
+ metric_type,
55
+ name,
56
+ description: nil,
57
+ unit: nil,
58
+ value_type: :integer
59
+ )
60
+ Metric.new(
61
+ metric_type:,
62
+ name:,
63
+ description:,
64
+ unit:,
65
+ value_type:,
66
+ bridge: Bridge::Metric.new(@bridge, metric_type, name, description, unit, value_type),
67
+ bridge_attrs: @bridge_attrs
68
+ )
69
+ end
70
+
71
+ def with_additional_attributes(additional_attributes)
72
+ Meter.new(@bridge, @bridge_attrs.with_additional(additional_attributes))
73
+ end
74
+ end
75
+
76
+ class NullMeter < Temporalio::Metric::Meter
77
+ include Singleton
78
+
79
+ def initialize # rubocop:disable Style/RedundantInitialize,Lint/MissingSuper
80
+ end
81
+
82
+ def create_metric(
83
+ metric_type,
84
+ name,
85
+ description: nil,
86
+ unit: nil,
87
+ value_type: :integer
88
+ )
89
+ NullMetric.new(
90
+ metric_type:,
91
+ name:,
92
+ description:,
93
+ unit:,
94
+ value_type:
95
+ )
96
+ end
97
+
98
+ def with_additional_attributes(_additional_attributes)
99
+ self
100
+ end
101
+ end
102
+
103
+ class NullMetric < Temporalio::Metric
104
+ attr_reader :metric_type, :name, :description, :unit, :value_type
105
+
106
+ def initialize(metric_type:, name:, description:, unit:, value_type:) # rubocop:disable Lint/MissingSuper
107
+ @metric_type = metric_type
108
+ @name = name
109
+ @description = description
110
+ @unit = unit
111
+ @value_type = value_type
112
+ end
113
+
114
+ def record(value, additional_attributes: nil); end
115
+
116
+ def with_additional_attributes(_additional_attributes)
117
+ self
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -5,26 +5,82 @@ require 'temporalio/api'
5
5
  module Temporalio
6
6
  module Internal
7
7
  module ProtoUtils
8
- def self.seconds_to_duration(seconds_float)
9
- return nil if seconds_float.nil?
8
+ def self.seconds_to_duration(seconds_numeric)
9
+ return nil if seconds_numeric.nil?
10
10
 
11
- seconds = seconds_float.to_i
12
- nanos = ((seconds_float - seconds) * 1_000_000_000).round
11
+ seconds = seconds_numeric.to_i
12
+ nanos = ((seconds_numeric - seconds) * 1_000_000_000).round
13
13
  Google::Protobuf::Duration.new(seconds:, nanos:)
14
14
  end
15
15
 
16
+ def self.duration_to_seconds(duration)
17
+ return nil if duration.nil?
18
+
19
+ # This logic was corrected for timestamp at
20
+ # https://github.com/protocolbuffers/protobuf/pull/2482 but not for
21
+ # duration, so 4.56 is not properly represented in to_f, it becomes
22
+ # 4.5600000000000005.
23
+ (duration.seconds + duration.nanos.quo(1_000_000_000)).to_f
24
+ end
25
+
26
+ def self.time_to_timestamp(time)
27
+ return nil if time.nil?
28
+
29
+ Google::Protobuf::Timestamp.from_time(time)
30
+ end
31
+
32
+ def self.timestamp_to_time(timestamp)
33
+ return nil if timestamp.nil?
34
+
35
+ # The regular to_time on the timestamp converts to local timezone,
36
+ # and we prefer not to make a separate .utc call (converts to local
37
+ # then back to UTC unnecessarily)
38
+ Time.at(timestamp.seconds, timestamp.nanos, :nanosecond, in: 'UTC')
39
+ end
40
+
16
41
  def self.memo_to_proto(hash, converter)
17
- return nil if hash.nil?
42
+ return nil if hash.nil? || hash.empty?
43
+
44
+ Api::Common::V1::Memo.new(fields: memo_to_proto_hash(hash, converter))
45
+ end
46
+
47
+ def self.memo_to_proto_hash(hash, converter)
48
+ return nil if hash.nil? || hash.empty?
18
49
 
19
- Api::Common::V1::Memo.new(fields: hash.transform_values { |val| converter.to_payload(val) })
50
+ hash.transform_keys(&:to_s).transform_values { |val| converter.to_payload(val) }
20
51
  end
21
52
 
22
53
  def self.memo_from_proto(memo, converter)
23
- return nil if memo.nil?
54
+ return nil if memo.nil? || memo.fields.size.zero? # rubocop:disable Style/ZeroLengthPredicate Google Maps don't have empty
24
55
 
25
56
  memo.fields.each_with_object({}) { |(key, val), h| h[key] = converter.from_payload(val) } # rubocop:disable Style/HashTransformValues
26
57
  end
27
58
 
59
+ def self.headers_to_proto(headers, converter)
60
+ return nil if headers.nil? || headers.empty?
61
+
62
+ Api::Common::V1::Header.new(fields: headers_to_proto_hash(headers, converter))
63
+ end
64
+
65
+ def self.headers_to_proto_hash(headers, converter)
66
+ return nil if headers.nil? || headers.empty?
67
+
68
+ headers.transform_values { |val| converter.to_payload(val) }
69
+ end
70
+
71
+ def self.headers_from_proto(headers, converter)
72
+ headers_from_proto_map(headers&.fields, converter)
73
+ end
74
+
75
+ def self.headers_from_proto_map(headers, converter)
76
+ return nil if headers.nil? || headers.size.zero? # rubocop:disable Style/ZeroLengthPredicate Google Maps don't have empty
77
+
78
+ headers.each_with_object({}) do |(key, val), h| # rubocop:disable Style/HashTransformValues
79
+ # @type var h: Hash[String, Object?]
80
+ h[key] = converter.from_payload(val)
81
+ end
82
+ end
83
+
28
84
  def self.string_or(str, default = nil)
29
85
  str && !str.empty? ? str : default
30
86
  end
@@ -49,6 +105,29 @@ module Temporalio
49
105
 
50
106
  converter.to_payloads(values).payloads.to_ary
51
107
  end
108
+
109
+ class LazyMemo
110
+ def initialize(raw_memo, converter)
111
+ @raw_memo = raw_memo
112
+ @converter = converter
113
+ end
114
+
115
+ def get
116
+ @memo = ProtoUtils.memo_from_proto(@raw_memo, @converter) unless defined?(@memo)
117
+ @memo
118
+ end
119
+ end
120
+
121
+ class LazySearchAttributes
122
+ def initialize(raw_search_attributes)
123
+ @raw_search_attributes = raw_search_attributes
124
+ end
125
+
126
+ def get
127
+ @search_attributes = SearchAttributes._from_proto(@raw_search_attributes) unless defined?(@search_attributes)
128
+ @search_attributes
129
+ end
130
+ end
52
131
  end
53
132
  end
54
133
  end
@@ -3,6 +3,7 @@
3
3
  require 'temporalio/activity'
4
4
  require 'temporalio/activity/definition'
5
5
  require 'temporalio/cancellation'
6
+ require 'temporalio/converters/raw_value'
6
7
  require 'temporalio/internal/bridge/api'
7
8
  require 'temporalio/internal/proto_utils'
8
9
  require 'temporalio/scoped_logger'
@@ -11,14 +12,17 @@ require 'temporalio/worker/interceptor'
11
12
  module Temporalio
12
13
  module Internal
13
14
  module Worker
15
+ # Worker for handling activity tasks. Upon overarching worker shutdown, {wait_all_complete} should be used to wait
16
+ # for the activities to complete.
14
17
  class ActivityWorker
15
18
  LOG_TASKS = false
16
19
 
17
20
  attr_reader :worker, :bridge_worker
18
21
 
19
- def initialize(worker, bridge_worker)
22
+ def initialize(worker:, bridge_worker:)
20
23
  @worker = worker
21
24
  @bridge_worker = bridge_worker
25
+ @runtime_metric_meter = worker.options.client.connection.options.runtime.metric_meter
22
26
 
23
27
  # Create shared logger that gives scoped activity details
24
28
  @scoped_logger = ScopedLogger.new(@worker.options.logger)
@@ -26,12 +30,13 @@ module Temporalio
26
30
  Activity::Context.current_or_nil&._scoped_logger_info
27
31
  }
28
32
 
29
- # Build up activity hash by name, failing if any fail validation
33
+ # Build up activity hash by name (can be nil for dynamic), failing if any fail validation
30
34
  @activities = worker.options.activities.each_with_object({}) do |act, hash|
31
35
  # Class means create each time, instance means just call, definition
32
36
  # does nothing special
33
- defn = Activity::Definition.from_activity(act)
37
+ defn = Activity::Definition::Info.from_activity(act)
34
38
  # Confirm name not in use
39
+ raise ArgumentError, 'Only one dynamic activity allowed' if !defn.name && hash.key?(defn.name)
35
40
  raise ArgumentError, "Multiple activities named #{defn.name}" if hash.key?(defn.name)
36
41
 
37
42
  # Confirm executor is a known executor and let it initialize
@@ -88,8 +93,8 @@ module Temporalio
88
93
  def handle_start_task(task_token, start)
89
94
  set_running_activity(task_token, nil)
90
95
 
91
- # Find activity definition
92
- defn = @activities[start.activity_type]
96
+ # Find activity definition, falling back to dynamic if present
97
+ defn = @activities[start.activity_type] || @activities[nil]
93
98
  if defn.nil?
94
99
  raise Error::ApplicationError.new(
95
100
  "Activity #{start.activity_type} for workflow #{start.workflow_execution.workflow_id} " \
@@ -158,17 +163,19 @@ module Temporalio
158
163
  activity_id: start.activity_id,
159
164
  activity_type: start.activity_type,
160
165
  attempt: start.attempt,
161
- current_attempt_scheduled_time: start.current_attempt_scheduled_time.to_time,
166
+ current_attempt_scheduled_time: Internal::ProtoUtils.timestamp_to_time(
167
+ start.current_attempt_scheduled_time
168
+ ) || raise, # Never nil
162
169
  heartbeat_details: ProtoUtils.convert_from_payload_array(
163
170
  @worker.options.client.data_converter,
164
171
  start.heartbeat_details.to_ary
165
172
  ),
166
- heartbeat_timeout: start.heartbeat_timeout&.to_f,
173
+ heartbeat_timeout: Internal::ProtoUtils.duration_to_seconds(start.heartbeat_timeout),
167
174
  local?: start.is_local,
168
- schedule_to_close_timeout: start.schedule_to_close_timeout&.to_f,
169
- scheduled_time: start.scheduled_time.to_time,
170
- start_to_close_timeout: start.start_to_close_timeout&.to_f,
171
- started_time: start.started_time.to_time,
175
+ schedule_to_close_timeout: Internal::ProtoUtils.duration_to_seconds(start.schedule_to_close_timeout),
176
+ scheduled_time: Internal::ProtoUtils.timestamp_to_time(start.scheduled_time) || raise, # Never nil
177
+ start_to_close_timeout: Internal::ProtoUtils.duration_to_seconds(start.start_to_close_timeout),
178
+ started_time: Internal::ProtoUtils.timestamp_to_time(start.started_time) || raise, # Never nil
172
179
  task_queue: @worker.options.task_queue,
173
180
  task_token:,
174
181
  workflow_id: start.workflow_execution.workflow_id,
@@ -178,13 +185,18 @@ module Temporalio
178
185
  ).freeze
179
186
 
180
187
  # Build input
181
- input = Temporalio::Worker::Interceptor::ExecuteActivityInput.new(
188
+ input = Temporalio::Worker::Interceptor::Activity::ExecuteInput.new(
182
189
  proc: defn.proc,
183
- args: ProtoUtils.convert_from_payload_array(
184
- @worker.options.client.data_converter,
185
- start.input.to_ary
186
- ),
187
- headers: start.header_fields
190
+ # If the activity wants raw_args, we only decode we don't convert
191
+ args: if defn.raw_args
192
+ payloads = start.input.to_ary
193
+ codec = @worker.options.client.data_converter.payload_codec
194
+ payloads = codec.decode(payloads) if codec
195
+ payloads.map { |p| Temporalio::Converters::RawValue.new(p) }
196
+ else
197
+ ProtoUtils.convert_from_payload_array(@worker.options.client.data_converter, start.input.to_ary)
198
+ end,
199
+ headers: ProtoUtils.headers_from_proto_map(start.header_fields, @worker.options.client.data_converter) || {}
188
200
  )
189
201
 
190
202
  # Run
@@ -193,7 +205,8 @@ module Temporalio
193
205
  cancellation: Cancellation.new,
194
206
  worker_shutdown_cancellation: @worker._worker_shutdown_cancellation,
195
207
  payload_converter: @worker.options.client.data_converter.payload_converter,
196
- logger: @scoped_logger
208
+ logger: @scoped_logger,
209
+ runtime_metric_meter: @runtime_metric_meter
197
210
  )
198
211
  Activity::Context._current_executor&.set_activity_context(defn, activity)
199
212
  set_running_activity(task_token, activity)
@@ -226,9 +239,9 @@ module Temporalio
226
239
  def run_activity(activity, input)
227
240
  result = begin
228
241
  # Build impl with interceptors
229
- # @type var impl: Temporalio::Worker::Interceptor::ActivityInbound
242
+ # @type var impl: Temporalio::Worker::Interceptor::Activity::Inbound
230
243
  impl = InboundImplementation.new(self)
231
- impl = @worker._all_interceptors.reverse_each.reduce(impl) do |acc, int|
244
+ impl = @worker._activity_interceptors.reverse_each.reduce(impl) do |acc, int|
232
245
  int.intercept_activity(acc)
233
246
  end
234
247
  impl.init(OutboundImplementation.new(self))
@@ -287,13 +300,15 @@ module Temporalio
287
300
  cancellation:,
288
301
  worker_shutdown_cancellation:,
289
302
  payload_converter:,
290
- logger:
303
+ logger:,
304
+ runtime_metric_meter:
291
305
  )
292
306
  @info = info
293
307
  @cancellation = cancellation
294
308
  @worker_shutdown_cancellation = worker_shutdown_cancellation
295
309
  @payload_converter = payload_converter
296
310
  @logger = logger
311
+ @runtime_metric_meter = runtime_metric_meter
297
312
  @_outbound_impl = nil
298
313
  @_server_requested_cancel = false
299
314
  end
@@ -301,11 +316,24 @@ module Temporalio
301
316
  def heartbeat(*details)
302
317
  raise 'Implementation not set yet' if _outbound_impl.nil?
303
318
 
304
- _outbound_impl.heartbeat(Temporalio::Worker::Interceptor::HeartbeatActivityInput.new(details:))
319
+ # No-op if local
320
+ return if info.local?
321
+
322
+ _outbound_impl.heartbeat(Temporalio::Worker::Interceptor::Activity::HeartbeatInput.new(details:))
323
+ end
324
+
325
+ def metric_meter
326
+ @metric_meter ||= @runtime_metric_meter.with_additional_attributes(
327
+ {
328
+ namespace: info.workflow_namespace,
329
+ task_queue: info.task_queue,
330
+ activity_type: info.activity_type
331
+ }
332
+ )
305
333
  end
306
334
  end
307
335
 
308
- class InboundImplementation < Temporalio::Worker::Interceptor::ActivityInbound
336
+ class InboundImplementation < Temporalio::Worker::Interceptor::Activity::Inbound
309
337
  def initialize(worker)
310
338
  super(nil) # steep:ignore
311
339
  @worker = worker
@@ -323,7 +351,7 @@ module Temporalio
323
351
  end
324
352
  end
325
353
 
326
- class OutboundImplementation < Temporalio::Worker::Interceptor::ActivityOutbound
354
+ class OutboundImplementation < Temporalio::Worker::Interceptor::Activity::Outbound
327
355
  def initialize(worker)
328
356
  super(nil) # steep:ignore
329
357
  @worker = worker
@@ -6,6 +6,8 @@ require 'temporalio/internal/bridge/worker'
6
6
  module Temporalio
7
7
  module Internal
8
8
  module Worker
9
+ # Primary worker (re)actor-style event handler. This handles multiple workers, receiving events from the bridge,
10
+ # and handling a user block.
9
11
  class MultiRunner
10
12
  def initialize(workers:, shutdown_signals:)
11
13
  @workers = workers
@@ -47,6 +49,16 @@ module Temporalio
47
49
  end
48
50
  end
49
51
 
52
+ def apply_workflow_activation_decoded(workflow_worker:, activation:)
53
+ @queue.push(Event::WorkflowActivationDecoded.new(workflow_worker:, activation:))
54
+ end
55
+
56
+ def apply_workflow_activation_complete(workflow_worker:, activation_completion:, encoded:)
57
+ @queue.push(Event::WorkflowActivationComplete.new(
58
+ workflow_worker:, activation_completion:, encoded:, completion_complete_queue: @queue
59
+ ))
60
+ end
61
+
50
62
  def raise_in_thread_or_fiber_block(error)
51
63
  @thread_or_fiber&.raise(error)
52
64
  end
@@ -80,22 +92,25 @@ module Temporalio
80
92
  # * [worker index, :activity/:workflow, error] - poll fail
81
93
  # * [worker index, :activity/:workflow, nil] - worker shutdown
82
94
  # * [nil, nil, nil] - all pollers done
95
+ # * [-1, run_id_string, error_or_nil] - workflow activation completion complete
83
96
  result = @queue.pop
84
97
  if result.is_a?(Event)
85
98
  result
86
99
  else
87
- worker_index, worker_type, poll_result = result
88
- if worker_index.nil? || worker_type.nil?
100
+ first, second, third = result
101
+ if first.nil? || second.nil?
89
102
  Event::AllPollersShutDown.instance
103
+ elsif first == -1
104
+ Event::WorkflowActivationCompletionComplete.new(run_id: second, error: third)
90
105
  else
91
- worker = @workers[worker_index]
92
- case poll_result
106
+ worker = @workers[first]
107
+ case third
93
108
  when nil
94
- Event::PollerShutDown.new(worker:, worker_type:)
109
+ Event::PollerShutDown.new(worker:, worker_type: second)
95
110
  when Exception
96
- Event::PollFailure.new(worker:, worker_type:, error: poll_result)
111
+ Event::PollFailure.new(worker:, worker_type: second, error: third)
97
112
  else
98
- Event::PollSuccess.new(worker:, worker_type:, bytes: poll_result)
113
+ Event::PollSuccess.new(worker:, worker_type: second, bytes: third)
99
114
  end
100
115
  end
101
116
  end
@@ -122,6 +137,35 @@ module Temporalio
122
137
  end
123
138
  end
124
139
 
140
+ class WorkflowActivationDecoded < Event
141
+ attr_reader :workflow_worker, :activation
142
+
143
+ def initialize(workflow_worker:, activation:) # rubocop:disable Lint/MissingSuper
144
+ @workflow_worker = workflow_worker
145
+ @activation = activation
146
+ end
147
+ end
148
+
149
+ class WorkflowActivationComplete < Event
150
+ attr_reader :workflow_worker, :activation_completion, :encoded, :completion_complete_queue
151
+
152
+ def initialize(workflow_worker:, activation_completion:, encoded:, completion_complete_queue:) # rubocop:disable Lint/MissingSuper
153
+ @workflow_worker = workflow_worker
154
+ @activation_completion = activation_completion
155
+ @encoded = encoded
156
+ @completion_complete_queue = completion_complete_queue
157
+ end
158
+ end
159
+
160
+ class WorkflowActivationCompletionComplete < Event
161
+ attr_reader :run_id, :error
162
+
163
+ def initialize(run_id:, error:) # rubocop:disable Lint/MissingSuper
164
+ @run_id = run_id
165
+ @error = error
166
+ end
167
+ end
168
+
125
169
  class PollerShutDown < Event
126
170
  attr_reader :worker, :worker_type
127
171
 
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/cancellation'
4
+ require 'temporalio/workflow'
5
+ require 'temporalio/workflow/child_workflow_handle'
6
+
7
+ module Temporalio
8
+ module Internal
9
+ module Worker
10
+ class WorkflowInstance
11
+ # Implementation of the child workflow handle.
12
+ class ChildWorkflowHandle < Workflow::ChildWorkflowHandle
13
+ attr_reader :id, :first_execution_run_id
14
+
15
+ def initialize(id:, first_execution_run_id:, instance:, cancellation:, cancel_callback_key:) # rubocop:disable Lint/MissingSuper
16
+ @id = id
17
+ @first_execution_run_id = first_execution_run_id
18
+ @instance = instance
19
+ @cancellation = cancellation
20
+ @cancel_callback_key = cancel_callback_key
21
+ @resolution = nil
22
+ end
23
+
24
+ def result
25
+ # Notice that we actually provide a detached cancellation here instead of defaulting to workflow
26
+ # cancellation because we don't want workflow cancellation (or a user-provided cancellation to this result
27
+ # call) to be able to interrupt waiting on a child that may be processing the cancellation.
28
+ Workflow.wait_condition(cancellation: Cancellation.new) { @resolution }
29
+
30
+ case @resolution.status
31
+ when :completed
32
+ @instance.payload_converter.from_payload(@resolution.completed.result)
33
+ when :failed
34
+ raise @instance.failure_converter.from_failure(@resolution.failed.failure, @instance.payload_converter)
35
+ when :cancelled
36
+ raise @instance.failure_converter.from_failure(@resolution.cancelled.failure, @instance.payload_converter)
37
+ else
38
+ raise "Unrecognized resolution status: #{@resolution.status}"
39
+ end
40
+ end
41
+
42
+ def _resolve(resolution)
43
+ @cancellation.remove_cancel_callback(@cancel_callback_key)
44
+ @resolution = resolution
45
+ end
46
+
47
+ def signal(signal, *args, cancellation: Workflow.cancellation)
48
+ @instance.context._signal_child_workflow(id:, signal:, args:, cancellation:)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end