temporalio 0.3.0 → 0.5.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 +1 -1
- data/Cargo.lock +1019 -669
- data/Cargo.toml +5 -5
- data/Gemfile +4 -0
- data/README.md +281 -44
- data/Rakefile +1 -1
- data/ext/Cargo.toml +4 -3
- data/lib/temporalio/activity/cancellation_details.rb +58 -0
- data/lib/temporalio/activity/context.rb +23 -1
- data/lib/temporalio/activity/definition.rb +63 -8
- data/lib/temporalio/activity/info.rb +28 -4
- data/lib/temporalio/activity.rb +2 -0
- data/lib/temporalio/api/activity/v1/message.rb +1 -1
- data/lib/temporalio/api/batch/v1/message.rb +9 -2
- data/lib/temporalio/api/cloud/account/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +11 -2
- data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +2 -2
- data/lib/temporalio/api/cloud/identity/v1/message.rb +7 -2
- data/lib/temporalio/api/cloud/namespace/v1/message.rb +6 -2
- data/lib/temporalio/api/cloud/nexus/v1/message.rb +3 -2
- data/lib/temporalio/api/cloud/operation/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/region/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/resource/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/sink/v1/message.rb +1 -1
- data/lib/temporalio/api/cloud/usage/v1/message.rb +1 -1
- data/lib/temporalio/api/command/v1/message.rb +2 -2
- data/lib/temporalio/api/common/v1/grpc_status.rb +1 -1
- data/lib/temporalio/api/common/v1/message.rb +4 -2
- data/lib/temporalio/api/deployment/v1/message.rb +39 -0
- data/lib/temporalio/api/enums/v1/batch_operation.rb +2 -2
- data/lib/temporalio/api/enums/v1/command_type.rb +1 -1
- data/lib/temporalio/api/enums/v1/common.rb +5 -2
- data/lib/temporalio/api/enums/v1/deployment.rb +24 -0
- data/lib/temporalio/api/enums/v1/event_type.rb +2 -2
- data/lib/temporalio/api/enums/v1/failed_cause.rb +2 -2
- data/lib/temporalio/api/enums/v1/namespace.rb +1 -1
- data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
- data/lib/temporalio/api/enums/v1/query.rb +1 -1
- data/lib/temporalio/api/enums/v1/reset.rb +2 -2
- data/lib/temporalio/api/enums/v1/schedule.rb +1 -1
- data/lib/temporalio/api/enums/v1/task_queue.rb +1 -1
- data/lib/temporalio/api/enums/v1/update.rb +1 -1
- data/lib/temporalio/api/enums/v1/workflow.rb +3 -2
- data/lib/temporalio/api/errordetails/v1/message.rb +4 -2
- data/lib/temporalio/api/export/v1/message.rb +1 -1
- data/lib/temporalio/api/failure/v1/message.rb +5 -2
- data/lib/temporalio/api/filter/v1/message.rb +1 -1
- data/lib/temporalio/api/history/v1/message.rb +6 -2
- data/lib/temporalio/api/namespace/v1/message.rb +1 -1
- data/lib/temporalio/api/nexus/v1/message.rb +3 -2
- data/lib/temporalio/api/operatorservice/v1/request_response.rb +1 -1
- data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
- data/lib/temporalio/api/payload_visitor.rb +162 -7
- data/lib/temporalio/api/protocol/v1/message.rb +1 -1
- data/lib/temporalio/api/query/v1/message.rb +3 -2
- data/lib/temporalio/api/replication/v1/message.rb +1 -1
- data/lib/temporalio/api/rules/v1/message.rb +27 -0
- data/lib/temporalio/api/schedule/v1/message.rb +2 -2
- data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +1 -1
- data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +1 -1
- data/lib/temporalio/api/sdk/v1/user_metadata.rb +1 -1
- data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +1 -1
- data/lib/temporalio/api/taskqueue/v1/message.rb +5 -2
- data/lib/temporalio/api/testservice/v1/request_response.rb +1 -1
- data/lib/temporalio/api/testservice/v1/service.rb +1 -1
- data/lib/temporalio/api/update/v1/message.rb +1 -1
- data/lib/temporalio/api/version/v1/message.rb +1 -1
- data/lib/temporalio/api/worker/v1/message.rb +30 -0
- data/lib/temporalio/api/workflow/v1/message.rb +22 -2
- data/lib/temporalio/api/workflowservice/v1/request_response.rb +58 -12
- data/lib/temporalio/api/workflowservice/v1/service.rb +2 -2
- data/lib/temporalio/api.rb +1 -0
- data/lib/temporalio/client/async_activity_handle.rb +12 -4
- data/lib/temporalio/client/connection/cloud_service.rb +60 -0
- data/lib/temporalio/client/connection/workflow_service.rb +343 -28
- data/lib/temporalio/client/interceptor.rb +64 -7
- data/lib/temporalio/client/schedule.rb +35 -3
- data/lib/temporalio/client/with_start_workflow_operation.rb +123 -0
- data/lib/temporalio/client/workflow_execution.rb +19 -0
- data/lib/temporalio/client/workflow_handle.rb +47 -7
- data/lib/temporalio/client/workflow_update_handle.rb +9 -3
- data/lib/temporalio/client.rb +231 -4
- data/lib/temporalio/common_enums.rb +14 -0
- data/lib/temporalio/contrib/open_telemetry.rb +474 -0
- data/lib/temporalio/converters/data_converter.rb +18 -8
- data/lib/temporalio/converters/failure_converter.rb +6 -3
- data/lib/temporalio/converters/payload_converter/binary_null.rb +2 -2
- data/lib/temporalio/converters/payload_converter/binary_plain.rb +2 -2
- data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +2 -2
- data/lib/temporalio/converters/payload_converter/composite.rb +6 -4
- data/lib/temporalio/converters/payload_converter/encoding.rb +4 -2
- data/lib/temporalio/converters/payload_converter/json_plain.rb +2 -2
- data/lib/temporalio/converters/payload_converter/json_protobuf.rb +2 -2
- data/lib/temporalio/converters/payload_converter.rb +16 -6
- data/lib/temporalio/error/failure.rb +19 -1
- data/lib/temporalio/error.rb +2 -1
- data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +1 -1
- data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +3 -2
- data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +1 -1
- data/lib/temporalio/internal/bridge/api/common/common.rb +3 -2
- data/lib/temporalio/internal/bridge/api/core_interface.rb +1 -1
- data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +1 -1
- data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +3 -2
- data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +2 -2
- data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +3 -2
- data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +3 -2
- data/lib/temporalio/internal/bridge/runtime.rb +3 -0
- data/lib/temporalio/internal/bridge/testing.rb +3 -0
- data/lib/temporalio/internal/bridge/worker.rb +28 -4
- data/lib/temporalio/internal/bridge.rb +1 -1
- data/lib/temporalio/internal/client/implementation.rb +281 -51
- data/lib/temporalio/internal/proto_utils.rb +38 -6
- data/lib/temporalio/internal/worker/activity_worker.rb +112 -27
- data/lib/temporalio/internal/worker/multi_runner.rb +2 -2
- data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +8 -6
- data/lib/temporalio/internal/worker/workflow_instance/context.rb +100 -5
- data/lib/temporalio/internal/worker/workflow_instance/details.rb +7 -2
- data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +2 -2
- data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +64 -18
- data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +39 -40
- data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +22 -2
- data/lib/temporalio/internal/worker/workflow_instance.rb +134 -55
- data/lib/temporalio/internal/worker/workflow_worker.rb +74 -21
- data/lib/temporalio/priority.rb +59 -0
- data/lib/temporalio/runtime/metric_buffer.rb +94 -0
- data/lib/temporalio/runtime.rb +48 -10
- data/lib/temporalio/search_attributes.rb +13 -0
- data/lib/temporalio/testing/activity_environment.rb +59 -16
- data/lib/temporalio/testing/workflow_environment.rb +29 -6
- data/lib/temporalio/version.rb +1 -1
- data/lib/temporalio/versioning_override.rb +56 -0
- data/lib/temporalio/worker/deployment_options.rb +45 -0
- data/lib/temporalio/worker/illegal_workflow_call_validator.rb +64 -0
- data/lib/temporalio/worker/interceptor.rb +16 -1
- data/lib/temporalio/worker/poller_behavior.rb +61 -0
- data/lib/temporalio/worker/thread_pool.rb +6 -6
- data/lib/temporalio/worker/tuner.rb +38 -0
- data/lib/temporalio/worker/workflow_executor/thread_pool.rb +14 -8
- data/lib/temporalio/worker/workflow_executor.rb +1 -1
- data/lib/temporalio/worker/workflow_replayer.rb +349 -0
- data/lib/temporalio/worker.rb +117 -75
- data/lib/temporalio/worker_deployment_version.rb +67 -0
- data/lib/temporalio/workflow/child_workflow_handle.rb +10 -2
- data/lib/temporalio/workflow/definition.rb +217 -35
- data/lib/temporalio/workflow/external_workflow_handle.rb +3 -1
- data/lib/temporalio/workflow/future.rb +2 -2
- data/lib/temporalio/workflow/info.rb +26 -1
- data/lib/temporalio/workflow.rb +119 -15
- data/lib/temporalio/workflow_history.rb +26 -1
- data/lib/temporalio.rb +1 -0
- data/temporalio.gemspec +3 -1
- metadata +36 -6
data/Cargo.toml
CHANGED
@@ -13,13 +13,13 @@ license-file = "LICENSE"
|
|
13
13
|
|
14
14
|
[workspace.dependencies]
|
15
15
|
derive_builder = "0.20"
|
16
|
-
derive_more = { version = "
|
16
|
+
derive_more = { version = "2.0", features = ["constructor", "display", "from", "into", "debug", "try_into"] }
|
17
17
|
thiserror = "2"
|
18
|
-
tonic = "0.
|
19
|
-
tonic-build = "0.
|
20
|
-
opentelemetry = { version = "0.
|
18
|
+
tonic = "0.13"
|
19
|
+
tonic-build = "0.13"
|
20
|
+
opentelemetry = { version = "0.30", features = ["metrics"] }
|
21
21
|
prost = "0.13"
|
22
22
|
prost-types = "0.13"
|
23
23
|
|
24
24
|
[workspace.lints.rust]
|
25
|
-
unreachable_pub = "warn"
|
25
|
+
unreachable_pub = "warn"
|
data/Gemfile
CHANGED
@@ -12,6 +12,10 @@ group :development do
|
|
12
12
|
gem 'grpc', '~> 1.69'
|
13
13
|
gem 'grpc-tools', '~> 1.69'
|
14
14
|
gem 'minitest'
|
15
|
+
# We are intentionally not pinning OTel versions here so that CI tests the latest. This also means that the OTel
|
16
|
+
# contrib library also does not require specific versions, we are relying on the compatibility rigor of OTel.
|
17
|
+
gem 'opentelemetry-api'
|
18
|
+
gem 'opentelemetry-sdk'
|
15
19
|
gem 'rake'
|
16
20
|
gem 'rake-compiler'
|
17
21
|
gem 'rbs', '~> 3.5.3'
|
data/README.md
CHANGED
@@ -11,15 +11,20 @@ execute asynchronous, long-running business logic in a scalable and resilient wa
|
|
11
11
|
|
12
12
|
Also see:
|
13
13
|
|
14
|
+
* [Ruby SDK](https://github.com/temporalio/sdk-ruby)
|
14
15
|
* [Ruby Samples](https://github.com/temporalio/samples-ruby)
|
15
|
-
* [API Documentation](https://
|
16
|
+
* [API Documentation](https://ruby.temporal.io)
|
17
|
+
|
18
|
+
**NOTE: This README is for the current branch and not necessarily what's released on RubyGems.**
|
16
19
|
|
17
20
|
⚠️ UNDER ACTIVE DEVELOPMENT
|
18
21
|
|
19
22
|
This SDK is under active development and has not released a stable version yet. APIs may change in incompatible ways
|
20
23
|
until the SDK is marked stable.
|
21
24
|
|
22
|
-
|
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`.
|
23
28
|
|
24
29
|
---
|
25
30
|
|
@@ -37,7 +42,8 @@ until the SDK is marked stable.
|
|
37
42
|
- [Cloud Client Using mTLS](#cloud-client-using-mtls)
|
38
43
|
- [Cloud Client Using API Key](#cloud-client-using-api-key)
|
39
44
|
- [Data Conversion](#data-conversion)
|
40
|
-
- [
|
45
|
+
- [ActiveModel](#activemodel)
|
46
|
+
- [Converter Hints](#converter-hints)
|
41
47
|
- [Workers](#workers)
|
42
48
|
- [Workflows](#workflows)
|
43
49
|
- [Workflow Definition](#workflow-definition)
|
@@ -62,6 +68,15 @@ until the SDK is marked stable.
|
|
62
68
|
- [Activity Worker Shutdown](#activity-worker-shutdown)
|
63
69
|
- [Activity Concurrency and Executors](#activity-concurrency-and-executors)
|
64
70
|
- [Activity Testing](#activity-testing)
|
71
|
+
- [Telemetry](#telemetry)
|
72
|
+
- [Metrics](#metrics)
|
73
|
+
- [OpenTelemetry Tracing](#opentelemetry-tracing)
|
74
|
+
- [OpenTelemetry Tracing in Workflows](#opentelemetry-tracing-in-workflows)
|
75
|
+
- [Rails](#rails)
|
76
|
+
- [ActiveRecord](#activerecord)
|
77
|
+
- [Lazy/Eager Loading](#lazyeager-loading)
|
78
|
+
- [Forking](#forking)
|
79
|
+
- [Ractors](#ractors)
|
65
80
|
- [Platform Support](#platform-support)
|
66
81
|
- [Development](#development)
|
67
82
|
- [Build](#build)
|
@@ -92,8 +107,8 @@ gem install temporalio
|
|
92
107
|
|
93
108
|
**NOTE**: Only macOS ARM/x64 and Linux ARM/x64 are supported, and the platform-specific gem chosen is based on when the
|
94
109
|
gem/bundle install is performed. A source gem is published but cannot be used directly and will fail to build if tried.
|
95
|
-
MinGW-based Windows
|
96
|
-
information.
|
110
|
+
MinGW-based Windows is not currently supported. There are caveats with the Google Protobuf dependency on musl-based
|
111
|
+
Linux. See the [Platform Support](#platform-support) section for more information.
|
97
112
|
|
98
113
|
**NOTE**: Due to [an issue](https://github.com/temporalio/sdk-ruby/issues/162), fibers (and `async` gem) are only
|
99
114
|
supported on Ruby versions 3.3 and newer.
|
@@ -285,57 +300,62 @@ will be tried in order until one accepts (default falls through to the JSON one)
|
|
285
300
|
`encoding` metadata value which is used to know which converter to use on deserialize. Custom encoding converters can be
|
286
301
|
created, or even the entire payload converter can be replaced with a different implementation.
|
287
302
|
|
288
|
-
|
303
|
+
**NOTE:** For ActiveRecord, or other general/ORM models that are used for a different purpose, it is not recommended to
|
304
|
+
try to reuse them as Temporal models. Eventually model purposes diverge and models for a Temporal workflows/activities
|
305
|
+
should be specific to their use for clarity and compatibility reasons. Also many Ruby ORMs do many lazy things and
|
306
|
+
therefore provide unclear serialization semantics. Instead, consider having models specific for workflows/activities and
|
307
|
+
translate to/from existing models as needed. See the next section on how to do this with ActiveModel objects.
|
289
308
|
|
290
|
-
|
291
|
-
|
309
|
+
##### ActiveModel
|
310
|
+
|
311
|
+
By default, ActiveModel objects do not natively support the `JSON` module. A mixin can be created to add this support
|
312
|
+
for ActiveRecord, for example:
|
292
313
|
|
293
314
|
```ruby
|
294
|
-
module
|
315
|
+
module ActiveModelJSONSupport
|
295
316
|
extend ActiveSupport::Concern
|
296
317
|
include ActiveModel::Serializers::JSON
|
297
318
|
|
298
319
|
included do
|
320
|
+
def as_json(*)
|
321
|
+
super.merge(::JSON.create_id => self.class.name)
|
322
|
+
end
|
323
|
+
|
299
324
|
def to_json(*args)
|
300
|
-
|
301
|
-
hash[::JSON.create_id] = self.class.name
|
302
|
-
hash.to_json(*args)
|
325
|
+
as_json.to_json(*args)
|
303
326
|
end
|
304
327
|
|
305
328
|
def self.json_create(object)
|
329
|
+
object = object.dup
|
306
330
|
object.delete(::JSON.create_id)
|
307
|
-
|
308
|
-
ret.attributes = object
|
309
|
-
ret
|
331
|
+
new(**object.symbolize_keys)
|
310
332
|
end
|
311
333
|
end
|
312
334
|
end
|
313
335
|
```
|
314
336
|
|
315
|
-
|
337
|
+
Now if `include ActiveModelJSONSupport` is present on any ActiveModel class, on serialization `to_json` will be used
|
338
|
+
which will use `as_json` which calls the super `as_json` but also includes the fully qualified class name as the JSON
|
339
|
+
`create_id` key. On deserialization, Ruby JSON then uses this key to know what class to call `json_create` on.
|
316
340
|
|
317
|
-
|
318
|
-
module ActiveModelJSONSupport
|
319
|
-
extend ActiveSupport::Concern
|
320
|
-
include ActiveRecordJSONSupport
|
341
|
+
##### Converter Hints
|
321
342
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
end
|
327
|
-
end
|
343
|
+
In most places where objects are converted to payloads or vice versa, a "hint" can be provided to tell the converter
|
344
|
+
something else about the object/payload to assist conversion. The default converters ignore these hints, but custom
|
345
|
+
converters can be written to take advantage of them. For example, hints may be used to provide a custom converter the
|
346
|
+
Ruby type to deserialize a payload into.
|
328
347
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
348
|
+
These hints manifest themselves various ways throughout the API. The most obvious way is when making definitions. An
|
349
|
+
activity can define `activity_arg_hint` (which accepts multiple) and/or `activity_result_hint` for activity-level hints.
|
350
|
+
Similarly, a workflow can define `workflow_arg_hint` and/or `workflow_result_hint` for workflow-level hints.
|
351
|
+
`workflow_signal`, `workflow_query`, and `workflow_update` all similarly accept `arg_hints` and `result_hint` (except
|
352
|
+
signal of course). These definition-level hints are passed to converters both from the caller side and the
|
353
|
+
implementation side.
|
335
354
|
|
336
|
-
|
337
|
-
|
338
|
-
|
355
|
+
There are some advanced payload uses in the SDK that do not currently have a way to set hints. These include
|
356
|
+
workflow/schedule memo, workflow get/upsert memo, and application error details. In some cases, users can use
|
357
|
+
`Temporalio::Converters::RawValue` and then manually convert with hints. For others, hints can be added as needed,
|
358
|
+
please open an issue or otherwise contact Temporal.
|
339
359
|
|
340
360
|
### Workers
|
341
361
|
|
@@ -408,7 +428,7 @@ class GreetingWorkflow < Temporalio::Workflow::Definition
|
|
408
428
|
# on wait_condition calls, so Cancellation object doesn't need to be passed
|
409
429
|
# explicitly.
|
410
430
|
Temporalio::Workflow.wait_condition { @greeting_params_update || @complete }
|
411
|
-
|
431
|
+
|
412
432
|
# If there was an update, exchange and rerun. If it's _only_ a complete, finish
|
413
433
|
# workflow with the greeting.
|
414
434
|
if @greeting_params_update
|
@@ -480,7 +500,8 @@ definition/behavior of the method:
|
|
480
500
|
side effects, meaning they should never mutate state or try to wait on anything.
|
481
501
|
|
482
502
|
Workflows can be inherited, but subclass workflow-level decorators override superclass ones, and the same method can't
|
483
|
-
be decorated with different handler types/names in the hierarchy.
|
503
|
+
be decorated with different handler types/names in the hierarchy. Workflow handlers (execute or any marked method)
|
504
|
+
cannot accept keyword arguments.
|
484
505
|
|
485
506
|
#### Running Workflows
|
486
507
|
|
@@ -608,7 +629,7 @@ Or, say, to wait on the first of 5 activities or a timeout to complete:
|
|
608
629
|
# Start 5 activities
|
609
630
|
act_futs = 5.times.map do |i|
|
610
631
|
Temporalio::Workflow::Future.new do
|
611
|
-
Temporalio::Workflow.execute_activity(MyActivity, "my-arg-#{i}" schedule_to_close_timeout: 300)
|
632
|
+
Temporalio::Workflow.execute_activity(MyActivity, "my-arg-#{i}", schedule_to_close_timeout: 300)
|
612
633
|
end
|
613
634
|
end
|
614
635
|
# Start a timer
|
@@ -853,7 +874,48 @@ the mock activity class to make it appear as the real name.
|
|
853
874
|
|
854
875
|
#### Workflow Replay
|
855
876
|
|
856
|
-
|
877
|
+
Given a workflow's history, it can be replayed locally to check for things like non-determinism errors. For example,
|
878
|
+
assuming the `history_json` parameter below is given a JSON string of history exported from the CLI or web UI, the
|
879
|
+
following function will replay it:
|
880
|
+
|
881
|
+
```ruby
|
882
|
+
def replay_from_json(history_json)
|
883
|
+
replayer = Temporalio::Worker::WorkflowReplayer.new(workflows: [MyWorkflow])
|
884
|
+
replayer.replay_workflow(Temporalio::WorkflowHistory.from_history_json(history_json))
|
885
|
+
end
|
886
|
+
```
|
887
|
+
|
888
|
+
If there is a non-determinism, this will raise an exception by default.
|
889
|
+
|
890
|
+
Workflow history can be loaded from more than just JSON. It can be fetched individually from a workflow handle, or even
|
891
|
+
in a list. For example, the following code will check that all workflow histories for a certain workflow type (i.e.
|
892
|
+
workflow class) are safe with the current workflow code.
|
893
|
+
|
894
|
+
```ruby
|
895
|
+
def check_past_histories(client)
|
896
|
+
replayer = Temporalio::Worker::WorkflowReplayer.new(workflows: [MyWorkflow])
|
897
|
+
results = replayer.replay_workflows(client.list_workflows("WorkflowType = 'MyWorkflow'").map do |desc|
|
898
|
+
client.workflow_handle(desc.id, run_id: desc.run_id).fetch_history
|
899
|
+
end)
|
900
|
+
results.each { |res| raise res.replay_failure if res.replay_failure }
|
901
|
+
end
|
902
|
+
```
|
903
|
+
|
904
|
+
But this only raises at the end because by default `replay_workflows` does not raise on failure like `replay_workflow`
|
905
|
+
does. The `raise_on_replay_failure: true` parameter could be set, or the replay worker can be used to process each one
|
906
|
+
like so:
|
907
|
+
|
908
|
+
```ruby
|
909
|
+
def check_past_histories(client)
|
910
|
+
Temporalio::Worker::WorkflowReplayer.new(workflows: [MyWorkflow]) do |worker|
|
911
|
+
client.list_workflows("WorkflowType = 'MyWorkflow'").each do |desc|
|
912
|
+
worker.replay_workflow(client.workflow_handle(desc.id, run_id: desc.run_id).fetch_history)
|
913
|
+
end
|
914
|
+
end
|
915
|
+
end
|
916
|
+
```
|
917
|
+
|
918
|
+
See the `WorkflowReplayer` API documentation for more details.
|
857
919
|
|
858
920
|
### Activities
|
859
921
|
|
@@ -904,6 +966,7 @@ Some notes about activity definition:
|
|
904
966
|
* `workflow_raw_args` can be used to have activity arguments delivered to `execute` as
|
905
967
|
`Temporalio::Converters::RawValue`s. These are wrappers for the raw payloads that have not been converted to types
|
906
968
|
(but they have been decoded by the codec if present). They can be converted with `payload_converter` on the context.
|
969
|
+
* Activities cannot accept keyword arguments.
|
907
970
|
|
908
971
|
#### Activity Context
|
909
972
|
|
@@ -987,6 +1050,169 @@ it will raise the error raised in the activity.
|
|
987
1050
|
The constructor of the environment has multiple keyword arguments that can be set to affect the activity context for the
|
988
1051
|
activity.
|
989
1052
|
|
1053
|
+
### Telemetry
|
1054
|
+
|
1055
|
+
#### Metrics
|
1056
|
+
|
1057
|
+
Metrics can be configured on a `Temporalio::Runtime`. Only one runtime is expected to be created for the entire
|
1058
|
+
application and it should be created before any clients are created. For example, this configures Prometheus to export
|
1059
|
+
metrics at `http://127.0.0.1:9000/metrics`:
|
1060
|
+
|
1061
|
+
```ruby
|
1062
|
+
require 'temporalio/runtime'
|
1063
|
+
|
1064
|
+
Temporalio::Runtime.default = Temporalio::Runtime.new(
|
1065
|
+
telemetry: Temporalio::Runtime::TelemetryOptions.new(
|
1066
|
+
metrics: Temporalio::Runtime::MetricsOptions.new(
|
1067
|
+
prometheus: Temporalio::Runtime::PrometheusMetricsOptions.new(
|
1068
|
+
bind_address: '127.0.0.1:9000'
|
1069
|
+
)
|
1070
|
+
)
|
1071
|
+
)
|
1072
|
+
)
|
1073
|
+
```
|
1074
|
+
|
1075
|
+
Now every client created will use this runtime. Setting the default will fail if a runtime has already been requested or
|
1076
|
+
a default already set. Technically a runtime can be created without setting the default and be set on each client via
|
1077
|
+
the `runtime` parameter, but this is discouraged because a runtime represents a heavy internal engine not meant to be
|
1078
|
+
created multiple times.
|
1079
|
+
|
1080
|
+
OpenTelemetry metrics can be configured instead by passing `Temporalio::Runtime::OpenTelemetryMetricsOptions` as the
|
1081
|
+
`opentelemetry` parameter to the metrics options. See API documentation for details.
|
1082
|
+
|
1083
|
+
#### OpenTelemetry Tracing
|
1084
|
+
|
1085
|
+
OpenTelemetry tracing for clients, activities, and workflows can be enabled using the
|
1086
|
+
`Temporalio::Contrib::OpenTelemetry::TracingInterceptor`. Specifically, when creating a client, set the interceptor like
|
1087
|
+
so:
|
1088
|
+
|
1089
|
+
```ruby
|
1090
|
+
require 'opentelemetry/api'
|
1091
|
+
require 'opentelemetry/sdk'
|
1092
|
+
require 'temporalio/client'
|
1093
|
+
require 'temporalio/contrib/open_telemetry'
|
1094
|
+
|
1095
|
+
# ... assumes my_otel_tracer_provider is a tracer provider created by the user
|
1096
|
+
my_tracer = my_otel_tracer_provider.tracer('my-otel-tracer')
|
1097
|
+
|
1098
|
+
my_client = Temporalio::Client.connect(
|
1099
|
+
'localhost:7233', 'my-namespace',
|
1100
|
+
interceptors: [Temporalio::Contrib::OpenTelemetry::TracingInterceptor.new(my_tracer)]
|
1101
|
+
)
|
1102
|
+
```
|
1103
|
+
|
1104
|
+
Now many high-level client calls and activities/workflows on workers using this client will have spans created on that
|
1105
|
+
OpenTelemetry tracer.
|
1106
|
+
|
1107
|
+
##### OpenTelemetry Tracing in Workflows
|
1108
|
+
|
1109
|
+
OpenTelemetry works by creating spans as necessary and in some cases serializing them to Temporal headers to be
|
1110
|
+
deserialized by workflows/activities to be set on the context. However, OpenTelemetry requires spans to be finished
|
1111
|
+
where they start, so spans cannot be resumed. This is fine for client calls and activity attempts, but Temporal
|
1112
|
+
workflows are resumable functions that may start on a different machine than they complete. Due to this, spans created
|
1113
|
+
by workflows are immediately closed since there is no way for the span to actually span machines. They are also not
|
1114
|
+
created during replay. The spans still become the proper parents of other spans if they are created.
|
1115
|
+
|
1116
|
+
Custom spans can be created inside of workflows using class methods on the
|
1117
|
+
`Temporalio::Contrib::OpenTelemetry::Workflow` module. For example:
|
1118
|
+
|
1119
|
+
```ruby
|
1120
|
+
class MyWorkflow < Temporalio::Workflow::Definition
|
1121
|
+
def execute
|
1122
|
+
# Sleep for a bit
|
1123
|
+
Temporalio::Workflow.sleep(10)
|
1124
|
+
# Run activity in span
|
1125
|
+
Temporalio::Contrib::OpenTelemetry::Workflow.with_completed_span(
|
1126
|
+
'my-span',
|
1127
|
+
attributes: { 'my-attr' => 'some val' }
|
1128
|
+
) do
|
1129
|
+
# Execute an activity
|
1130
|
+
Temporalio::Workflow.execute_activity(MyActivity, start_to_close_timeout: 10)
|
1131
|
+
end
|
1132
|
+
end
|
1133
|
+
end
|
1134
|
+
```
|
1135
|
+
|
1136
|
+
If this all executes on one worker (because Temporal has a concept of stickiness that caches instances), the span tree
|
1137
|
+
may look like:
|
1138
|
+
|
1139
|
+
```
|
1140
|
+
StartWorkflow:MyWorkflow <-- created by client outbound
|
1141
|
+
RunWorkflow:MyWorkflow <-- created inside workflow on first task
|
1142
|
+
my-span <-- created inside workflow by code
|
1143
|
+
StartActivity:MyActivity <-- created inside workflow when first called
|
1144
|
+
RunActivity:MyActivity <-- created inside activity attempt 1
|
1145
|
+
CompleteWorkflow:MyWorkflow <-- created inside workflow on last task
|
1146
|
+
```
|
1147
|
+
|
1148
|
+
However if, say, the worker crashed during the 10s sleep and the workflow was resumed (i.e. replayed) on another worker,
|
1149
|
+
the span tree may look like:
|
1150
|
+
|
1151
|
+
```
|
1152
|
+
StartWorkflow:MyWorkflow <-- created by client outbound
|
1153
|
+
RunWorkflow:MyWorkflow <-- created by workflow inbound on first task
|
1154
|
+
my-span <-- created inside the workflow
|
1155
|
+
StartActivity:MyActivity <-- created by workflow outbound
|
1156
|
+
RunActivity:MyActivity <-- created by activity attempt 1 inbound
|
1157
|
+
CompleteWorkflow:MyWorkflow <-- created by workflow inbound on last task
|
1158
|
+
```
|
1159
|
+
|
1160
|
+
Notice how the spans are no longer under `RunWorkflow`. This is because spans inside the workflow are not created on
|
1161
|
+
replay, so there is no parent on replay. But there are no orphans because we still have the overarching parent of
|
1162
|
+
`StartWorkflow` that was created by the client and is serialized into Temporal headers so it can always be the parent.
|
1163
|
+
|
1164
|
+
And reminder that `StartWorkflow` and `RunActivity` spans do last the length of their calls (so time to start the
|
1165
|
+
workflow and time to run the activity attempt respectively), but the other spans have no measurable time because they
|
1166
|
+
are created in workflows and closed immediately since long-lived spans cannot work for durable software that may resume
|
1167
|
+
on other machines.
|
1168
|
+
|
1169
|
+
### Rails
|
1170
|
+
|
1171
|
+
Temporal Ruby SDK is a generic Ruby library that can work in any Ruby environment. However, there are some common
|
1172
|
+
conventions for Rails users to be aware of.
|
1173
|
+
|
1174
|
+
See the [rails_app](https://github.com/temporalio/samples-ruby/tree/main/rails_app) sample for an example of using
|
1175
|
+
Temporal from Rails.
|
1176
|
+
|
1177
|
+
#### ActiveRecord
|
1178
|
+
|
1179
|
+
For ActiveRecord, or other general/ORM models that are used for a different purpose, it is not recommended to
|
1180
|
+
try to reuse them as Temporal models. Eventually model purposes diverge and models for a Temporal workflows/activities
|
1181
|
+
should be specific to their use for clarity and compatibility reasons. Also many Ruby ORMs do many lazy things and
|
1182
|
+
therefore provide unclear serialization semantics. Instead, consider having models specific for workflows/activities and
|
1183
|
+
translate to/from existing models as needed. See the [ActiveModel](#activemodel) section on how to do this with
|
1184
|
+
ActiveModel objects.
|
1185
|
+
|
1186
|
+
#### Lazy/Eager Loading
|
1187
|
+
|
1188
|
+
By default, Rails
|
1189
|
+
[eagerly loads](https://guides.rubyonrails.org/v7.2/autoloading_and_reloading_constants.html#eager-loading) all
|
1190
|
+
application code on application start in production, but lazily loads it in non-production environments. Temporal
|
1191
|
+
workflows by default disallow use of IO during the workflow run. With lazy loading enabled in dev/test environments,
|
1192
|
+
when an activity class is referenced in a workflow before it has been explicitly `require`d, it can give an error like:
|
1193
|
+
|
1194
|
+
> Cannot access File path from inside a workflow. If this is known to be safe, the code can be run in a
|
1195
|
+
> Temporalio::Workflow::Unsafe.illegal_call_tracing_disabled block.
|
1196
|
+
|
1197
|
+
This comes from `bootsnap` via `zeitwork` because it is lazily loading a class/module at workflow runtime. It is not
|
1198
|
+
good to lazily load code durnig a workflow run because it can be side effecting. Workflows and the classes they
|
1199
|
+
reference should not be eagerly loaded.
|
1200
|
+
|
1201
|
+
To resolve this, either always eagerly load (e.g. `config.eager_load = true`) or explicitly `require` what is used by a
|
1202
|
+
workflow at the top of the file.
|
1203
|
+
|
1204
|
+
Note, this only affects non-production environments.
|
1205
|
+
|
1206
|
+
### Forking
|
1207
|
+
|
1208
|
+
Objects created with the Temporal Ruby SDK cannot be used across forks. This includes runtimes, clients, and workers. By
|
1209
|
+
default, using `Client.connect` uses `Runtime.default` which is lazily created. If it was already created on the parent,
|
1210
|
+
an exception will occur when trying to reuse it to create clients or workers in a forked child. Similarly any RPC
|
1211
|
+
invocation or worker execution inside of a forked child separate from where the runtime or client or worker were created
|
1212
|
+
will raise an exception.
|
1213
|
+
|
1214
|
+
If forking must be used, make sure Temporal objects are only created _inside_ the fork.
|
1215
|
+
|
990
1216
|
### Ractors
|
991
1217
|
|
992
1218
|
It was an original goal to have workflows actually be Ractors for deterministic state isolation and have the library
|
@@ -1000,21 +1226,27 @@ This SDK is backed by a Ruby C extension written in Rust leveraging the
|
|
1000
1226
|
[Temporal Rust Core](https://github.com/temporalio/sdk-core). Gems are currently published for the following platforms:
|
1001
1227
|
|
1002
1228
|
* `aarch64-linux`
|
1229
|
+
* `aarch64-linux-musl`
|
1003
1230
|
* `x86_64-linux`
|
1231
|
+
* `x86_64-linux-musl`
|
1004
1232
|
* `arm64-darwin`
|
1005
1233
|
* `x86_64-darwin`
|
1006
1234
|
|
1007
|
-
This means Linux and macOS for ARM and x64 have published gems.
|
1008
|
-
`aarch64-linux-musl` so Alpine Linux users may need to build from scratch or use a libc-based distro.
|
1235
|
+
This means Linux and macOS for ARM and x64 have published gems.
|
1009
1236
|
|
1010
1237
|
Due to [an issue](https://github.com/temporalio/sdk-ruby/issues/172) with Windows and multi-threaded Rust, MinGW-based
|
1011
1238
|
Windows (i.e. `x64-mingw-ucrt`) is not supported. But WSL is supported using the normal Linux gem.
|
1012
1239
|
|
1240
|
+
Due to [an issue](https://github.com/protocolbuffers/protobuf/issues/16853) with Google Protobuf, latest Linux versions
|
1241
|
+
of Google Protobuf gems will not work in musl-based environments. Instead use the pure "ruby" platform which will build
|
1242
|
+
the Google Protobuf gem on install (e.g.
|
1243
|
+
`gem 'google-protobuf', force_ruby_platform: RUBY_PLATFORM.include?('linux-musl')` in the `Gemfile`).
|
1244
|
+
|
1013
1245
|
At this time a pure source gem is published for documentation reasons, but it cannot be built and will fail if tried.
|
1014
1246
|
Building from source requires many files across submodules and requires Rust to be installed. See the [Build](#build)
|
1015
1247
|
section for how to build a the repository.
|
1016
1248
|
|
1017
|
-
The SDK works on Ruby 3.
|
1249
|
+
The SDK works on Ruby 3.2+, but due to [an issue](https://github.com/temporalio/sdk-ruby/issues/162), fibers (and
|
1018
1250
|
`async` gem) are only supported on Ruby versions 3.3 and newer.
|
1019
1251
|
|
1020
1252
|
## Development
|
@@ -1023,16 +1255,18 @@ The SDK works on Ruby 3.1+, but due to [an issue](https://github.com/temporalio/
|
|
1023
1255
|
|
1024
1256
|
Prerequisites:
|
1025
1257
|
|
1026
|
-
* [Ruby](https://www.ruby-lang.org/) >= 3.
|
1258
|
+
* [Ruby](https://www.ruby-lang.org/) >= 3.2 (i.e. `ruby` and `bundle` on the `PATH`)
|
1027
1259
|
* [Rust](https://www.rust-lang.org/) latest stable (i.e. `cargo` on the `PATH`)
|
1028
1260
|
* This repository, cloned recursively
|
1029
1261
|
* Change to the `temporalio/` directory
|
1030
1262
|
|
1031
1263
|
First, install dependencies:
|
1032
1264
|
|
1265
|
+
# Optional: Change bundler install path to be local
|
1266
|
+
bundle config --local path $(pwd)/.bundle
|
1033
1267
|
bundle install
|
1034
1268
|
|
1035
|
-
To build shared library for development use:
|
1269
|
+
To build shared library for development use (ensure you have cloned submodules) :
|
1036
1270
|
|
1037
1271
|
bundle exec rake compile
|
1038
1272
|
|
@@ -1064,6 +1298,9 @@ the gem at `lib/temporalio/internal/bridge/3.2/temporalio_bridge.so` and
|
|
1064
1298
|
|
1065
1299
|
### Testing
|
1066
1300
|
|
1301
|
+
Note you can set `TEMPORAL_TEST_CLIENT_TARGET_HOST` and `TEMPORAL_TEST_CLIENT_TARGET_NAMESPACE`
|
1302
|
+
(optional, defaults to 'default') environment variables to use an existing server.
|
1303
|
+
|
1067
1304
|
This project uses `minitest`. To test:
|
1068
1305
|
|
1069
1306
|
bundle exec rake test
|
data/Rakefile
CHANGED
data/ext/Cargo.toml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
[package]
|
2
2
|
name = "temporalio_bridge"
|
3
3
|
version = "0.1.0"
|
4
|
-
edition = "
|
4
|
+
edition = "2024"
|
5
5
|
authors = ["Chad Retz <chad@temporal.io>"]
|
6
6
|
license = "MIT"
|
7
7
|
publish = false
|
@@ -19,8 +19,9 @@ temporal-client = { version = "0.1.0", path = "./sdk-core/client" }
|
|
19
19
|
temporal-sdk-core = { version = "0.1.0", path = "./sdk-core/core", features = ["ephemeral-server"] }
|
20
20
|
temporal-sdk-core-api = { version = "0.1.0", path = "./sdk-core/core-api" }
|
21
21
|
temporal-sdk-core-protos = { version = "0.1.0", path = "./sdk-core/sdk-core-protos" }
|
22
|
-
tokio = "1.
|
22
|
+
tokio = "1.37"
|
23
|
+
tokio-stream = "0.1"
|
23
24
|
tokio-util = "0.7"
|
24
|
-
tonic = "0.
|
25
|
+
tonic = "0.13"
|
25
26
|
tracing = "0.1"
|
26
27
|
url = "2.2"
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'temporalio/error'
|
4
|
+
|
5
|
+
module Temporalio
|
6
|
+
module Activity
|
7
|
+
# Details that are set when an activity is cancelled. This is only valid at the time the cancel was received, the
|
8
|
+
# state may change on the server after it is received.
|
9
|
+
class CancellationDetails
|
10
|
+
def initialize(
|
11
|
+
gone_from_server: false,
|
12
|
+
cancel_requested: true,
|
13
|
+
timed_out: false,
|
14
|
+
worker_shutdown: false,
|
15
|
+
paused: false,
|
16
|
+
reset: false
|
17
|
+
)
|
18
|
+
@gone_from_server = gone_from_server
|
19
|
+
@cancel_requested = cancel_requested
|
20
|
+
@timed_out = timed_out
|
21
|
+
@worker_shutdown = worker_shutdown
|
22
|
+
@paused = paused
|
23
|
+
@reset = reset
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Boolean] Whether the activity no longer exists on the server (may already be completed or its workflow
|
27
|
+
# may be completed).
|
28
|
+
def gone_from_server?
|
29
|
+
@gone_from_server
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Boolean] Whether the activity was explicitly cancelled.
|
33
|
+
def cancel_requested?
|
34
|
+
@cancel_requested
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Boolean] Whether the activity timeout caused activity to be marked cancelled.
|
38
|
+
def timed_out?
|
39
|
+
@timed_out
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Boolean] Whether the worker the activity is running on is shutting down.
|
43
|
+
def worker_shutdown?
|
44
|
+
@worker_shutdown
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Boolean] Whether the activity was explicitly paused.
|
48
|
+
def paused?
|
49
|
+
@paused
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Boolean] Whether the activity was explicitly reset.
|
53
|
+
def reset?
|
54
|
+
@reset
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -48,6 +48,12 @@ module Temporalio
|
|
48
48
|
raise NotImplementedError
|
49
49
|
end
|
50
50
|
|
51
|
+
# @return [Object, nil] Activity class instance. This should always be present except for advanced cases where the
|
52
|
+
# definition was manually created without any instance getter/creator.
|
53
|
+
def instance
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
56
|
+
|
51
57
|
# Record a heartbeat on the activity.
|
52
58
|
#
|
53
59
|
# Heartbeats should be used for all non-immediately-returning, non-local activities and they are required to
|
@@ -55,7 +61,8 @@ module Temporalio
|
|
55
61
|
# Users do not have to be concerned with burdening the server by calling this too frequently.
|
56
62
|
#
|
57
63
|
# @param details [Array<Object>] Details to record with the heartbeat.
|
58
|
-
|
64
|
+
# @param detail_hints [Array<Object>, nil] Hints to pass to converter.
|
65
|
+
def heartbeat(*details, detail_hints: nil)
|
59
66
|
raise NotImplementedError
|
60
67
|
end
|
61
68
|
|
@@ -64,6 +71,14 @@ module Temporalio
|
|
64
71
|
raise NotImplementedError
|
65
72
|
end
|
66
73
|
|
74
|
+
# @return [CancellationDetails, nil] Cancellation details if canceled. These are set just before cancellation is
|
75
|
+
# actually canceled. These details only represent when the cancel was first performed. Once set, this object is
|
76
|
+
# never mutated. Therefore, the situation on the server may have changed (e.g. unpause), but this still
|
77
|
+
# represents the values when cancellation first occurred for this attempt.
|
78
|
+
def cancellation_details
|
79
|
+
raise NotImplementedError
|
80
|
+
end
|
81
|
+
|
67
82
|
# @return [Cancellation] Cancellation that is canceled when the worker is shutting down. On worker shutdown, this
|
68
83
|
# is canceled, then the `graceful_shutdown_period` is waited (default 0s), then the activity is canceled.
|
69
84
|
def worker_shutdown_cancellation
|
@@ -102,9 +117,16 @@ module Temporalio
|
|
102
117
|
end
|
103
118
|
|
104
119
|
# @return [Metric::Meter] Metric meter to create metrics on, with some activity-specific attributes already set.
|
120
|
+
# @raise [RuntimeError] Called within a {Testing::ActivityEnvironment} and it was not set.
|
105
121
|
def metric_meter
|
106
122
|
raise NotImplementedError
|
107
123
|
end
|
124
|
+
|
125
|
+
# @return [Client] Temporal client this activity worker is running in.
|
126
|
+
# @raise [RuntimeError] Called within a {Testing::ActivityEnvironment} and it was not set.
|
127
|
+
def client
|
128
|
+
raise NotImplementedError
|
129
|
+
end
|
108
130
|
end
|
109
131
|
end
|
110
132
|
end
|