temporalio 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +2 -0
- data/Cargo.lock +980 -583
- data/Cargo.toml +2 -2
- data/Gemfile +7 -3
- data/README.md +769 -54
- data/Rakefile +10 -296
- data/ext/Cargo.toml +2 -0
- data/lib/temporalio/activity/complete_async_error.rb +1 -1
- data/lib/temporalio/activity/context.rb +18 -2
- data/lib/temporalio/activity/definition.rb +180 -65
- data/lib/temporalio/activity/info.rb +25 -21
- data/lib/temporalio/activity.rb +2 -59
- data/lib/temporalio/api/activity/v1/message.rb +25 -0
- data/lib/temporalio/api/batch/v1/message.rb +6 -1
- 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/command/v1/message.rb +1 -1
- data/lib/temporalio/api/common/v1/message.rb +8 -1
- data/lib/temporalio/api/deployment/v1/message.rb +38 -0
- data/lib/temporalio/api/enums/v1/batch_operation.rb +1 -1
- data/lib/temporalio/api/enums/v1/common.rb +1 -1
- data/lib/temporalio/api/enums/v1/deployment.rb +23 -0
- 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/nexus.rb +21 -0
- data/lib/temporalio/api/enums/v1/reset.rb +1 -1
- data/lib/temporalio/api/enums/v1/workflow.rb +2 -1
- data/lib/temporalio/api/errordetails/v1/message.rb +3 -1
- data/lib/temporalio/api/failure/v1/message.rb +3 -1
- data/lib/temporalio/api/history/v1/message.rb +3 -1
- data/lib/temporalio/api/nexus/v1/message.rb +3 -2
- data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
- data/lib/temporalio/api/payload_visitor.rb +1581 -0
- data/lib/temporalio/api/query/v1/message.rb +2 -1
- data/lib/temporalio/api/schedule/v1/message.rb +2 -1
- data/lib/temporalio/api/taskqueue/v1/message.rb +4 -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 +9 -1
- data/lib/temporalio/api/workflowservice/v1/request_response.rb +46 -2
- data/lib/temporalio/api/workflowservice/v1/service.rb +1 -1
- data/lib/temporalio/api.rb +2 -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 +474 -441
- data/lib/temporalio/client/connection.rb +90 -44
- data/lib/temporalio/client/interceptor.rb +199 -60
- data/lib/temporalio/client/schedule.rb +991 -0
- data/lib/temporalio/client/schedule_handle.rb +126 -0
- data/lib/temporalio/client/with_start_workflow_operation.rb +115 -0
- data/lib/temporalio/client/workflow_execution.rb +26 -10
- data/lib/temporalio/client/workflow_handle.rb +41 -98
- data/lib/temporalio/client/workflow_update_handle.rb +3 -5
- data/lib/temporalio/client.rb +247 -44
- data/lib/temporalio/common_enums.rb +17 -0
- data/lib/temporalio/contrib/open_telemetry.rb +470 -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 +11 -2
- data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +1 -1
- data/lib/temporalio/internal/bridge/api/common/common.rb +2 -1
- 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/api/workflow_completion/workflow_completion.rb +2 -1
- data/lib/temporalio/internal/bridge/client.rb +11 -6
- data/lib/temporalio/internal/bridge/runtime.rb +3 -0
- data/lib/temporalio/internal/bridge/testing.rb +23 -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 +468 -71
- data/lib/temporalio/internal/metric.rb +122 -0
- data/lib/temporalio/internal/proto_utils.rb +118 -7
- data/lib/temporalio/internal/worker/activity_worker.rb +69 -29
- data/lib/temporalio/internal/worker/multi_runner.rb +53 -9
- data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
- data/lib/temporalio/internal/worker/workflow_instance/context.rb +383 -0
- data/lib/temporalio/internal/worker/workflow_instance/details.rb +46 -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 +400 -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 +183 -0
- data/lib/temporalio/internal/worker/workflow_instance.rb +774 -0
- data/lib/temporalio/internal/worker/workflow_worker.rb +239 -0
- data/lib/temporalio/metric.rb +109 -0
- data/lib/temporalio/retry_policy.rb +37 -14
- data/lib/temporalio/runtime/metric_buffer.rb +94 -0
- data/lib/temporalio/runtime.rb +160 -79
- data/lib/temporalio/search_attributes.rb +93 -37
- data/lib/temporalio/testing/activity_environment.rb +44 -16
- data/lib/temporalio/testing/workflow_environment.rb +276 -7
- 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 +343 -66
- data/lib/temporalio/worker/thread_pool.rb +237 -0
- data/lib/temporalio/worker/tuner.rb +38 -0
- data/lib/temporalio/worker/workflow_executor/thread_pool.rb +235 -0
- data/lib/temporalio/worker/workflow_executor.rb +26 -0
- data/lib/temporalio/worker/workflow_replayer.rb +350 -0
- data/lib/temporalio/worker.rb +235 -58
- 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 +598 -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 +104 -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 +575 -0
- data/lib/temporalio/workflow_history.rb +26 -1
- data/lib/temporalio.rb +4 -0
- data/temporalio.gemspec +4 -3
- metadata +73 -10
@@ -13,8 +13,8 @@ module Temporalio
|
|
13
13
|
# cancellation can be set, users create this for each activity that is run. There is no real performance penalty for
|
14
14
|
# creating an environment for every run.
|
15
15
|
class ActivityEnvironment
|
16
|
-
# @return [Activity::Info] The activity info used by default. This is frozen, but can be
|
17
|
-
# in to {initialize}.
|
16
|
+
# @return [Activity::Info] The activity info used by default. This is frozen, but `with` can be used to make a new
|
17
|
+
# instance with changes to pass in to {initialize}.
|
18
18
|
def self.default_info
|
19
19
|
@default_info ||= Activity::Info.new(
|
20
20
|
activity_id: 'test',
|
@@ -34,12 +34,13 @@ module Temporalio
|
|
34
34
|
workflow_namespace: 'default',
|
35
35
|
workflow_run_id: 'test-run',
|
36
36
|
workflow_type: 'test'
|
37
|
-
)
|
37
|
+
)
|
38
38
|
end
|
39
39
|
|
40
40
|
# Create a test environment for activities.
|
41
41
|
#
|
42
|
-
# @param info [Activity::Info] Value for {Activity::Context#info}.
|
42
|
+
# @param info [Activity::Info] Value for {Activity::Context#info}. Users should not try to instantiate this
|
43
|
+
# themselves, but rather use `with` on {default_info}.
|
43
44
|
# @param on_heartbeat [Proc(Array), nil] Proc that is called with all heartbeat details when
|
44
45
|
# {Activity::Context#heartbeat} is called.
|
45
46
|
# @param cancellation [Cancellation] Value for {Activity::Context#cancellation}.
|
@@ -47,6 +48,9 @@ module Temporalio
|
|
47
48
|
# @param payload_converter [Converters::PayloadConverter] Value for {Activity::Context#payload_converter}.
|
48
49
|
# @param logger [Logger] Value for {Activity::Context#logger}.
|
49
50
|
# @param activity_executors [Hash<Symbol, Worker::ActivityExecutor>] Executors that activities can run within.
|
51
|
+
# @param metric_meter [Metric::Meter, nil] Value for {Activity::Context#metric_meter}, or nil to raise when
|
52
|
+
# called.
|
53
|
+
# @param client [Client, nil] Value for {Activity::Context#client}, or nil to raise when called.
|
50
54
|
def initialize(
|
51
55
|
info: ActivityEnvironment.default_info,
|
52
56
|
on_heartbeat: nil,
|
@@ -54,7 +58,9 @@ module Temporalio
|
|
54
58
|
worker_shutdown_cancellation: Cancellation.new,
|
55
59
|
payload_converter: Converters::PayloadConverter.default,
|
56
60
|
logger: Logger.new(nil),
|
57
|
-
activity_executors: Worker::ActivityExecutor.defaults
|
61
|
+
activity_executors: Worker::ActivityExecutor.defaults,
|
62
|
+
metric_meter: nil,
|
63
|
+
client: nil
|
58
64
|
)
|
59
65
|
@info = info
|
60
66
|
@on_heartbeat = on_heartbeat
|
@@ -63,15 +69,17 @@ module Temporalio
|
|
63
69
|
@payload_converter = payload_converter
|
64
70
|
@logger = logger
|
65
71
|
@activity_executors = activity_executors
|
72
|
+
@metric_meter = metric_meter
|
73
|
+
@client = client
|
66
74
|
end
|
67
75
|
|
68
76
|
# Run an activity and returns its result or raises its exception.
|
69
77
|
#
|
70
|
-
# @param activity [Activity, Class<Activity>, Activity::Definition] Activity to run.
|
78
|
+
# @param activity [Activity::Definition, Class<Activity::Definition>, Activity::Definition::Info] Activity to run.
|
71
79
|
# @param args [Array<Object>] Arguments to the activity.
|
72
80
|
# @return Activity result.
|
73
81
|
def run(activity, *args)
|
74
|
-
defn = Activity::Definition.from_activity(activity)
|
82
|
+
defn = Activity::Definition::Info.from_activity(activity)
|
75
83
|
executor = @activity_executors[defn.executor]
|
76
84
|
raise ArgumentError, "Unknown executor: #{defn.executor}" if executor.nil?
|
77
85
|
|
@@ -80,14 +88,18 @@ module Temporalio
|
|
80
88
|
Activity::Context._current_executor = executor
|
81
89
|
executor.set_activity_context(defn, Context.new(
|
82
90
|
info: @info.dup,
|
91
|
+
instance:
|
92
|
+
defn.instance.is_a?(Proc) ? defn.instance.call : defn.instance,
|
83
93
|
on_heartbeat: @on_heartbeat,
|
84
94
|
cancellation: @cancellation,
|
85
95
|
worker_shutdown_cancellation: @worker_shutdown_cancellation,
|
86
96
|
payload_converter: @payload_converter,
|
87
|
-
logger: @logger
|
97
|
+
logger: @logger,
|
98
|
+
metric_meter: @metric_meter,
|
99
|
+
client: @client
|
88
100
|
))
|
89
101
|
queue.push([defn.proc.call(*args), nil])
|
90
|
-
rescue Exception => e # rubocop:disable Lint/RescueException Intentionally capturing all exceptions
|
102
|
+
rescue Exception => e # rubocop:disable Lint/RescueException -- Intentionally capturing all exceptions
|
91
103
|
queue.push([nil, e])
|
92
104
|
ensure
|
93
105
|
executor.set_activity_context(defn, nil)
|
@@ -102,28 +114,44 @@ module Temporalio
|
|
102
114
|
|
103
115
|
# @!visibility private
|
104
116
|
class Context < Activity::Context
|
105
|
-
attr_reader :info, :cancellation, :worker_shutdown_cancellation, :payload_converter, :logger
|
117
|
+
attr_reader :info, :instance, :cancellation, :worker_shutdown_cancellation, :payload_converter, :logger
|
106
118
|
|
107
119
|
def initialize( # rubocop:disable Lint/MissingSuper
|
108
|
-
info
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
120
|
+
info:,
|
121
|
+
instance:,
|
122
|
+
on_heartbeat:,
|
123
|
+
cancellation:,
|
124
|
+
worker_shutdown_cancellation:,
|
125
|
+
payload_converter:,
|
126
|
+
logger:,
|
127
|
+
metric_meter:,
|
128
|
+
client:
|
114
129
|
)
|
115
130
|
@info = info
|
131
|
+
@instance = instance
|
116
132
|
@on_heartbeat = on_heartbeat
|
117
133
|
@cancellation = cancellation
|
118
134
|
@worker_shutdown_cancellation = worker_shutdown_cancellation
|
119
135
|
@payload_converter = payload_converter
|
120
136
|
@logger = logger
|
137
|
+
@metric_meter = metric_meter
|
138
|
+
@client = client
|
121
139
|
end
|
122
140
|
|
123
141
|
# @!visibility private
|
124
142
|
def heartbeat(*details)
|
125
143
|
@on_heartbeat&.call(details)
|
126
144
|
end
|
145
|
+
|
146
|
+
# @!visibility private
|
147
|
+
def metric_meter
|
148
|
+
@metric_meter or raise 'No metric meter configured in this test environment'
|
149
|
+
end
|
150
|
+
|
151
|
+
# @!visibility private
|
152
|
+
def client
|
153
|
+
@client or raise 'No client configured in this test environment'
|
154
|
+
end
|
127
155
|
end
|
128
156
|
|
129
157
|
private_constant :Context
|
@@ -1,9 +1,16 @@
|
|
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'
|
13
|
+
require 'temporalio/search_attributes'
|
7
14
|
require 'temporalio/version'
|
8
15
|
|
9
16
|
module Temporalio
|
@@ -28,8 +35,10 @@ module Temporalio
|
|
28
35
|
# @param default_workflow_query_reject_condition [WorkflowQueryRejectCondition, nil] Default rejection condition
|
29
36
|
# for the client.
|
30
37
|
# @param ip [String] IP to bind to.
|
31
|
-
# @param port [Integer, nil] Port to bind on, or
|
38
|
+
# @param port [Integer, nil] Port to bind on, or `nil` for random.
|
32
39
|
# @param ui [Boolean] If +true+, also starts the UI.
|
40
|
+
# @param ui_port [Integer, nil] Port to bind on if `ui` is true, or `nil` for random.
|
41
|
+
# @param search_attributes [Array<SearchAttributes::Key>] Search attributes to make available on start.
|
33
42
|
# @param runtime [Runtime] Runtime for the server and client.
|
34
43
|
# @param dev_server_existing_path [String, nil] Existing CLI path to use instead of downloading and caching to
|
35
44
|
# tmp.
|
@@ -40,6 +49,8 @@ module Temporalio
|
|
40
49
|
# @param dev_server_download_version [String] Version of dev server to download and cache.
|
41
50
|
# @param dev_server_download_dest_dir [String, nil] Where to download. Defaults to tmp.
|
42
51
|
# @param dev_server_extra_args [Array<String>] Any extra arguments for the CLI dev server.
|
52
|
+
# @param dev_server_download_ttl [Float, nil] How long the automatic download should be cached for. If nil, cached
|
53
|
+
# indefinitely.
|
43
54
|
#
|
44
55
|
# @yield [environment] If a block is given, it is called with the environment and upon complete the environment is
|
45
56
|
# shutdown.
|
@@ -56,6 +67,8 @@ module Temporalio
|
|
56
67
|
ip: '127.0.0.1',
|
57
68
|
port: nil,
|
58
69
|
ui: false, # rubocop:disable Naming/MethodParameterName
|
70
|
+
ui_port: nil,
|
71
|
+
search_attributes: [],
|
59
72
|
runtime: Runtime.default,
|
60
73
|
dev_server_existing_path: nil,
|
61
74
|
dev_server_database_filename: nil,
|
@@ -63,8 +76,19 @@ module Temporalio
|
|
63
76
|
dev_server_log_level: 'warn',
|
64
77
|
dev_server_download_version: 'default',
|
65
78
|
dev_server_download_dest_dir: nil,
|
66
|
-
dev_server_extra_args: []
|
79
|
+
dev_server_extra_args: [],
|
80
|
+
dev_server_download_ttl: nil,
|
81
|
+
&
|
67
82
|
)
|
83
|
+
# Add search attribute args
|
84
|
+
unless search_attributes.empty?
|
85
|
+
dev_server_extra_args += search_attributes.flat_map do |key|
|
86
|
+
raise 'Search attribute must be Key' unless key.is_a?(SearchAttributes::Key)
|
87
|
+
|
88
|
+
['--search-attribute', "#{key.name}=#{SearchAttributes::IndexedValueType::PROTO_NAMES[key.type]}"]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
68
92
|
server_options = Internal::Bridge::Testing::EphemeralServer::StartDevServerOptions.new(
|
69
93
|
existing_path: dev_server_existing_path,
|
70
94
|
sdk_name: 'sdk-ruby',
|
@@ -76,11 +100,106 @@ module Temporalio
|
|
76
100
|
port:,
|
77
101
|
database_filename: dev_server_database_filename,
|
78
102
|
ui:,
|
103
|
+
ui_port: ui ? ui_port : nil,
|
79
104
|
log_format: dev_server_log_format,
|
80
105
|
log_level: dev_server_log_level,
|
81
|
-
extra_args: dev_server_extra_args
|
106
|
+
extra_args: dev_server_extra_args,
|
107
|
+
download_ttl: dev_server_download_ttl
|
108
|
+
)
|
109
|
+
_with_core_server(
|
110
|
+
core_server: Internal::Bridge::Testing::EphemeralServer.start_dev_server(
|
111
|
+
runtime._core_runtime, server_options
|
112
|
+
),
|
113
|
+
namespace:,
|
114
|
+
data_converter:,
|
115
|
+
interceptors:,
|
116
|
+
logger:,
|
117
|
+
default_workflow_query_reject_condition:,
|
118
|
+
runtime:,
|
119
|
+
supports_time_skipping: false,
|
120
|
+
& # steep:ignore
|
82
121
|
)
|
83
|
-
|
122
|
+
end
|
123
|
+
|
124
|
+
# Start a time-skipping test server. This server can skip time but may not have all of the Temporal features of
|
125
|
+
# the {start_local} form. By default, the server is downloaded to tmp if not already present. The test server is
|
126
|
+
# run as a child process. All options that start with +test_server_+ are for this specific implementation and
|
127
|
+
# therefore are not stable and may be changed as the underlying implementation changes.
|
128
|
+
#
|
129
|
+
# If a block is given it is passed the environment and the environment is shut down after. If a block is not
|
130
|
+
# given, the environment is returned and {shutdown} needs to be called manually.
|
131
|
+
#
|
132
|
+
# @param data_converter [Converters::DataConverter] Data converter for the client.
|
133
|
+
# @param interceptors [Array<Client::Interceptor>] Interceptors for the client.
|
134
|
+
# @param logger [Logger] Logger for the client.
|
135
|
+
# @param default_workflow_query_reject_condition [WorkflowQueryRejectCondition, nil] Default rejection condition
|
136
|
+
# for the client.
|
137
|
+
# @param port [Integer, nil] Port to bind on, or +nil+ for random.
|
138
|
+
# @param runtime [Runtime] Runtime for the server and client.
|
139
|
+
# @param test_server_existing_path [String, nil] Existing CLI path to use instead of downloading and caching to
|
140
|
+
# tmp.
|
141
|
+
# @param test_server_download_version [String] Version of test server to download and cache.
|
142
|
+
# @param test_server_download_dest_dir [String, nil] Where to download. Defaults to tmp.
|
143
|
+
# @param test_server_extra_args [Array<String>] Any extra arguments for the test server.
|
144
|
+
# @param test_server_download_ttl [Float, nil] How long the automatic download should be cached for. If nil,
|
145
|
+
# cached indefinitely.
|
146
|
+
#
|
147
|
+
# @yield [environment] If a block is given, it is called with the environment and upon complete the environment is
|
148
|
+
# shutdown.
|
149
|
+
# @yieldparam environment [WorkflowEnvironment] Environment that is shut down upon block completion.
|
150
|
+
#
|
151
|
+
# @return [WorkflowEnvironment, Object] Started local server environment with client if there was no block given,
|
152
|
+
# or block result if block was given.
|
153
|
+
def self.start_time_skipping(
|
154
|
+
data_converter: Converters::DataConverter.default,
|
155
|
+
interceptors: [],
|
156
|
+
logger: Logger.new($stdout, level: Logger::WARN),
|
157
|
+
default_workflow_query_reject_condition: nil,
|
158
|
+
port: nil,
|
159
|
+
runtime: Runtime.default,
|
160
|
+
test_server_existing_path: nil,
|
161
|
+
test_server_download_version: 'default',
|
162
|
+
test_server_download_dest_dir: nil,
|
163
|
+
test_server_extra_args: [],
|
164
|
+
test_server_download_ttl: nil,
|
165
|
+
&
|
166
|
+
)
|
167
|
+
server_options = Internal::Bridge::Testing::EphemeralServer::StartTestServerOptions.new(
|
168
|
+
existing_path: test_server_existing_path,
|
169
|
+
sdk_name: 'sdk-ruby',
|
170
|
+
sdk_version: VERSION,
|
171
|
+
download_version: test_server_download_version,
|
172
|
+
download_dest_dir: test_server_download_dest_dir,
|
173
|
+
port:,
|
174
|
+
extra_args: test_server_extra_args,
|
175
|
+
download_ttl: test_server_download_ttl
|
176
|
+
)
|
177
|
+
_with_core_server(
|
178
|
+
core_server: Internal::Bridge::Testing::EphemeralServer.start_test_server(
|
179
|
+
runtime._core_runtime, server_options
|
180
|
+
),
|
181
|
+
namespace: 'default',
|
182
|
+
data_converter:,
|
183
|
+
interceptors:,
|
184
|
+
logger:,
|
185
|
+
default_workflow_query_reject_condition:,
|
186
|
+
runtime:,
|
187
|
+
supports_time_skipping: true,
|
188
|
+
& # steep:ignore
|
189
|
+
)
|
190
|
+
end
|
191
|
+
|
192
|
+
# @!visibility private
|
193
|
+
def self._with_core_server(
|
194
|
+
core_server:,
|
195
|
+
namespace:,
|
196
|
+
data_converter:,
|
197
|
+
interceptors:,
|
198
|
+
logger:,
|
199
|
+
default_workflow_query_reject_condition:,
|
200
|
+
runtime:,
|
201
|
+
supports_time_skipping:
|
202
|
+
)
|
84
203
|
# Try to connect, shutdown if we can't
|
85
204
|
begin
|
86
205
|
client = Client.connect(
|
@@ -92,8 +211,8 @@ module Temporalio
|
|
92
211
|
default_workflow_query_reject_condition:,
|
93
212
|
runtime:
|
94
213
|
)
|
95
|
-
server = Ephemeral.new(client, core_server)
|
96
|
-
rescue
|
214
|
+
server = Ephemeral.new(client, core_server, supports_time_skipping:)
|
215
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
97
216
|
core_server.shutdown
|
98
217
|
raise
|
99
218
|
end
|
@@ -120,18 +239,168 @@ module Temporalio
|
|
120
239
|
# Do nothing by default
|
121
240
|
end
|
122
241
|
|
242
|
+
# @return [Boolean] Whether this environment supports time skipping.
|
243
|
+
def supports_time_skipping?
|
244
|
+
false
|
245
|
+
end
|
246
|
+
|
247
|
+
# Advanced time.
|
248
|
+
#
|
249
|
+
# If this server supports time skipping, this will immediately advance time and return. If it does not, this is
|
250
|
+
# a standard {::sleep}.
|
251
|
+
#
|
252
|
+
# @param duration [Float] Duration seconds.
|
253
|
+
def sleep(duration)
|
254
|
+
Kernel.sleep(duration)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Current time of the environment.
|
258
|
+
#
|
259
|
+
# If this server supports time skipping, this will be the current time as known to the environment. If it does
|
260
|
+
# not, this is a standard {::Time.now}.
|
261
|
+
#
|
262
|
+
# @return [Time] Current time.
|
263
|
+
def current_time
|
264
|
+
Time.now
|
265
|
+
end
|
266
|
+
|
267
|
+
# Run a block with automatic time skipping disabled. This just runs the block for environments that don't support
|
268
|
+
# time skipping.
|
269
|
+
#
|
270
|
+
# @yield Block to run.
|
271
|
+
# @return [Object] Result of the block.
|
272
|
+
def auto_time_skipping_disabled(&)
|
273
|
+
raise 'Block required' unless block_given?
|
274
|
+
|
275
|
+
yield
|
276
|
+
end
|
277
|
+
|
123
278
|
# @!visibility private
|
124
279
|
class Ephemeral < WorkflowEnvironment
|
125
|
-
def initialize(client, core_server)
|
280
|
+
def initialize(client, core_server, supports_time_skipping:)
|
281
|
+
# Add our interceptor at the end of the existing interceptors that skips time
|
282
|
+
client_options = client.options.with(
|
283
|
+
interceptors: client.options.interceptors + [TimeSkippingClientInterceptor.new(self)]
|
284
|
+
)
|
285
|
+
client = Client.new(**client_options.to_h) # steep:ignore
|
126
286
|
super(client)
|
287
|
+
|
288
|
+
@auto_time_skipping = true
|
127
289
|
@core_server = core_server
|
290
|
+
@test_service = Client::Connection::TestService.new(client.connection) if supports_time_skipping
|
128
291
|
end
|
129
292
|
|
130
293
|
# @!visibility private
|
131
294
|
def shutdown
|
132
295
|
@core_server.shutdown
|
133
296
|
end
|
297
|
+
|
298
|
+
# @!visibility private
|
299
|
+
def supports_time_skipping?
|
300
|
+
!@test_service.nil?
|
301
|
+
end
|
302
|
+
|
303
|
+
# @!visibility private
|
304
|
+
def sleep(duration)
|
305
|
+
return super unless supports_time_skipping?
|
306
|
+
|
307
|
+
@test_service.unlock_time_skipping_with_sleep(
|
308
|
+
Api::TestService::V1::SleepRequest.new(duration: Internal::ProtoUtils.seconds_to_duration(duration))
|
309
|
+
)
|
310
|
+
end
|
311
|
+
|
312
|
+
# @!visibility private
|
313
|
+
def current_time
|
314
|
+
return super unless supports_time_skipping?
|
315
|
+
|
316
|
+
resp = @test_service.get_current_time(Google::Protobuf::Empty.new)
|
317
|
+
Internal::ProtoUtils.timestamp_to_time(resp.time) or raise 'Time missing'
|
318
|
+
end
|
319
|
+
|
320
|
+
# @!visibility private
|
321
|
+
def auto_time_skipping_disabled(&)
|
322
|
+
raise 'Block required' unless block_given?
|
323
|
+
return super unless supports_time_skipping?
|
324
|
+
|
325
|
+
already_disabled = @auto_time_skipping
|
326
|
+
@auto_time_skipping = false
|
327
|
+
begin
|
328
|
+
yield
|
329
|
+
ensure
|
330
|
+
@auto_time_skipping = true unless already_disabled
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# @!visibility private
|
335
|
+
def time_skipping_unlocked(&)
|
336
|
+
# If disabled or unsupported, no locking/unlocking, just run and return
|
337
|
+
return yield if !supports_time_skipping? || !@auto_time_skipping
|
338
|
+
|
339
|
+
# Unlock to start time skipping, lock again to stop it
|
340
|
+
@test_service.unlock_time_skipping(Api::TestService::V1::UnlockTimeSkippingRequest.new)
|
341
|
+
user_code_success = false
|
342
|
+
begin
|
343
|
+
result = yield
|
344
|
+
user_code_success = true
|
345
|
+
result
|
346
|
+
ensure
|
347
|
+
# Lock it back
|
348
|
+
begin
|
349
|
+
@test_service.lock_time_skipping(Api::TestService::V1::LockTimeSkippingRequest.new)
|
350
|
+
rescue StandardError => e
|
351
|
+
# Re-raise if user code succeeded, otherwise swallow
|
352
|
+
raise if user_code_success
|
353
|
+
|
354
|
+
client.options.logger.error('Failed locking time skipping after error')
|
355
|
+
client.options.logger.error(e)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
134
359
|
end
|
360
|
+
|
361
|
+
private_constant :Ephemeral
|
362
|
+
|
363
|
+
# @!visibility private
|
364
|
+
class TimeSkippingClientInterceptor
|
365
|
+
include Client::Interceptor
|
366
|
+
|
367
|
+
def initialize(env)
|
368
|
+
@env = env
|
369
|
+
end
|
370
|
+
|
371
|
+
# @!visibility private
|
372
|
+
def intercept_client(next_interceptor)
|
373
|
+
Outbound.new(next_interceptor, @env)
|
374
|
+
end
|
375
|
+
|
376
|
+
# @!visibility private
|
377
|
+
class Outbound < Client::Interceptor::Outbound
|
378
|
+
def initialize(next_interceptor, env)
|
379
|
+
super(next_interceptor)
|
380
|
+
@env = env
|
381
|
+
end
|
382
|
+
|
383
|
+
# @!visibility private
|
384
|
+
def start_workflow(input)
|
385
|
+
TimeSkippingWorkflowHandle.new(super, @env)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
# @!visibility private
|
390
|
+
class TimeSkippingWorkflowHandle < SimpleDelegator
|
391
|
+
def initialize(handle, env)
|
392
|
+
super(handle) # steep:ignore
|
393
|
+
@env = env
|
394
|
+
end
|
395
|
+
|
396
|
+
# @!visibility private
|
397
|
+
def result(follow_runs: true, rpc_options: nil)
|
398
|
+
@env.time_skipping_unlocked { super(follow_runs:, rpc_options:) }
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
private_constant :TimeSkippingClientInterceptor
|
135
404
|
end
|
136
405
|
end
|
137
406
|
end
|
data/lib/temporalio/version.rb
CHANGED