temporalio 0.2.0-x86_64-darwin → 0.3.0-x86_64-darwin
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 +2 -0
- data/Gemfile +3 -3
- data/Rakefile +10 -296
- data/lib/temporalio/activity/complete_async_error.rb +1 -1
- data/lib/temporalio/activity/context.rb +5 -2
- data/lib/temporalio/activity/definition.rb +163 -65
- data/lib/temporalio/activity/info.rb +22 -21
- data/lib/temporalio/activity.rb +2 -59
- data/lib/temporalio/api/activity/v1/message.rb +25 -0
- data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
- data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +34 -1
- data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +1 -1
- data/lib/temporalio/api/cloud/identity/v1/message.rb +6 -1
- data/lib/temporalio/api/cloud/namespace/v1/message.rb +8 -1
- data/lib/temporalio/api/cloud/nexus/v1/message.rb +31 -0
- data/lib/temporalio/api/cloud/operation/v1/message.rb +2 -1
- data/lib/temporalio/api/cloud/region/v1/message.rb +2 -1
- data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
- data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
- data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
- data/lib/temporalio/api/common/v1/message.rb +7 -1
- data/lib/temporalio/api/enums/v1/event_type.rb +1 -1
- data/lib/temporalio/api/enums/v1/failed_cause.rb +1 -1
- data/lib/temporalio/api/enums/v1/reset.rb +1 -1
- data/lib/temporalio/api/history/v1/message.rb +1 -1
- data/lib/temporalio/api/nexus/v1/message.rb +2 -2
- data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
- data/lib/temporalio/api/payload_visitor.rb +1513 -0
- data/lib/temporalio/api/schedule/v1/message.rb +2 -1
- data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
- data/lib/temporalio/api/testservice/v1/service.rb +23 -0
- data/lib/temporalio/api/workflow/v1/message.rb +1 -1
- data/lib/temporalio/api/workflowservice/v1/request_response.rb +17 -2
- data/lib/temporalio/api/workflowservice/v1/service.rb +1 -1
- data/lib/temporalio/api.rb +1 -0
- data/lib/temporalio/cancellation.rb +34 -14
- data/lib/temporalio/client/async_activity_handle.rb +12 -37
- data/lib/temporalio/client/connection/cloud_service.rb +309 -231
- data/lib/temporalio/client/connection/operator_service.rb +36 -84
- data/lib/temporalio/client/connection/service.rb +6 -5
- data/lib/temporalio/client/connection/test_service.rb +111 -0
- data/lib/temporalio/client/connection/workflow_service.rb +264 -441
- data/lib/temporalio/client/connection.rb +90 -44
- data/lib/temporalio/client/interceptor.rb +160 -60
- data/lib/temporalio/client/schedule.rb +967 -0
- data/lib/temporalio/client/schedule_handle.rb +126 -0
- data/lib/temporalio/client/workflow_execution.rb +7 -10
- data/lib/temporalio/client/workflow_handle.rb +38 -95
- data/lib/temporalio/client/workflow_update_handle.rb +3 -5
- data/lib/temporalio/client.rb +122 -42
- data/lib/temporalio/common_enums.rb +17 -0
- data/lib/temporalio/converters/data_converter.rb +4 -7
- data/lib/temporalio/converters/failure_converter.rb +5 -3
- data/lib/temporalio/converters/payload_converter/composite.rb +4 -0
- data/lib/temporalio/converters/payload_converter.rb +6 -8
- data/lib/temporalio/converters/raw_value.rb +20 -0
- data/lib/temporalio/error/failure.rb +1 -1
- data/lib/temporalio/error.rb +10 -2
- data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/{3.1 → 3.4}/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/api/core_interface.rb +5 -1
- data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +33 -0
- data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +5 -1
- data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +4 -1
- data/lib/temporalio/internal/bridge/client.rb +11 -6
- data/lib/temporalio/internal/bridge/testing.rb +20 -0
- data/lib/temporalio/internal/bridge/worker.rb +2 -0
- data/lib/temporalio/internal/bridge.rb +1 -1
- data/lib/temporalio/internal/client/implementation.rb +245 -70
- data/lib/temporalio/internal/metric.rb +122 -0
- data/lib/temporalio/internal/proto_utils.rb +86 -7
- data/lib/temporalio/internal/worker/activity_worker.rb +52 -24
- data/lib/temporalio/internal/worker/multi_runner.rb +51 -7
- data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
- data/lib/temporalio/internal/worker/workflow_instance/context.rb +329 -0
- data/lib/temporalio/internal/worker/workflow_instance/details.rb +44 -0
- data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
- data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
- data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
- data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
- data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
- data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
- data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +415 -0
- data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
- data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
- data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +163 -0
- data/lib/temporalio/internal/worker/workflow_instance.rb +730 -0
- data/lib/temporalio/internal/worker/workflow_worker.rb +196 -0
- data/lib/temporalio/metric.rb +109 -0
- data/lib/temporalio/retry_policy.rb +37 -14
- data/lib/temporalio/runtime.rb +118 -75
- data/lib/temporalio/search_attributes.rb +80 -37
- data/lib/temporalio/testing/activity_environment.rb +2 -2
- data/lib/temporalio/testing/workflow_environment.rb +251 -5
- data/lib/temporalio/version.rb +1 -1
- data/lib/temporalio/worker/activity_executor/thread_pool.rb +9 -217
- data/lib/temporalio/worker/activity_executor.rb +3 -3
- data/lib/temporalio/worker/interceptor.rb +340 -66
- data/lib/temporalio/worker/thread_pool.rb +237 -0
- data/lib/temporalio/worker/workflow_executor/thread_pool.rb +230 -0
- data/lib/temporalio/worker/workflow_executor.rb +26 -0
- data/lib/temporalio/worker.rb +201 -30
- data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
- data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
- data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
- data/lib/temporalio/workflow/definition.rb +566 -0
- data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
- data/lib/temporalio/workflow/future.rb +151 -0
- data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
- data/lib/temporalio/workflow/info.rb +82 -0
- data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
- data/lib/temporalio/workflow/update_info.rb +20 -0
- data/lib/temporalio/workflow.rb +523 -0
- data/lib/temporalio.rb +4 -0
- data/temporalio.gemspec +2 -2
- metadata +52 -6
@@ -7,7 +7,7 @@ module Temporalio
|
|
7
7
|
#
|
8
8
|
# This is represented as a mapping of {SearchAttributes::Key} to object values. This is not a hash though it does have
|
9
9
|
# a few hash-like methods and can be converted to a hash via {#to_h}. In some situations, such as in workflows, this
|
10
|
-
# class is
|
10
|
+
# class is immutable for outside use.
|
11
11
|
class SearchAttributes
|
12
12
|
# Key for a search attribute.
|
13
13
|
class Key
|
@@ -20,7 +20,7 @@ module Temporalio
|
|
20
20
|
def initialize(name, type)
|
21
21
|
raise ArgumentError, 'Invalid type' unless Api::Enums::V1::IndexedValueType.lookup(type)
|
22
22
|
|
23
|
-
@name = name
|
23
|
+
@name = name.to_s
|
24
24
|
@type = type
|
25
25
|
end
|
26
26
|
|
@@ -104,28 +104,54 @@ module Temporalio
|
|
104
104
|
@key = key
|
105
105
|
@value = value
|
106
106
|
end
|
107
|
+
|
108
|
+
# @!visibility private
|
109
|
+
def _to_proto_pair
|
110
|
+
SearchAttributes._to_proto_pair(key, value)
|
111
|
+
end
|
107
112
|
end
|
108
113
|
|
109
114
|
# @!visibility private
|
110
|
-
def self.
|
111
|
-
return nil unless proto
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
115
|
+
def self._from_proto(proto, disable_mutations: false, never_nil: false)
|
116
|
+
return nil unless proto || never_nil
|
117
|
+
|
118
|
+
attrs = if proto
|
119
|
+
unless proto.is_a?(Api::Common::V1::SearchAttributes)
|
120
|
+
raise ArgumentError, 'Expected proto search attribute'
|
121
|
+
end
|
122
|
+
|
123
|
+
SearchAttributes.new(proto.indexed_fields.map do |key_name, payload| # rubocop:disable Style/MapToHash
|
124
|
+
key = Key.new(key_name, IndexedValueType::PROTO_VALUES[payload.metadata['type']])
|
125
|
+
value = _value_from_payload(payload)
|
126
|
+
[key, value]
|
127
|
+
end.to_h)
|
128
|
+
else
|
129
|
+
SearchAttributes.new
|
130
|
+
end
|
131
|
+
attrs._disable_mutations = disable_mutations
|
132
|
+
attrs
|
119
133
|
end
|
120
134
|
|
121
135
|
# @!visibility private
|
122
|
-
def self.
|
136
|
+
def self._value_from_payload(payload)
|
123
137
|
value = Converters::PayloadConverter.default.from_payload(payload)
|
124
138
|
# Time needs to be converted
|
125
139
|
value = Time.iso8601(value) if payload.metadata['type'] == 'DateTime' && value.is_a?(String)
|
126
140
|
value
|
127
141
|
end
|
128
142
|
|
143
|
+
# @!visibility private
|
144
|
+
def self._to_proto_pair(key, value)
|
145
|
+
# We use a default converter, but if type is a time, we need ISO format
|
146
|
+
value = value.iso8601 if key.type == IndexedValueType::TIME && value.is_a?(Time)
|
147
|
+
|
148
|
+
# Convert to payload
|
149
|
+
payload = Converters::PayloadConverter.default.to_payload(value)
|
150
|
+
payload.metadata['type'] = IndexedValueType::PROTO_NAMES[key.type]
|
151
|
+
|
152
|
+
[key.name, payload]
|
153
|
+
end
|
154
|
+
|
129
155
|
# Create a search attribute collection.
|
130
156
|
#
|
131
157
|
# @param existing [SearchAttributes, Hash<Key, Object>, nil] Existing collection. This can be another
|
@@ -149,6 +175,7 @@ module Temporalio
|
|
149
175
|
# @param key [Key] A key to set. This must be a {Key} and the value must be proper for the {Key#type}.
|
150
176
|
# @param value [Object, nil] The value to set. If `nil`, the key is removed. The value must be proper for the `key`.
|
151
177
|
def []=(key, value)
|
178
|
+
_assert_mutations_enabled
|
152
179
|
# Key must be a Key
|
153
180
|
raise ArgumentError, 'Key must be a key' unless key.is_a?(Key)
|
154
181
|
|
@@ -162,33 +189,37 @@ module Temporalio
|
|
162
189
|
|
163
190
|
# Get a search attribute value for a key.
|
164
191
|
#
|
165
|
-
# @param key [Key, String] The key to find. If this is a {Key}, it will use key equality (i.e. name and
|
166
|
-
# search. If this is a {::String}, the type is not checked when finding the proper key.
|
192
|
+
# @param key [Key, String, Symbol] The key to find. If this is a {Key}, it will use key equality (i.e. name and
|
193
|
+
# type) to search. If this is a {::String}, the type is not checked when finding the proper key.
|
167
194
|
# @return [Object, nil] Value if found or `nil` if not.
|
168
195
|
def [](key)
|
169
196
|
# Key must be a Key or a string
|
170
|
-
|
197
|
+
case key
|
198
|
+
when Key
|
171
199
|
@raw_hash[key]
|
172
|
-
|
173
|
-
@raw_hash.find { |hash_key, _| hash_key.name == key }&.last
|
200
|
+
when String, Symbol
|
201
|
+
@raw_hash.find { |hash_key, _| hash_key.name == key.to_s }&.last
|
174
202
|
else
|
175
|
-
raise ArgumentError, 'Key must be a key or string'
|
203
|
+
raise ArgumentError, 'Key must be a key or string/symbol'
|
176
204
|
end
|
177
205
|
end
|
178
206
|
|
179
207
|
# Delete a search attribute key
|
180
208
|
#
|
181
|
-
# @param key [Key, String] The key to delete. Regardless of whether this is a {Key} or a {::String}, the key
|
182
|
-
# the matching name will be deleted. This means a {Key} with a matching name but different type may be
|
209
|
+
# @param key [Key, String, Symbol] The key to delete. Regardless of whether this is a {Key} or a {::String}, the key
|
210
|
+
# with the matching name will be deleted. This means a {Key} with a matching name but different type may be
|
211
|
+
# deleted.
|
183
212
|
def delete(key)
|
213
|
+
_assert_mutations_enabled
|
184
214
|
# Key must be a Key or a string, but we delete all values for the
|
185
215
|
# name no matter what
|
186
|
-
name =
|
216
|
+
name = case key
|
217
|
+
when Key
|
187
218
|
key.name
|
188
|
-
|
189
|
-
key
|
219
|
+
when String, Symbol
|
220
|
+
key.to_s
|
190
221
|
else
|
191
|
-
raise ArgumentError, 'Key must be a key or string'
|
222
|
+
raise ArgumentError, 'Key must be a key or string/symbol'
|
192
223
|
end
|
193
224
|
@raw_hash.delete_if { |hash_key, _| hash_key.name == name }
|
194
225
|
end
|
@@ -205,7 +236,9 @@ module Temporalio
|
|
205
236
|
|
206
237
|
# @return [SearchAttributes] Copy of the search attributes.
|
207
238
|
def dup
|
208
|
-
SearchAttributes.new(self)
|
239
|
+
attrs = SearchAttributes.new(self)
|
240
|
+
attrs._disable_mutations = false
|
241
|
+
attrs
|
209
242
|
end
|
210
243
|
|
211
244
|
# @return [Boolean] Whether the set of attributes is empty.
|
@@ -225,6 +258,7 @@ module Temporalio
|
|
225
258
|
# @param updates [Update] Updates created via {Key#value_set} or {Key#value_unset}.
|
226
259
|
# @return [SearchAttributes] New collection.
|
227
260
|
def update(*updates)
|
261
|
+
_assert_mutations_enabled
|
228
262
|
attrs = dup
|
229
263
|
attrs.update!(*updates)
|
230
264
|
attrs
|
@@ -234,27 +268,36 @@ module Temporalio
|
|
234
268
|
#
|
235
269
|
# @param updates [Update] Updates created via {Key#value_set} or {Key#value_unset}.
|
236
270
|
def update!(*updates)
|
271
|
+
_assert_mutations_enabled
|
237
272
|
updates.each do |update|
|
238
273
|
raise ArgumentError, 'Update must be an update' unless update.is_a?(Update)
|
239
274
|
|
240
|
-
|
275
|
+
if update.value.nil?
|
276
|
+
delete(update.key)
|
277
|
+
else
|
278
|
+
self[update.key] = update.value
|
279
|
+
end
|
241
280
|
end
|
242
281
|
end
|
243
282
|
|
244
283
|
# @!visibility private
|
245
|
-
def
|
246
|
-
Api::Common::V1::SearchAttributes.new(
|
247
|
-
|
248
|
-
|
249
|
-
|
284
|
+
def _to_proto
|
285
|
+
Api::Common::V1::SearchAttributes.new(indexed_fields: _to_proto_hash)
|
286
|
+
end
|
287
|
+
|
288
|
+
# @!visibility private
|
289
|
+
def _to_proto_hash
|
290
|
+
@raw_hash.to_h { |key, value| SearchAttributes._to_proto_pair(key, value) }
|
291
|
+
end
|
250
292
|
|
251
|
-
|
252
|
-
|
253
|
-
|
293
|
+
# @!visibility private
|
294
|
+
def _assert_mutations_enabled
|
295
|
+
raise 'Search attribute mutations disabled' if @disable_mutations
|
296
|
+
end
|
254
297
|
|
255
|
-
|
256
|
-
|
257
|
-
|
298
|
+
# @!visibility private
|
299
|
+
def _disable_mutations=(value)
|
300
|
+
@disable_mutations = value
|
258
301
|
end
|
259
302
|
|
260
303
|
# Type for a search attribute key/value.
|
@@ -67,11 +67,11 @@ module Temporalio
|
|
67
67
|
|
68
68
|
# Run an activity and returns its result or raises its exception.
|
69
69
|
#
|
70
|
-
# @param activity [Activity, Class<Activity>, Activity::Definition] Activity to run.
|
70
|
+
# @param activity [Activity::Definition, Class<Activity::Definition>, Activity::Definition::Info] Activity to run.
|
71
71
|
# @param args [Array<Object>] Arguments to the activity.
|
72
72
|
# @return Activity result.
|
73
73
|
def run(activity, *args)
|
74
|
-
defn = Activity::Definition.from_activity(activity)
|
74
|
+
defn = Activity::Definition::Info.from_activity(activity)
|
75
75
|
executor = @activity_executors[defn.executor]
|
76
76
|
raise ArgumentError, "Unknown executor: #{defn.executor}" if executor.nil?
|
77
77
|
|
@@ -1,8 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'delegate'
|
4
|
+
require 'temporalio/api'
|
5
|
+
require 'temporalio/api/testservice/v1/request_response'
|
3
6
|
require 'temporalio/client'
|
7
|
+
require 'temporalio/client/connection/test_service'
|
8
|
+
require 'temporalio/client/workflow_handle'
|
4
9
|
require 'temporalio/converters'
|
5
10
|
require 'temporalio/internal/bridge/testing'
|
11
|
+
require 'temporalio/internal/proto_utils'
|
6
12
|
require 'temporalio/runtime'
|
7
13
|
require 'temporalio/version'
|
8
14
|
|
@@ -63,7 +69,8 @@ module Temporalio
|
|
63
69
|
dev_server_log_level: 'warn',
|
64
70
|
dev_server_download_version: 'default',
|
65
71
|
dev_server_download_dest_dir: nil,
|
66
|
-
dev_server_extra_args: []
|
72
|
+
dev_server_extra_args: [],
|
73
|
+
&
|
67
74
|
)
|
68
75
|
server_options = Internal::Bridge::Testing::EphemeralServer::StartDevServerOptions.new(
|
69
76
|
existing_path: dev_server_existing_path,
|
@@ -80,7 +87,96 @@ module Temporalio
|
|
80
87
|
log_level: dev_server_log_level,
|
81
88
|
extra_args: dev_server_extra_args
|
82
89
|
)
|
83
|
-
|
90
|
+
_with_core_server(
|
91
|
+
core_server: Internal::Bridge::Testing::EphemeralServer.start_dev_server(
|
92
|
+
runtime._core_runtime, server_options
|
93
|
+
),
|
94
|
+
namespace:,
|
95
|
+
data_converter:,
|
96
|
+
interceptors:,
|
97
|
+
logger:,
|
98
|
+
default_workflow_query_reject_condition:,
|
99
|
+
runtime:,
|
100
|
+
supports_time_skipping: false,
|
101
|
+
& # steep:ignore
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Start a time-skipping test server. This server can skip time but may not have all of the Temporal features of
|
106
|
+
# the {start_local} form. By default, the server is downloaded to tmp if not already present. The test server is
|
107
|
+
# run as a child process. All options that start with +test_server_+ are for this specific implementation and
|
108
|
+
# therefore are not stable and may be changed as the underlying implementation changes.
|
109
|
+
#
|
110
|
+
# If a block is given it is passed the environment and the environment is shut down after. If a block is not
|
111
|
+
# given, the environment is returned and {shutdown} needs to be called manually.
|
112
|
+
#
|
113
|
+
# @param data_converter [Converters::DataConverter] Data converter for the client.
|
114
|
+
# @param interceptors [Array<Client::Interceptor>] Interceptors for the client.
|
115
|
+
# @param logger [Logger] Logger for the client.
|
116
|
+
# @param default_workflow_query_reject_condition [WorkflowQueryRejectCondition, nil] Default rejection condition
|
117
|
+
# for the client.
|
118
|
+
# @param port [Integer, nil] Port to bind on, or +nil+ for random.
|
119
|
+
# @param runtime [Runtime] Runtime for the server and client.
|
120
|
+
# @param test_server_existing_path [String, nil] Existing CLI path to use instead of downloading and caching to
|
121
|
+
# tmp.
|
122
|
+
# @param test_server_download_version [String] Version of test server to download and cache.
|
123
|
+
# @param test_server_download_dest_dir [String, nil] Where to download. Defaults to tmp.
|
124
|
+
# @param test_server_extra_args [Array<String>] Any extra arguments for the test server.
|
125
|
+
#
|
126
|
+
# @yield [environment] If a block is given, it is called with the environment and upon complete the environment is
|
127
|
+
# shutdown.
|
128
|
+
# @yieldparam environment [WorkflowEnvironment] Environment that is shut down upon block completion.
|
129
|
+
#
|
130
|
+
# @return [WorkflowEnvironment, Object] Started local server environment with client if there was no block given,
|
131
|
+
# or block result if block was given.
|
132
|
+
def self.start_time_skipping(
|
133
|
+
data_converter: Converters::DataConverter.default,
|
134
|
+
interceptors: [],
|
135
|
+
logger: Logger.new($stdout, level: Logger::WARN),
|
136
|
+
default_workflow_query_reject_condition: nil,
|
137
|
+
port: nil,
|
138
|
+
runtime: Runtime.default,
|
139
|
+
test_server_existing_path: nil,
|
140
|
+
test_server_download_version: 'default',
|
141
|
+
test_server_download_dest_dir: nil,
|
142
|
+
test_server_extra_args: [],
|
143
|
+
&
|
144
|
+
)
|
145
|
+
server_options = Internal::Bridge::Testing::EphemeralServer::StartTestServerOptions.new(
|
146
|
+
existing_path: test_server_existing_path,
|
147
|
+
sdk_name: 'sdk-ruby',
|
148
|
+
sdk_version: VERSION,
|
149
|
+
download_version: test_server_download_version,
|
150
|
+
download_dest_dir: test_server_download_dest_dir,
|
151
|
+
port:,
|
152
|
+
extra_args: test_server_extra_args
|
153
|
+
)
|
154
|
+
_with_core_server(
|
155
|
+
core_server: Internal::Bridge::Testing::EphemeralServer.start_test_server(
|
156
|
+
runtime._core_runtime, server_options
|
157
|
+
),
|
158
|
+
namespace: 'default',
|
159
|
+
data_converter:,
|
160
|
+
interceptors:,
|
161
|
+
logger:,
|
162
|
+
default_workflow_query_reject_condition:,
|
163
|
+
runtime:,
|
164
|
+
supports_time_skipping: true,
|
165
|
+
& # steep:ignore
|
166
|
+
)
|
167
|
+
end
|
168
|
+
|
169
|
+
# @!visibility private
|
170
|
+
def self._with_core_server(
|
171
|
+
core_server:,
|
172
|
+
namespace:,
|
173
|
+
data_converter:,
|
174
|
+
interceptors:,
|
175
|
+
logger:,
|
176
|
+
default_workflow_query_reject_condition:,
|
177
|
+
runtime:,
|
178
|
+
supports_time_skipping:
|
179
|
+
)
|
84
180
|
# Try to connect, shutdown if we can't
|
85
181
|
begin
|
86
182
|
client = Client.connect(
|
@@ -92,8 +188,8 @@ module Temporalio
|
|
92
188
|
default_workflow_query_reject_condition:,
|
93
189
|
runtime:
|
94
190
|
)
|
95
|
-
server = Ephemeral.new(client, core_server)
|
96
|
-
rescue
|
191
|
+
server = Ephemeral.new(client, core_server, supports_time_skipping:)
|
192
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
97
193
|
core_server.shutdown
|
98
194
|
raise
|
99
195
|
end
|
@@ -120,18 +216,168 @@ module Temporalio
|
|
120
216
|
# Do nothing by default
|
121
217
|
end
|
122
218
|
|
219
|
+
# @return [Boolean] Whether this environment supports time skipping.
|
220
|
+
def supports_time_skipping?
|
221
|
+
false
|
222
|
+
end
|
223
|
+
|
224
|
+
# Advanced time.
|
225
|
+
#
|
226
|
+
# If this server supports time skipping, this will immediately advance time and return. If it does not, this is
|
227
|
+
# a standard {::sleep}.
|
228
|
+
#
|
229
|
+
# @param duration [Float] Duration seconds.
|
230
|
+
def sleep(duration)
|
231
|
+
Kernel.sleep(duration)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Current time of the environment.
|
235
|
+
#
|
236
|
+
# If this server supports time skipping, this will be the current time as known to the environment. If it does
|
237
|
+
# not, this is a standard {::Time.now}.
|
238
|
+
#
|
239
|
+
# @return [Time] Current time.
|
240
|
+
def current_time
|
241
|
+
Time.now
|
242
|
+
end
|
243
|
+
|
244
|
+
# Run a block with automatic time skipping disabled. This just runs the block for environments that don't support
|
245
|
+
# time skipping.
|
246
|
+
#
|
247
|
+
# @yield Block to run.
|
248
|
+
# @return [Object] Result of the block.
|
249
|
+
def auto_time_skipping_disabled(&)
|
250
|
+
raise 'Block required' unless block_given?
|
251
|
+
|
252
|
+
yield
|
253
|
+
end
|
254
|
+
|
123
255
|
# @!visibility private
|
124
256
|
class Ephemeral < WorkflowEnvironment
|
125
|
-
def initialize(client, core_server)
|
257
|
+
def initialize(client, core_server, supports_time_skipping:)
|
258
|
+
# Add our interceptor at the end of the existing interceptors that skips time
|
259
|
+
client_options = client.options.with(
|
260
|
+
interceptors: client.options.interceptors + [TimeSkippingClientInterceptor.new(self)]
|
261
|
+
)
|
262
|
+
client = Client.new(**client_options.to_h) # steep:ignore
|
126
263
|
super(client)
|
264
|
+
|
265
|
+
@auto_time_skipping = true
|
127
266
|
@core_server = core_server
|
267
|
+
@test_service = Client::Connection::TestService.new(client.connection) if supports_time_skipping
|
128
268
|
end
|
129
269
|
|
130
270
|
# @!visibility private
|
131
271
|
def shutdown
|
132
272
|
@core_server.shutdown
|
133
273
|
end
|
274
|
+
|
275
|
+
# @!visibility private
|
276
|
+
def supports_time_skipping?
|
277
|
+
!@test_service.nil?
|
278
|
+
end
|
279
|
+
|
280
|
+
# @!visibility private
|
281
|
+
def sleep(duration)
|
282
|
+
return super unless supports_time_skipping?
|
283
|
+
|
284
|
+
@test_service.unlock_time_skipping_with_sleep(
|
285
|
+
Api::TestService::V1::SleepRequest.new(duration: Internal::ProtoUtils.seconds_to_duration(duration))
|
286
|
+
)
|
287
|
+
end
|
288
|
+
|
289
|
+
# @!visibility private
|
290
|
+
def current_time
|
291
|
+
return super unless supports_time_skipping?
|
292
|
+
|
293
|
+
resp = @test_service.get_current_time(Google::Protobuf::Empty.new)
|
294
|
+
Internal::ProtoUtils.timestamp_to_time(resp.time) or raise 'Time missing'
|
295
|
+
end
|
296
|
+
|
297
|
+
# @!visibility private
|
298
|
+
def auto_time_skipping_disabled(&)
|
299
|
+
raise 'Block required' unless block_given?
|
300
|
+
return super unless supports_time_skipping?
|
301
|
+
|
302
|
+
already_disabled = @auto_time_skipping
|
303
|
+
@auto_time_skipping = false
|
304
|
+
begin
|
305
|
+
yield
|
306
|
+
ensure
|
307
|
+
@auto_time_skipping = true unless already_disabled
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# @!visibility private
|
312
|
+
def time_skipping_unlocked(&)
|
313
|
+
# If disabled or unsupported, no locking/unlocking, just run and return
|
314
|
+
return yield if !supports_time_skipping? || !@auto_time_skipping
|
315
|
+
|
316
|
+
# Unlock to start time skipping, lock again to stop it
|
317
|
+
@test_service.unlock_time_skipping(Api::TestService::V1::UnlockTimeSkippingRequest.new)
|
318
|
+
user_code_success = false
|
319
|
+
begin
|
320
|
+
result = yield
|
321
|
+
user_code_success = true
|
322
|
+
result
|
323
|
+
ensure
|
324
|
+
# Lock it back
|
325
|
+
begin
|
326
|
+
@test_service.lock_time_skipping(Api::TestService::V1::LockTimeSkippingRequest.new)
|
327
|
+
rescue StandardError => e
|
328
|
+
# Re-raise if user code succeeded, otherwise swallow
|
329
|
+
raise if user_code_success
|
330
|
+
|
331
|
+
client.options.logger.error('Failed locking time skipping after error')
|
332
|
+
client.options.logger.error(e)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
private_constant :Ephemeral
|
339
|
+
|
340
|
+
# @!visibility private
|
341
|
+
class TimeSkippingClientInterceptor
|
342
|
+
include Client::Interceptor
|
343
|
+
|
344
|
+
def initialize(env)
|
345
|
+
@env = env
|
346
|
+
end
|
347
|
+
|
348
|
+
# @!visibility private
|
349
|
+
def intercept_client(next_interceptor)
|
350
|
+
Outbound.new(next_interceptor, @env)
|
351
|
+
end
|
352
|
+
|
353
|
+
# @!visibility private
|
354
|
+
class Outbound < Client::Interceptor::Outbound
|
355
|
+
def initialize(next_interceptor, env)
|
356
|
+
super(next_interceptor)
|
357
|
+
@env = env
|
358
|
+
end
|
359
|
+
|
360
|
+
# @!visibility private
|
361
|
+
def start_workflow(input)
|
362
|
+
TimeSkippingWorkflowHandle.new(super, @env)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# @!visibility private
|
367
|
+
class TimeSkippingWorkflowHandle < SimpleDelegator
|
368
|
+
def initialize(handle, env)
|
369
|
+
super(handle) # steep:ignore
|
370
|
+
@env = env
|
371
|
+
end
|
372
|
+
|
373
|
+
# @!visibility private
|
374
|
+
def result(follow_runs: true, rpc_options: nil)
|
375
|
+
@env.time_skipping_unlocked { super(follow_runs:, rpc_options:) }
|
376
|
+
end
|
377
|
+
end
|
134
378
|
end
|
379
|
+
|
380
|
+
private_constant :TimeSkippingClientInterceptor
|
135
381
|
end
|
136
382
|
end
|
137
383
|
end
|
data/lib/temporalio/version.rb
CHANGED