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.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/Cargo.lock +1019 -669
  4. data/Cargo.toml +5 -5
  5. data/Gemfile +4 -0
  6. data/README.md +281 -44
  7. data/Rakefile +1 -1
  8. data/ext/Cargo.toml +4 -3
  9. data/lib/temporalio/activity/cancellation_details.rb +58 -0
  10. data/lib/temporalio/activity/context.rb +23 -1
  11. data/lib/temporalio/activity/definition.rb +63 -8
  12. data/lib/temporalio/activity/info.rb +28 -4
  13. data/lib/temporalio/activity.rb +2 -0
  14. data/lib/temporalio/api/activity/v1/message.rb +1 -1
  15. data/lib/temporalio/api/batch/v1/message.rb +9 -2
  16. data/lib/temporalio/api/cloud/account/v1/message.rb +1 -1
  17. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +11 -2
  18. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +2 -2
  19. data/lib/temporalio/api/cloud/identity/v1/message.rb +7 -2
  20. data/lib/temporalio/api/cloud/namespace/v1/message.rb +6 -2
  21. data/lib/temporalio/api/cloud/nexus/v1/message.rb +3 -2
  22. data/lib/temporalio/api/cloud/operation/v1/message.rb +1 -1
  23. data/lib/temporalio/api/cloud/region/v1/message.rb +1 -1
  24. data/lib/temporalio/api/cloud/resource/v1/message.rb +1 -1
  25. data/lib/temporalio/api/cloud/sink/v1/message.rb +1 -1
  26. data/lib/temporalio/api/cloud/usage/v1/message.rb +1 -1
  27. data/lib/temporalio/api/command/v1/message.rb +2 -2
  28. data/lib/temporalio/api/common/v1/grpc_status.rb +1 -1
  29. data/lib/temporalio/api/common/v1/message.rb +4 -2
  30. data/lib/temporalio/api/deployment/v1/message.rb +39 -0
  31. data/lib/temporalio/api/enums/v1/batch_operation.rb +2 -2
  32. data/lib/temporalio/api/enums/v1/command_type.rb +1 -1
  33. data/lib/temporalio/api/enums/v1/common.rb +5 -2
  34. data/lib/temporalio/api/enums/v1/deployment.rb +24 -0
  35. data/lib/temporalio/api/enums/v1/event_type.rb +2 -2
  36. data/lib/temporalio/api/enums/v1/failed_cause.rb +2 -2
  37. data/lib/temporalio/api/enums/v1/namespace.rb +1 -1
  38. data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
  39. data/lib/temporalio/api/enums/v1/query.rb +1 -1
  40. data/lib/temporalio/api/enums/v1/reset.rb +2 -2
  41. data/lib/temporalio/api/enums/v1/schedule.rb +1 -1
  42. data/lib/temporalio/api/enums/v1/task_queue.rb +1 -1
  43. data/lib/temporalio/api/enums/v1/update.rb +1 -1
  44. data/lib/temporalio/api/enums/v1/workflow.rb +3 -2
  45. data/lib/temporalio/api/errordetails/v1/message.rb +4 -2
  46. data/lib/temporalio/api/export/v1/message.rb +1 -1
  47. data/lib/temporalio/api/failure/v1/message.rb +5 -2
  48. data/lib/temporalio/api/filter/v1/message.rb +1 -1
  49. data/lib/temporalio/api/history/v1/message.rb +6 -2
  50. data/lib/temporalio/api/namespace/v1/message.rb +1 -1
  51. data/lib/temporalio/api/nexus/v1/message.rb +3 -2
  52. data/lib/temporalio/api/operatorservice/v1/request_response.rb +1 -1
  53. data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
  54. data/lib/temporalio/api/payload_visitor.rb +162 -7
  55. data/lib/temporalio/api/protocol/v1/message.rb +1 -1
  56. data/lib/temporalio/api/query/v1/message.rb +3 -2
  57. data/lib/temporalio/api/replication/v1/message.rb +1 -1
  58. data/lib/temporalio/api/rules/v1/message.rb +27 -0
  59. data/lib/temporalio/api/schedule/v1/message.rb +2 -2
  60. data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +1 -1
  61. data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +1 -1
  62. data/lib/temporalio/api/sdk/v1/user_metadata.rb +1 -1
  63. data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +1 -1
  64. data/lib/temporalio/api/taskqueue/v1/message.rb +5 -2
  65. data/lib/temporalio/api/testservice/v1/request_response.rb +1 -1
  66. data/lib/temporalio/api/testservice/v1/service.rb +1 -1
  67. data/lib/temporalio/api/update/v1/message.rb +1 -1
  68. data/lib/temporalio/api/version/v1/message.rb +1 -1
  69. data/lib/temporalio/api/worker/v1/message.rb +30 -0
  70. data/lib/temporalio/api/workflow/v1/message.rb +22 -2
  71. data/lib/temporalio/api/workflowservice/v1/request_response.rb +58 -12
  72. data/lib/temporalio/api/workflowservice/v1/service.rb +2 -2
  73. data/lib/temporalio/api.rb +1 -0
  74. data/lib/temporalio/client/async_activity_handle.rb +12 -4
  75. data/lib/temporalio/client/connection/cloud_service.rb +60 -0
  76. data/lib/temporalio/client/connection/workflow_service.rb +343 -28
  77. data/lib/temporalio/client/interceptor.rb +64 -7
  78. data/lib/temporalio/client/schedule.rb +35 -3
  79. data/lib/temporalio/client/with_start_workflow_operation.rb +123 -0
  80. data/lib/temporalio/client/workflow_execution.rb +19 -0
  81. data/lib/temporalio/client/workflow_handle.rb +47 -7
  82. data/lib/temporalio/client/workflow_update_handle.rb +9 -3
  83. data/lib/temporalio/client.rb +231 -4
  84. data/lib/temporalio/common_enums.rb +14 -0
  85. data/lib/temporalio/contrib/open_telemetry.rb +474 -0
  86. data/lib/temporalio/converters/data_converter.rb +18 -8
  87. data/lib/temporalio/converters/failure_converter.rb +6 -3
  88. data/lib/temporalio/converters/payload_converter/binary_null.rb +2 -2
  89. data/lib/temporalio/converters/payload_converter/binary_plain.rb +2 -2
  90. data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +2 -2
  91. data/lib/temporalio/converters/payload_converter/composite.rb +6 -4
  92. data/lib/temporalio/converters/payload_converter/encoding.rb +4 -2
  93. data/lib/temporalio/converters/payload_converter/json_plain.rb +2 -2
  94. data/lib/temporalio/converters/payload_converter/json_protobuf.rb +2 -2
  95. data/lib/temporalio/converters/payload_converter.rb +16 -6
  96. data/lib/temporalio/error/failure.rb +19 -1
  97. data/lib/temporalio/error.rb +2 -1
  98. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +1 -1
  99. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +3 -2
  100. data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +1 -1
  101. data/lib/temporalio/internal/bridge/api/common/common.rb +3 -2
  102. data/lib/temporalio/internal/bridge/api/core_interface.rb +1 -1
  103. data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +1 -1
  104. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +3 -2
  105. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +2 -2
  106. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +3 -2
  107. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +3 -2
  108. data/lib/temporalio/internal/bridge/runtime.rb +3 -0
  109. data/lib/temporalio/internal/bridge/testing.rb +3 -0
  110. data/lib/temporalio/internal/bridge/worker.rb +28 -4
  111. data/lib/temporalio/internal/bridge.rb +1 -1
  112. data/lib/temporalio/internal/client/implementation.rb +281 -51
  113. data/lib/temporalio/internal/proto_utils.rb +38 -6
  114. data/lib/temporalio/internal/worker/activity_worker.rb +112 -27
  115. data/lib/temporalio/internal/worker/multi_runner.rb +2 -2
  116. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +8 -6
  117. data/lib/temporalio/internal/worker/workflow_instance/context.rb +100 -5
  118. data/lib/temporalio/internal/worker/workflow_instance/details.rb +7 -2
  119. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +2 -2
  120. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +64 -18
  121. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +39 -40
  122. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +22 -2
  123. data/lib/temporalio/internal/worker/workflow_instance.rb +134 -55
  124. data/lib/temporalio/internal/worker/workflow_worker.rb +74 -21
  125. data/lib/temporalio/priority.rb +59 -0
  126. data/lib/temporalio/runtime/metric_buffer.rb +94 -0
  127. data/lib/temporalio/runtime.rb +48 -10
  128. data/lib/temporalio/search_attributes.rb +13 -0
  129. data/lib/temporalio/testing/activity_environment.rb +59 -16
  130. data/lib/temporalio/testing/workflow_environment.rb +29 -6
  131. data/lib/temporalio/version.rb +1 -1
  132. data/lib/temporalio/versioning_override.rb +56 -0
  133. data/lib/temporalio/worker/deployment_options.rb +45 -0
  134. data/lib/temporalio/worker/illegal_workflow_call_validator.rb +64 -0
  135. data/lib/temporalio/worker/interceptor.rb +16 -1
  136. data/lib/temporalio/worker/poller_behavior.rb +61 -0
  137. data/lib/temporalio/worker/thread_pool.rb +6 -6
  138. data/lib/temporalio/worker/tuner.rb +38 -0
  139. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +14 -8
  140. data/lib/temporalio/worker/workflow_executor.rb +1 -1
  141. data/lib/temporalio/worker/workflow_replayer.rb +349 -0
  142. data/lib/temporalio/worker.rb +117 -75
  143. data/lib/temporalio/worker_deployment_version.rb +67 -0
  144. data/lib/temporalio/workflow/child_workflow_handle.rb +10 -2
  145. data/lib/temporalio/workflow/definition.rb +217 -35
  146. data/lib/temporalio/workflow/external_workflow_handle.rb +3 -1
  147. data/lib/temporalio/workflow/future.rb +2 -2
  148. data/lib/temporalio/workflow/info.rb +26 -1
  149. data/lib/temporalio/workflow.rb +119 -15
  150. data/lib/temporalio/workflow_history.rb +26 -1
  151. data/lib/temporalio.rb +1 -0
  152. data/temporalio.gemspec +3 -1
  153. 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 = "1.0", features = ["constructor", "display", "from", "into", "debug"] }
16
+ derive_more = { version = "2.0", features = ["constructor", "display", "from", "into", "debug", "try_into"] }
17
17
  thiserror = "2"
18
- tonic = "0.12"
19
- tonic-build = "0.12"
20
- opentelemetry = { version = "0.26", features = ["metrics"] }
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://rubydoc.info/gems/temporalio/0.2.0)
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
- **NOTE: This README is for the current branch and not necessarily what's released on RubyGems.**
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
- - [ActiveRecord and ActiveModel](#activerecord-and-activemodel)
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 and Linux MUSL do not have gems. See the [Platform Support](#platform-support) section for more
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
- ##### ActiveRecord and ActiveModel
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
- By default, `ActiveRecord` and `ActiveModel` objects do not natively support the `JSON` module. A mixin can be created
291
- to add this support for `ActiveRecord`, for example:
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 ActiveRecordJSONSupport
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
- hash = as_json
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
- ret = new
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
- Similarly, a mixin for `ActiveModel` that adds `attributes` accessors can leverage this same mixin, for example:
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
- ```ruby
318
- module ActiveModelJSONSupport
319
- extend ActiveSupport::Concern
320
- include ActiveRecordJSONSupport
341
+ ##### Converter Hints
321
342
 
322
- included do
323
- def attributes=(hash)
324
- hash.each do |key, value|
325
- send("#{key}=", value)
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
- def attributes
330
- instance_values
331
- end
332
- end
333
- end
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
- Now `include ActiveRecordJSONSupport` or `include ActiveModelJSONSupport` will make the models work with Ruby `JSON`
337
- module and therefore Temporal. Of course any other approach to make the models work with the `JSON` module will work as
338
- well.
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
- TODO: Workflow replayer not yet implemented
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. Currently, a gem is not published for
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.1+, but due to [an issue](https://github.com/temporalio/sdk-ruby/issues/162), fibers (and
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.1 (i.e. `ruby` and `bundle` on the `PATH`)
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
@@ -94,7 +94,7 @@ Rake::Task[:build].enhance([:copy_parent_files]) do
94
94
  end
95
95
 
96
96
  task :rust_lint do
97
- sh 'cargo', 'clippy', '--', '-Dwarnings'
97
+ sh 'cargo', 'clippy', '-p', 'temporalio_bridge', '--no-deps', '--', '-Dwarnings'
98
98
  sh 'cargo', 'fmt', '--check'
99
99
  end
100
100
 
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 = "2021"
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.26"
22
+ tokio = "1.37"
23
+ tokio-stream = "0.1"
23
24
  tokio-util = "0.7"
24
- tonic = "0.12"
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
- def heartbeat(*details)
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