temporalio 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +2 -0
- data/Cargo.lock +659 -370
- data/Cargo.toml +2 -2
- data/Gemfile +3 -3
- data/README.md +589 -47
- data/Rakefile +10 -296
- data/ext/Cargo.toml +1 -0
- 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/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 +50 -8
@@ -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