temporalio 0.6.0 → 1.0.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/Cargo.lock +148 -464
- data/README.md +2 -11
- data/ext/Cargo.toml +3 -1
- data/lib/temporalio/activity/info.rb +5 -0
- data/lib/temporalio/cancellation.rb +2 -2
- data/lib/temporalio/client/async_activity_handle.rb +1 -0
- data/lib/temporalio/env_config.rb +343 -0
- data/lib/temporalio/error.rb +5 -1
- data/lib/temporalio/internal/bridge/worker.rb +50 -0
- data/lib/temporalio/internal/client/implementation.rb +7 -2
- data/lib/temporalio/internal/worker/activity_worker.rb +1 -0
- data/lib/temporalio/internal/worker/workflow_instance/context.rb +2 -0
- data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +17 -10
- data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +6 -5
- data/lib/temporalio/internal/worker/workflow_instance.rb +77 -80
- data/lib/temporalio/testing/activity_environment.rb +1 -0
- data/lib/temporalio/version.rb +1 -1
- data/lib/temporalio/worker/interceptor.rb +1 -0
- data/lib/temporalio/worker/tuner.rb +185 -16
- data/lib/temporalio/workflow/info.rb +3 -0
- data/lib/temporalio/workflow.rb +6 -3
- metadata +2 -1
data/README.md
CHANGED
@@ -17,15 +17,6 @@ Also see:
|
|
17
17
|
|
18
18
|
**NOTE: This README is for the current branch and not necessarily what's released on RubyGems.**
|
19
19
|
|
20
|
-
⚠️ UNDER ACTIVE DEVELOPMENT
|
21
|
-
|
22
|
-
This SDK is under active development and has not released a stable version yet. APIs may change in incompatible ways
|
23
|
-
until the SDK is marked stable.
|
24
|
-
|
25
|
-
During this time, we are requesting any/all feedback from early adopters. We welcome all forms of suggestions or
|
26
|
-
opinions. Please communicate with us on [Slack](https://t.mp/slack) in the `#ruby-sdk` channel or via email at
|
27
|
-
`sdk@temporal.io`.
|
28
|
-
|
29
20
|
---
|
30
21
|
|
31
22
|
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
@@ -1246,8 +1237,8 @@ when an activity class is referenced in a workflow before it has been explicitly
|
|
1246
1237
|
> Temporalio::Workflow::Unsafe.illegal_call_tracing_disabled block.
|
1247
1238
|
|
1248
1239
|
This comes from `bootsnap` via `zeitwork` because it is lazily loading a class/module at workflow runtime. It is not
|
1249
|
-
good to lazily load code
|
1250
|
-
reference should
|
1240
|
+
good to lazily load code during a workflow run because it can be side effecting. Workflows and the classes they
|
1241
|
+
reference should be eagerly loaded.
|
1251
1242
|
|
1252
1243
|
To resolve this, either always eagerly load (e.g. `config.eager_load = true`) or explicitly `require` what is used by a
|
1253
1244
|
workflow at the top of the file.
|
data/ext/Cargo.toml
CHANGED
@@ -10,14 +10,16 @@ publish = false
|
|
10
10
|
crate-type = ["cdylib"]
|
11
11
|
|
12
12
|
[dependencies]
|
13
|
+
async-trait = "0.1"
|
13
14
|
futures = "0.3"
|
15
|
+
log = "0.4"
|
14
16
|
magnus = "0.7"
|
15
17
|
parking_lot = "0.12"
|
16
18
|
prost = "0.13"
|
17
19
|
rb-sys = "0.9"
|
18
20
|
temporal-client = { version = "0.1.0", path = "./sdk-core/client" }
|
19
21
|
temporal-sdk-core = { version = "0.1.0", path = "./sdk-core/core", features = ["ephemeral-server"] }
|
20
|
-
temporal-sdk-core-api = { version = "0.1.0", path = "./sdk-core/core-api" }
|
22
|
+
temporal-sdk-core-api = { version = "0.1.0", path = "./sdk-core/core-api", features = ["envconfig"] }
|
21
23
|
temporal-sdk-core-protos = { version = "0.1.0", path = "./sdk-core/sdk-core-protos" }
|
22
24
|
tokio = "1.37"
|
23
25
|
tokio-stream = "0.1"
|
@@ -13,6 +13,7 @@ module Temporalio
|
|
13
13
|
:heartbeat_timeout,
|
14
14
|
:local?,
|
15
15
|
:priority,
|
16
|
+
:retry_policy,
|
16
17
|
:raw_heartbeat_details,
|
17
18
|
:schedule_to_close_timeout,
|
18
19
|
:scheduled_time,
|
@@ -42,6 +43,10 @@ module Temporalio
|
|
42
43
|
# @return [Boolean] Whether the activity is a local activity or not.
|
43
44
|
# @!attribute priority
|
44
45
|
# @return [Priority] The priority of this activity.
|
46
|
+
# @!attribute retry_policy
|
47
|
+
# @return [RetryPolicy, nil] Retry policy for the activity. Note that the server may have set a different policy
|
48
|
+
# than the one provided when scheduling the activity. If the value is None, it means the server didn't send
|
49
|
+
# information about retry policy (e.g. due to old server version), but it may still be defined server-side.
|
45
50
|
# @!attribute raw_heartbeat_details
|
46
51
|
# @return [Array<Converter::RawValue>] Raw details from the last heartbeat of the last attempt. Can use
|
47
52
|
# {heartbeat_details} to get lazily-converted values.
|
@@ -167,8 +167,8 @@ module Temporalio
|
|
167
167
|
to_return.values
|
168
168
|
end
|
169
169
|
|
170
|
-
def canceled_mutex_synchronize(&)
|
171
|
-
Workflow::Unsafe.illegal_call_tracing_disabled { @canceled_mutex.synchronize(&) }
|
170
|
+
def canceled_mutex_synchronize(&block)
|
171
|
+
Workflow::Unsafe.illegal_call_tracing_disabled { @canceled_mutex.synchronize(&block) }
|
172
172
|
end
|
173
173
|
end
|
174
174
|
end
|
@@ -29,6 +29,7 @@ module Temporalio
|
|
29
29
|
# @param details [Array<Object>] Details of the heartbeat.
|
30
30
|
# @param detail_hints [Array<Object>, nil] Converter hints for the details.
|
31
31
|
# @param rpc_options [RPCOptions, nil] Advanced RPC options.
|
32
|
+
# @raise [Error::AsyncActivityCanceledError] If the activity was canceled, paused, and/or reset.
|
32
33
|
def heartbeat(*details, detail_hints: nil, rpc_options: nil)
|
33
34
|
@client._impl.heartbeat_async_activity(Interceptor::HeartbeatAsyncActivityInput.new(
|
34
35
|
task_token_or_id_reference:,
|
@@ -0,0 +1,343 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'temporalio/internal/bridge'
|
5
|
+
|
6
|
+
module Temporalio
|
7
|
+
# Environment and file-based configuration for Temporal clients
|
8
|
+
#
|
9
|
+
# WARNING: Experimental API.
|
10
|
+
module EnvConfig
|
11
|
+
# This module provides utilities to load Temporal client configuration from TOML files
|
12
|
+
# and environment variables.
|
13
|
+
|
14
|
+
# TLS configuration as specified as part of client configuration
|
15
|
+
#
|
16
|
+
# @!attribute [r] disabled
|
17
|
+
# @return [Boolean, nil] If true, TLS is explicitly disabled; if nil, not specified
|
18
|
+
# @!attribute [r] server_name
|
19
|
+
# @return [String, nil] SNI override
|
20
|
+
# @!attribute [r] server_root_ca_cert
|
21
|
+
# @return [Pathname, String, nil] Server CA certificate source
|
22
|
+
# @!attribute [r] client_cert
|
23
|
+
# @return [Pathname, String, nil] Client certificate source
|
24
|
+
# @!attribute [r] client_private_key
|
25
|
+
# @return [Pathname, String, nil] Client key source
|
26
|
+
ClientConfigTLS = Data.define(:disabled, :server_name, :server_root_ca_cert, :client_cert, :client_private_key)
|
27
|
+
|
28
|
+
# TLS configuration for Temporal client connections.
|
29
|
+
#
|
30
|
+
# This class provides methods for creating, serializing, and converting
|
31
|
+
# TLS configuration objects used by Temporal clients.
|
32
|
+
class ClientConfigTLS
|
33
|
+
# Create a ClientConfigTLS from a hash
|
34
|
+
# @param hash [Hash, nil] Hash representation
|
35
|
+
# @return [ClientConfigTLS, nil] The TLS configuration or nil if hash is nil/empty
|
36
|
+
def self.from_h(hash)
|
37
|
+
return nil if hash.nil? || hash.empty?
|
38
|
+
|
39
|
+
new(
|
40
|
+
disabled: hash[:disabled],
|
41
|
+
server_name: hash[:server_name],
|
42
|
+
server_root_ca_cert: hash_to_source(hash[:server_ca_cert]),
|
43
|
+
client_cert: hash_to_source(hash[:client_cert]),
|
44
|
+
client_private_key: hash_to_source(hash[:client_key])
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Set default values
|
49
|
+
def initialize(disabled: nil, server_name: nil, server_root_ca_cert: nil, client_cert: nil,
|
50
|
+
client_private_key: nil)
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
# Convert to a hash that can be used for TOML serialization
|
55
|
+
# @return [Hash] Dictionary representation
|
56
|
+
def to_h
|
57
|
+
{
|
58
|
+
disabled:,
|
59
|
+
server_name:,
|
60
|
+
server_ca_cert: server_root_ca_cert ? source_to_hash(server_root_ca_cert) : nil,
|
61
|
+
client_cert: client_cert ? source_to_hash(client_cert) : nil,
|
62
|
+
client_key: client_private_key ? source_to_hash(client_private_key) : nil
|
63
|
+
}.compact
|
64
|
+
end
|
65
|
+
|
66
|
+
# Create a TLS configuration for use with connections
|
67
|
+
# @return [Connection::TLSOptions, false] TLS options or false if disabled
|
68
|
+
def to_client_tls_options
|
69
|
+
return false if disabled
|
70
|
+
|
71
|
+
Client::Connection::TLSOptions.new(
|
72
|
+
domain: server_name,
|
73
|
+
server_root_ca_cert: read_source(server_root_ca_cert),
|
74
|
+
client_cert: read_source(client_cert),
|
75
|
+
client_private_key: read_source(client_private_key)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
class << self
|
82
|
+
private
|
83
|
+
|
84
|
+
# Convert hash to source object (Pathname or String)
|
85
|
+
def hash_to_source(hash)
|
86
|
+
return nil if hash.nil?
|
87
|
+
|
88
|
+
# Always expect a hash with path or data
|
89
|
+
if hash[:path]
|
90
|
+
Pathname.new(hash[:path])
|
91
|
+
elsif hash[:data]
|
92
|
+
hash[:data]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def source_to_hash(source)
|
98
|
+
case source
|
99
|
+
when Pathname
|
100
|
+
{ path: source.to_s }
|
101
|
+
when String
|
102
|
+
# String is always treated as data content
|
103
|
+
{ data: source }
|
104
|
+
when nil
|
105
|
+
nil
|
106
|
+
else
|
107
|
+
raise TypeError, "Source must be Pathname, String, or nil, got #{source.class}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def read_source(source)
|
112
|
+
case source
|
113
|
+
when Pathname
|
114
|
+
File.read(source.to_s)
|
115
|
+
when String
|
116
|
+
# String is always treated as raw data content
|
117
|
+
source
|
118
|
+
when nil
|
119
|
+
nil
|
120
|
+
else
|
121
|
+
raise TypeError, "Source must be Pathname, String, or nil, got #{source.class}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Represents a client configuration profile.
|
127
|
+
#
|
128
|
+
# This class holds the configuration as loaded from a file or environment.
|
129
|
+
# See #to_client_connect_options to transform the profile to a connect config hash.
|
130
|
+
#
|
131
|
+
# @!attribute [r] address
|
132
|
+
# @return [String, nil] Client address
|
133
|
+
# @!attribute [r] namespace
|
134
|
+
# @return [String, nil] Client namespace
|
135
|
+
# @!attribute [r] api_key
|
136
|
+
# @return [String, nil] Client API key
|
137
|
+
# @!attribute [r] tls
|
138
|
+
# @return [Boolean, ClientConfigTLS, nil] TLS configuration
|
139
|
+
# @!attribute [r] grpc_meta
|
140
|
+
# @return [Hash] gRPC metadata
|
141
|
+
ClientConfigProfile = Data.define(:address, :namespace, :api_key, :tls, :grpc_meta)
|
142
|
+
|
143
|
+
# A client configuration profile loaded from environment and files.
|
144
|
+
#
|
145
|
+
# This class represents a complete client configuration profile that can be
|
146
|
+
# loaded from TOML files and environment variables, and converted to client
|
147
|
+
# connection options.
|
148
|
+
class ClientConfigProfile
|
149
|
+
# Create a ClientConfigProfile from a hash
|
150
|
+
# @param hash [Hash] Hash representation
|
151
|
+
# @return [ClientConfigProfile] The client profile
|
152
|
+
def self.from_h(hash)
|
153
|
+
new(
|
154
|
+
address: hash[:address],
|
155
|
+
namespace: hash[:namespace],
|
156
|
+
api_key: hash[:api_key],
|
157
|
+
tls: ClientConfigTLS.from_h(hash[:tls]),
|
158
|
+
grpc_meta: hash[:grpc_meta] || {}
|
159
|
+
)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Load a single client profile from given sources, applying env overrides.
|
163
|
+
#
|
164
|
+
# @param profile [String, nil] Profile to load from the config
|
165
|
+
# @param config_source [Pathname, String, nil] Configuration source -
|
166
|
+
# Pathname for file path, String for TOML content
|
167
|
+
# @param disable_file [Boolean] If true, file loading is disabled
|
168
|
+
# @param disable_env [Boolean] If true, environment variable loading and overriding is disabled
|
169
|
+
# @param config_file_strict [Boolean] If true, will error on unrecognized keys
|
170
|
+
# @param override_env_vars [Hash, nil] Environment variables to use for loading and overrides
|
171
|
+
# @return [ClientConfigProfile] The client configuration profile
|
172
|
+
def self.load(
|
173
|
+
profile: nil,
|
174
|
+
config_source: nil,
|
175
|
+
disable_file: false,
|
176
|
+
disable_env: false,
|
177
|
+
config_file_strict: false,
|
178
|
+
override_env_vars: nil
|
179
|
+
)
|
180
|
+
path, data = EnvConfig._source_to_path_and_data(config_source)
|
181
|
+
|
182
|
+
raw_profile = Internal::Bridge::EnvConfig.load_client_connect_config(
|
183
|
+
profile,
|
184
|
+
path,
|
185
|
+
data,
|
186
|
+
disable_file,
|
187
|
+
disable_env,
|
188
|
+
config_file_strict,
|
189
|
+
override_env_vars || {}
|
190
|
+
)
|
191
|
+
|
192
|
+
from_h(raw_profile)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Create a ClientConfigProfile instance with defaults
|
196
|
+
def initialize(address: nil, namespace: nil, api_key: nil, tls: nil, grpc_meta: {})
|
197
|
+
super
|
198
|
+
end
|
199
|
+
|
200
|
+
# Convert to a hash that can be used for TOML serialization
|
201
|
+
# @return [Hash] Dictionary representation
|
202
|
+
def to_h
|
203
|
+
{
|
204
|
+
address: address,
|
205
|
+
namespace: namespace,
|
206
|
+
api_key: api_key,
|
207
|
+
tls: tls&.to_h&.then { |tls_hash| tls_hash.empty? ? nil : tls_hash }, # steep:ignore
|
208
|
+
grpc_meta: grpc_meta && grpc_meta.empty? ? nil : grpc_meta
|
209
|
+
}.compact
|
210
|
+
end
|
211
|
+
|
212
|
+
# Create a client connect config from this profile
|
213
|
+
# @return [Array] Tuple of [positional_args, keyword_args] that can be splatted to Client.connect
|
214
|
+
def to_client_connect_options
|
215
|
+
positional_args = [address, namespace].compact
|
216
|
+
tls_value = false
|
217
|
+
if tls
|
218
|
+
tls_value = tls.to_client_tls_options
|
219
|
+
elsif api_key
|
220
|
+
tls_value = true
|
221
|
+
end
|
222
|
+
|
223
|
+
keyword_args = {
|
224
|
+
api_key: api_key,
|
225
|
+
rpc_metadata: (grpc_meta if grpc_meta && !grpc_meta.empty?),
|
226
|
+
tls: tls_value
|
227
|
+
}.compact
|
228
|
+
|
229
|
+
[positional_args, keyword_args]
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Client configuration loaded from TOML and environment variables.
|
234
|
+
#
|
235
|
+
# This contains a mapping of profile names to client profiles.
|
236
|
+
#
|
237
|
+
# @!attribute [r] profiles
|
238
|
+
# @return [Hash<String, ClientConfigProfile>] Map of profile name to its corresponding ClientConfigProfile
|
239
|
+
ClientConfig = Data.define(:profiles)
|
240
|
+
|
241
|
+
# Container for multiple client configuration profiles.
|
242
|
+
#
|
243
|
+
# This class holds a collection of named client profiles loaded from
|
244
|
+
# configuration sources and provides methods for profile management
|
245
|
+
# and client connection configuration.
|
246
|
+
class ClientConfig
|
247
|
+
# Create a ClientConfig from a hash
|
248
|
+
# @param hash [Hash] Hash representation
|
249
|
+
# @return [ClientConfig] The client configuration
|
250
|
+
def self.from_h(hash)
|
251
|
+
profiles = hash.transform_values do |profile_hash|
|
252
|
+
ClientConfigProfile.from_h(profile_hash)
|
253
|
+
end
|
254
|
+
new(profiles: profiles)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Load all client profiles from given sources.
|
258
|
+
#
|
259
|
+
# This does not apply environment variable overrides to the profiles, it
|
260
|
+
# only uses an environment variable to find the default config file path
|
261
|
+
# (TEMPORAL_CONFIG_FILE).
|
262
|
+
#
|
263
|
+
# @param config_source [Pathname, String, nil] Configuration source
|
264
|
+
# @param config_file_strict [Boolean] If true, will error on unrecognized keys
|
265
|
+
# @param override_env_vars [Hash, nil] Environment variables to use
|
266
|
+
# @return [ClientConfig] The client configuration
|
267
|
+
def self.load(
|
268
|
+
config_source: nil,
|
269
|
+
config_file_strict: false,
|
270
|
+
override_env_vars: nil
|
271
|
+
)
|
272
|
+
path, data = EnvConfig._source_to_path_and_data(config_source)
|
273
|
+
|
274
|
+
loaded_profiles = Internal::Bridge::EnvConfig.load_client_config(
|
275
|
+
path,
|
276
|
+
data,
|
277
|
+
config_file_strict,
|
278
|
+
override_env_vars || {}
|
279
|
+
)
|
280
|
+
|
281
|
+
from_h(loaded_profiles)
|
282
|
+
end
|
283
|
+
|
284
|
+
# Load a single client profile and convert to connect config
|
285
|
+
#
|
286
|
+
# This is a convenience function that combines loading a profile and
|
287
|
+
# converting it to a connect config hash.
|
288
|
+
#
|
289
|
+
# @param profile [String, nil] The profile to load from the config
|
290
|
+
# @param config_source [Pathname, String, nil] Configuration source
|
291
|
+
# @param disable_file [Boolean] If true, file loading is disabled
|
292
|
+
# @param disable_env [Boolean] If true, environment variable loading and overriding is disabled
|
293
|
+
# @param config_file_strict [Boolean] If true, will error on unrecognized keys
|
294
|
+
# @param override_env_vars [Hash, nil] Environment variables to use for loading and overrides
|
295
|
+
# @return [Array] Tuple of [positional_args, keyword_args] that can be splatted to Client.connect
|
296
|
+
def self.load_client_connect_options(
|
297
|
+
profile: nil,
|
298
|
+
config_source: nil,
|
299
|
+
disable_file: false,
|
300
|
+
disable_env: false,
|
301
|
+
config_file_strict: false,
|
302
|
+
override_env_vars: nil
|
303
|
+
)
|
304
|
+
prof = ClientConfigProfile.load(
|
305
|
+
profile: profile,
|
306
|
+
config_source: config_source,
|
307
|
+
disable_file: disable_file,
|
308
|
+
disable_env: disable_env,
|
309
|
+
config_file_strict: config_file_strict,
|
310
|
+
override_env_vars: override_env_vars
|
311
|
+
)
|
312
|
+
prof.to_client_connect_options
|
313
|
+
end
|
314
|
+
|
315
|
+
# Create a ClientConfig instance with defaults
|
316
|
+
def initialize(profiles: {})
|
317
|
+
super
|
318
|
+
end
|
319
|
+
|
320
|
+
# Convert to a hash that can be used for TOML serialization
|
321
|
+
# @return [Hash] Dictionary representation
|
322
|
+
def to_h
|
323
|
+
profiles.transform_values(&:to_h)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# @param source [Pathname, String, nil] Configuration source
|
328
|
+
# @return [Array<String?, String?>] Tuple of [path, data]
|
329
|
+
# @!visibility private
|
330
|
+
def self._source_to_path_and_data(source)
|
331
|
+
case source
|
332
|
+
when Pathname
|
333
|
+
[source.to_s, nil]
|
334
|
+
when String
|
335
|
+
[nil, source]
|
336
|
+
when nil
|
337
|
+
[nil, nil]
|
338
|
+
else
|
339
|
+
raise TypeError, "Must be Pathname, String, or nil, got #{source.class}"
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
data/lib/temporalio/error.rb
CHANGED
@@ -97,9 +97,13 @@ module Temporalio
|
|
97
97
|
|
98
98
|
# Error that occurs when an async activity handle tries to heartbeat and the activity is marked as canceled.
|
99
99
|
class AsyncActivityCanceledError < Error
|
100
|
+
# @return [Activity::CancellationDetails]
|
101
|
+
attr_reader :details
|
102
|
+
|
100
103
|
# @!visibility private
|
101
|
-
def initialize
|
104
|
+
def initialize(details)
|
102
105
|
super('Activity canceled')
|
106
|
+
@details = details
|
103
107
|
end
|
104
108
|
end
|
105
109
|
|
@@ -40,6 +40,7 @@ module Temporalio
|
|
40
40
|
TunerSlotSupplierOptions = Struct.new(
|
41
41
|
:fixed_size,
|
42
42
|
:resource_based,
|
43
|
+
:custom,
|
43
44
|
keyword_init: true
|
44
45
|
)
|
45
46
|
|
@@ -103,6 +104,55 @@ module Temporalio
|
|
103
104
|
# TODO(cretz): Log error on this somehow?
|
104
105
|
async_complete_activity_task(proto.to_proto, queue)
|
105
106
|
end
|
107
|
+
|
108
|
+
class CustomSlotSupplier
|
109
|
+
def initialize(slot_supplier:, thread_pool:)
|
110
|
+
@slot_supplier = slot_supplier
|
111
|
+
@thread_pool = thread_pool
|
112
|
+
end
|
113
|
+
|
114
|
+
def reserve_slot(context, cancellation, &block)
|
115
|
+
run_user_code do
|
116
|
+
@slot_supplier.reserve_slot(context, cancellation) { |v| block.call(v) }
|
117
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
118
|
+
block.call(e)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def try_reserve_slot(context, &block)
|
123
|
+
run_user_code do
|
124
|
+
block.call(@slot_supplier.try_reserve_slot(context))
|
125
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
126
|
+
block.call(e)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def mark_slot_used(context, &block)
|
131
|
+
run_user_code do
|
132
|
+
block.call(@slot_supplier.mark_slot_used(context))
|
133
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
134
|
+
block.call(e)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def release_slot(context, &block)
|
139
|
+
run_user_code do
|
140
|
+
block.call(@slot_supplier.release_slot(context))
|
141
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
142
|
+
block.call(e)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def run_user_code(&)
|
149
|
+
if @thread_pool
|
150
|
+
@thread_pool.execute(&)
|
151
|
+
else
|
152
|
+
yield
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
106
156
|
end
|
107
157
|
end
|
108
158
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'google/protobuf/well_known_types'
|
4
4
|
require 'securerandom'
|
5
|
+
require 'temporalio/activity'
|
5
6
|
require 'temporalio/api'
|
6
7
|
require 'temporalio/client/activity_id_reference'
|
7
8
|
require 'temporalio/client/async_activity_handle'
|
@@ -829,9 +830,13 @@ module Temporalio
|
|
829
830
|
rpc_options: Implementation.with_default_rpc_options(input.rpc_options)
|
830
831
|
)
|
831
832
|
end
|
832
|
-
|
833
|
+
return unless resp.cancel_requested || resp.activity_paused || resp.activity_reset
|
833
834
|
|
834
|
-
|
835
|
+
raise Error::AsyncActivityCanceledError, Activity::CancellationDetails.new(
|
836
|
+
cancel_requested: resp.cancel_requested,
|
837
|
+
paused: resp.activity_paused,
|
838
|
+
reset: resp.activity_reset
|
839
|
+
)
|
835
840
|
end
|
836
841
|
|
837
842
|
def complete_async_activity(input)
|
@@ -185,6 +185,7 @@ module Temporalio
|
|
185
185
|
payloads = codec.decode(payloads) if codec
|
186
186
|
payloads.map { |p| Temporalio::Converters::RawValue.new(p) }
|
187
187
|
end,
|
188
|
+
retry_policy: (RetryPolicy._from_proto(start.retry_policy) if start.retry_policy),
|
188
189
|
schedule_to_close_timeout: Internal::ProtoUtils.duration_to_seconds(start.schedule_to_close_timeout),
|
189
190
|
scheduled_time: Internal::ProtoUtils.timestamp_to_time(start.scheduled_time) || raise, # Never nil
|
190
191
|
start_to_close_timeout: Internal::ProtoUtils.duration_to_seconds(start.start_to_close_timeout),
|
@@ -130,6 +130,7 @@ module Temporalio
|
|
130
130
|
def execute_local_activity(
|
131
131
|
activity,
|
132
132
|
*args,
|
133
|
+
summary:,
|
133
134
|
schedule_to_close_timeout:,
|
134
135
|
schedule_to_start_timeout:,
|
135
136
|
start_to_close_timeout:,
|
@@ -157,6 +158,7 @@ module Temporalio
|
|
157
158
|
Temporalio::Worker::Interceptor::Workflow::ExecuteLocalActivityInput.new(
|
158
159
|
activity:,
|
159
160
|
args:,
|
161
|
+
summary:,
|
160
162
|
schedule_to_close_timeout:,
|
161
163
|
schedule_to_start_timeout:,
|
162
164
|
start_to_close_timeout:,
|
@@ -84,7 +84,7 @@ module Temporalio
|
|
84
84
|
when :all
|
85
85
|
''
|
86
86
|
when Temporalio::Worker::IllegalWorkflowCallValidator
|
87
|
-
|
87
|
+
disable_temporarily do
|
88
88
|
vals.block.call(Temporalio::Worker::IllegalWorkflowCallValidator::CallInfo.new(
|
89
89
|
class_name:, method_name: tp.callee_id, trace_point: tp
|
90
90
|
))
|
@@ -98,7 +98,7 @@ module Temporalio
|
|
98
98
|
when true
|
99
99
|
''
|
100
100
|
when Temporalio::Worker::IllegalWorkflowCallValidator
|
101
|
-
|
101
|
+
disable_temporarily do
|
102
102
|
per_method.block.call(Temporalio::Worker::IllegalWorkflowCallValidator::CallInfo.new(
|
103
103
|
class_name:, method_name: tp.callee_id, trace_point: tp
|
104
104
|
))
|
@@ -118,8 +118,11 @@ module Temporalio
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def enable(&block)
|
121
|
-
# We've seen leaking issues in Ruby 3.2 where
|
122
|
-
# that it was not started on. So we will check
|
121
|
+
# This is not reentrant and not expected to be called as such. We've seen leaking issues in Ruby 3.2 where
|
122
|
+
# the TracePoint inadvertently remains enabled even for threads that it was not started on. So we will check
|
123
|
+
# the thread ourselves. We also use the "enabled thread" concept for disabling checks too, see
|
124
|
+
# disable_temporarily for more details.
|
125
|
+
|
123
126
|
@enabled_thread = Thread.current
|
124
127
|
@tracepoint.enable do
|
125
128
|
block.call
|
@@ -128,13 +131,17 @@ module Temporalio
|
|
128
131
|
end
|
129
132
|
end
|
130
133
|
|
131
|
-
def
|
134
|
+
def disable_temporarily(&)
|
135
|
+
# An earlier version of this used @tracepoint.disable, but in some versions of Ruby, the observed behavior
|
136
|
+
# is confusingly not reentrant or at least not predictable. Therefore, instead of calling
|
137
|
+
# @tracepoint.disable, we are just unsetting the enabled thread. This means the tracer is still running, but
|
138
|
+
# no checks are performed. This is effectively a no-op if tracing was never enabled.
|
139
|
+
|
132
140
|
previous_thread = @enabled_thread
|
133
|
-
@
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
end
|
141
|
+
@enabled_thread = nil
|
142
|
+
yield
|
143
|
+
ensure
|
144
|
+
@enabled_thread = previous_thread
|
138
145
|
end
|
139
146
|
end
|
140
147
|
end
|
@@ -114,14 +114,15 @@ module Temporalio
|
|
114
114
|
local_retry_threshold: ProtoUtils.seconds_to_duration(input.local_retry_threshold),
|
115
115
|
attempt: do_backoff&.attempt || 0,
|
116
116
|
original_schedule_time: do_backoff&.original_schedule_time
|
117
|
-
)
|
117
|
+
),
|
118
|
+
user_metadata: ProtoUtils.to_user_metadata(input.summary, nil, @instance.payload_converter)
|
118
119
|
)
|
119
120
|
)
|
120
121
|
seq
|
121
122
|
end
|
122
123
|
end
|
123
124
|
|
124
|
-
def execute_activity_with_local_backoffs(local:, cancellation:, result_hint:, &)
|
125
|
+
def execute_activity_with_local_backoffs(local:, cancellation:, result_hint:, &block)
|
125
126
|
# We do not even want to schedule if the cancellation is already cancelled. We choose to use canceled
|
126
127
|
# failure instead of wrapping in activity failure which is similar to what other SDKs do, with the accepted
|
127
128
|
# tradeoff that it makes rescue more difficult (hence the presence of Error.canceled? helper).
|
@@ -130,7 +131,7 @@ module Temporalio
|
|
130
131
|
# This has to be done in a loop for local activity backoff
|
131
132
|
last_local_backoff = nil
|
132
133
|
loop do
|
133
|
-
result = execute_activity_once(local:, cancellation:, last_local_backoff:, result_hint:, &)
|
134
|
+
result = execute_activity_once(local:, cancellation:, last_local_backoff:, result_hint:, &block)
|
134
135
|
return result unless result.is_a?(Bridge::Api::ActivityResult::DoBackoff)
|
135
136
|
|
136
137
|
# @type var result: untyped
|
@@ -142,9 +143,9 @@ module Temporalio
|
|
142
143
|
end
|
143
144
|
|
144
145
|
# If this doesn't raise, it returns success | DoBackoff
|
145
|
-
def execute_activity_once(local:, cancellation:, last_local_backoff:, result_hint:, &)
|
146
|
+
def execute_activity_once(local:, cancellation:, last_local_backoff:, result_hint:, &block)
|
146
147
|
# Add to pending activities (removed by the resolver)
|
147
|
-
seq =
|
148
|
+
seq = block.call(last_local_backoff)
|
148
149
|
@instance.pending_activities[seq] = Fiber.current
|
149
150
|
|
150
151
|
# Add cancellation hook
|