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.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/Cargo.lock +980 -583
  4. data/Cargo.toml +2 -2
  5. data/Gemfile +7 -3
  6. data/README.md +769 -54
  7. data/Rakefile +10 -296
  8. data/ext/Cargo.toml +2 -0
  9. data/lib/temporalio/activity/complete_async_error.rb +1 -1
  10. data/lib/temporalio/activity/context.rb +18 -2
  11. data/lib/temporalio/activity/definition.rb +180 -65
  12. data/lib/temporalio/activity/info.rb +25 -21
  13. data/lib/temporalio/activity.rb +2 -59
  14. data/lib/temporalio/api/activity/v1/message.rb +25 -0
  15. data/lib/temporalio/api/batch/v1/message.rb +6 -1
  16. data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
  17. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +34 -1
  18. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +1 -1
  19. data/lib/temporalio/api/cloud/identity/v1/message.rb +6 -1
  20. data/lib/temporalio/api/cloud/namespace/v1/message.rb +8 -1
  21. data/lib/temporalio/api/cloud/nexus/v1/message.rb +31 -0
  22. data/lib/temporalio/api/cloud/operation/v1/message.rb +2 -1
  23. data/lib/temporalio/api/cloud/region/v1/message.rb +2 -1
  24. data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
  25. data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
  26. data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
  27. data/lib/temporalio/api/command/v1/message.rb +1 -1
  28. data/lib/temporalio/api/common/v1/message.rb +8 -1
  29. data/lib/temporalio/api/deployment/v1/message.rb +38 -0
  30. data/lib/temporalio/api/enums/v1/batch_operation.rb +1 -1
  31. data/lib/temporalio/api/enums/v1/common.rb +1 -1
  32. data/lib/temporalio/api/enums/v1/deployment.rb +23 -0
  33. data/lib/temporalio/api/enums/v1/event_type.rb +1 -1
  34. data/lib/temporalio/api/enums/v1/failed_cause.rb +1 -1
  35. data/lib/temporalio/api/enums/v1/nexus.rb +21 -0
  36. data/lib/temporalio/api/enums/v1/reset.rb +1 -1
  37. data/lib/temporalio/api/enums/v1/workflow.rb +2 -1
  38. data/lib/temporalio/api/errordetails/v1/message.rb +3 -1
  39. data/lib/temporalio/api/failure/v1/message.rb +3 -1
  40. data/lib/temporalio/api/history/v1/message.rb +3 -1
  41. data/lib/temporalio/api/nexus/v1/message.rb +3 -2
  42. data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
  43. data/lib/temporalio/api/payload_visitor.rb +1581 -0
  44. data/lib/temporalio/api/query/v1/message.rb +2 -1
  45. data/lib/temporalio/api/schedule/v1/message.rb +2 -1
  46. data/lib/temporalio/api/taskqueue/v1/message.rb +4 -1
  47. data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
  48. data/lib/temporalio/api/testservice/v1/service.rb +23 -0
  49. data/lib/temporalio/api/workflow/v1/message.rb +9 -1
  50. data/lib/temporalio/api/workflowservice/v1/request_response.rb +46 -2
  51. data/lib/temporalio/api/workflowservice/v1/service.rb +1 -1
  52. data/lib/temporalio/api.rb +2 -0
  53. data/lib/temporalio/cancellation.rb +34 -14
  54. data/lib/temporalio/client/async_activity_handle.rb +12 -37
  55. data/lib/temporalio/client/connection/cloud_service.rb +309 -231
  56. data/lib/temporalio/client/connection/operator_service.rb +36 -84
  57. data/lib/temporalio/client/connection/service.rb +6 -5
  58. data/lib/temporalio/client/connection/test_service.rb +111 -0
  59. data/lib/temporalio/client/connection/workflow_service.rb +474 -441
  60. data/lib/temporalio/client/connection.rb +90 -44
  61. data/lib/temporalio/client/interceptor.rb +199 -60
  62. data/lib/temporalio/client/schedule.rb +991 -0
  63. data/lib/temporalio/client/schedule_handle.rb +126 -0
  64. data/lib/temporalio/client/with_start_workflow_operation.rb +115 -0
  65. data/lib/temporalio/client/workflow_execution.rb +26 -10
  66. data/lib/temporalio/client/workflow_handle.rb +41 -98
  67. data/lib/temporalio/client/workflow_update_handle.rb +3 -5
  68. data/lib/temporalio/client.rb +247 -44
  69. data/lib/temporalio/common_enums.rb +17 -0
  70. data/lib/temporalio/contrib/open_telemetry.rb +470 -0
  71. data/lib/temporalio/converters/data_converter.rb +4 -7
  72. data/lib/temporalio/converters/failure_converter.rb +5 -3
  73. data/lib/temporalio/converters/payload_converter/composite.rb +4 -0
  74. data/lib/temporalio/converters/payload_converter.rb +6 -8
  75. data/lib/temporalio/converters/raw_value.rb +20 -0
  76. data/lib/temporalio/error/failure.rb +1 -1
  77. data/lib/temporalio/error.rb +11 -2
  78. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +1 -1
  79. data/lib/temporalio/internal/bridge/api/common/common.rb +2 -1
  80. data/lib/temporalio/internal/bridge/api/core_interface.rb +5 -1
  81. data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +33 -0
  82. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +5 -1
  83. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +4 -1
  84. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +2 -1
  85. data/lib/temporalio/internal/bridge/client.rb +11 -6
  86. data/lib/temporalio/internal/bridge/runtime.rb +3 -0
  87. data/lib/temporalio/internal/bridge/testing.rb +23 -0
  88. data/lib/temporalio/internal/bridge/worker.rb +2 -0
  89. data/lib/temporalio/internal/bridge.rb +1 -1
  90. data/lib/temporalio/internal/client/implementation.rb +468 -71
  91. data/lib/temporalio/internal/metric.rb +122 -0
  92. data/lib/temporalio/internal/proto_utils.rb +118 -7
  93. data/lib/temporalio/internal/worker/activity_worker.rb +69 -29
  94. data/lib/temporalio/internal/worker/multi_runner.rb +53 -9
  95. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
  96. data/lib/temporalio/internal/worker/workflow_instance/context.rb +383 -0
  97. data/lib/temporalio/internal/worker/workflow_instance/details.rb +46 -0
  98. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
  99. data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
  100. data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
  101. data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
  102. data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
  103. data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
  104. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +400 -0
  105. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
  106. data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
  107. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +183 -0
  108. data/lib/temporalio/internal/worker/workflow_instance.rb +774 -0
  109. data/lib/temporalio/internal/worker/workflow_worker.rb +239 -0
  110. data/lib/temporalio/metric.rb +109 -0
  111. data/lib/temporalio/retry_policy.rb +37 -14
  112. data/lib/temporalio/runtime/metric_buffer.rb +94 -0
  113. data/lib/temporalio/runtime.rb +160 -79
  114. data/lib/temporalio/search_attributes.rb +93 -37
  115. data/lib/temporalio/testing/activity_environment.rb +44 -16
  116. data/lib/temporalio/testing/workflow_environment.rb +276 -7
  117. data/lib/temporalio/version.rb +1 -1
  118. data/lib/temporalio/worker/activity_executor/thread_pool.rb +9 -217
  119. data/lib/temporalio/worker/activity_executor.rb +3 -3
  120. data/lib/temporalio/worker/interceptor.rb +343 -66
  121. data/lib/temporalio/worker/thread_pool.rb +237 -0
  122. data/lib/temporalio/worker/tuner.rb +38 -0
  123. data/lib/temporalio/worker/workflow_executor/thread_pool.rb +235 -0
  124. data/lib/temporalio/worker/workflow_executor.rb +26 -0
  125. data/lib/temporalio/worker/workflow_replayer.rb +350 -0
  126. data/lib/temporalio/worker.rb +235 -58
  127. data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
  128. data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
  129. data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
  130. data/lib/temporalio/workflow/definition.rb +598 -0
  131. data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
  132. data/lib/temporalio/workflow/future.rb +151 -0
  133. data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
  134. data/lib/temporalio/workflow/info.rb +104 -0
  135. data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
  136. data/lib/temporalio/workflow/update_info.rb +20 -0
  137. data/lib/temporalio/workflow.rb +575 -0
  138. data/lib/temporalio/workflow_history.rb +26 -1
  139. data/lib/temporalio.rb +4 -0
  140. data/temporalio.gemspec +4 -3
  141. metadata +73 -10
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'temporalio/internal/bridge'
3
4
  require 'temporalio/internal/bridge/runtime'
5
+ require 'temporalio/internal/metric'
6
+ require 'temporalio/metric'
7
+ require 'temporalio/runtime/metric_buffer'
4
8
 
5
9
  module Temporalio
6
10
  # Runtime for Temporal Ruby SDK.
@@ -9,6 +13,11 @@ module Temporalio
9
13
  # before any clients are created, and set it via {default=}. Every time a new runtime is created, a new internal Rust
10
14
  # thread pool is created.
11
15
  class Runtime
16
+ TelemetryOptions = Data.define(
17
+ :logging,
18
+ :metrics
19
+ )
20
+
12
21
  # Telemetry options for the runtime.
13
22
  #
14
23
  # @!attribute logging
@@ -16,15 +25,13 @@ module Temporalio
16
25
  # to nil to disable logging.
17
26
  # @!attribute metrics
18
27
  # @return [MetricsOptions, nil] Metrics options.
19
- TelemetryOptions = Struct.new(
20
- :logging,
21
- :metrics,
22
- keyword_init: true
23
- ) do
24
- # @!visibility private
25
- def initialize(**kwargs)
26
- # @type var kwargs: untyped
27
- kwargs[:logging] = LoggingOptions.new unless kwargs.key?(:logging)
28
+ class TelemetryOptions
29
+ # Create telemetry options.
30
+ #
31
+ # @param logging [LoggingOptions, nil] Logging options, default is new {LoggingOptions} with no parameters. Can be
32
+ # set to nil to disable logging.
33
+ # @param metrics [MetricsOptions, nil] Metrics options.
34
+ def initialize(logging: LoggingOptions.new, metrics: nil)
28
35
  super
29
36
  end
30
37
 
@@ -38,20 +45,21 @@ module Temporalio
38
45
  end
39
46
  end
40
47
 
48
+ LoggingOptions = Data.define(
49
+ :log_filter
50
+ # TODO(cretz): forward_to
51
+ )
52
+
41
53
  # Logging options for runtime telemetry.
42
54
  #
43
55
  # @!attribute log_filter
44
56
  # @return [LoggingFilterOptions, String] Logging filter for Core, default is new {LoggingFilterOptions} with no
45
57
  # parameters.
46
- LoggingOptions = Struct.new(
47
- :log_filter,
48
- # TODO(cretz): forward_to
49
- keyword_init: true
50
- ) do
51
- # @!visibility private
52
- def initialize(**kwargs)
53
- # @type var kwargs: untyped
54
- kwargs[:log_filter] = LoggingFilterOptions.new unless kwargs.key?(:log_filter)
58
+ class LoggingOptions
59
+ # Create logging options
60
+ #
61
+ # @param log_filter [LoggingFilterOptions, String] Logging filter for Core.
62
+ def initialize(log_filter: LoggingFilterOptions.new)
55
63
  super
56
64
  end
57
65
 
@@ -70,22 +78,23 @@ module Temporalio
70
78
  end
71
79
  end
72
80
 
81
+ LoggingFilterOptions = Data.define(
82
+ :core_level,
83
+ :other_level
84
+ )
85
+
73
86
  # Logging filter options for Core.
74
87
  #
75
88
  # @!attribute core_level
76
- # @return ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'] Log level for Core log messages, default is +'WARN'+.
89
+ # @return ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'] Log level for Core log messages.
77
90
  # @!attribute other_level
78
- # @return ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'] Log level for other Rust log messages, default is +'WARN'+.
79
- LoggingFilterOptions = Struct.new(
80
- :core_level,
81
- :other_level,
82
- keyword_init: true
83
- ) do
84
- # @!visibility private
85
- def initialize(**kwargs)
86
- # @type var kwargs: untyped
87
- kwargs[:core_level] = 'WARN' unless kwargs.key?(:core_level)
88
- kwargs[:other_level] = 'ERROR' unless kwargs.key?(:other_level)
91
+ # @return ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'] Log level for other Rust log messages.
92
+ class LoggingFilterOptions
93
+ # Create logging filter options.
94
+ #
95
+ # @param core_level ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'] Log level for Core log messages.
96
+ # @!attribute other_level ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'] Log level for other Rust log messages.
97
+ def initialize(core_level: 'WARN', other_level: 'ERROR')
89
98
  super
90
99
  end
91
100
 
@@ -96,32 +105,56 @@ module Temporalio
96
105
  end
97
106
  end
98
107
 
108
+ MetricsOptions = Data.define(
109
+ :opentelemetry,
110
+ :prometheus,
111
+ :buffer,
112
+ :attach_service_name,
113
+ :global_tags,
114
+ :metric_prefix
115
+ )
116
+
99
117
  # Metrics options for runtime telemetry. Either {opentelemetry} or {prometheus} required, but not both.
100
118
  #
101
119
  # @!attribute opentelemetry
102
120
  # @return [OpenTelemetryMetricsOptions, nil] OpenTelemetry options if using OpenTelemetry. This is mutually
103
- # exclusive with +prometheus+.
121
+ # exclusive with `prometheus` and `buffer`.
104
122
  # @!attribute prometheus
105
123
  # @return [PrometheusMetricsOptions, nil] Prometheus options if using Prometheus. This is mutually exclusive with
106
- # +opentelemetry+.
124
+ # `opentelemetry` and `buffer`.
125
+ # @!attribute buffer
126
+ # @return [MetricBuffer, nil] Metric buffer to send all metrics to. This is mutually exclusive with `prometheus`
127
+ # and `opentelemetry`.
107
128
  # @!attribute attach_service_name
108
- # @return [Boolean] Whether to put the service_name on every metric, default +true+.
129
+ # @return [Boolean] Whether to put the service_name on every metric.
109
130
  # @!attribute global_tags
110
131
  # @return [Hash<String, String>, nil] Resource tags to be applied to all metrics.
111
132
  # @!attribute metric_prefix
112
- # @return [String, nil] Prefix to put on every Temporal metric. If unset, defaults to +temporal_+.
113
- MetricsOptions = Struct.new(
114
- :opentelemetry,
115
- :prometheus,
116
- :attach_service_name,
117
- :global_tags,
118
- :metric_prefix,
119
- keyword_init: true
120
- ) do
121
- # @!visibility private
122
- def initialize(**kwargs)
123
- # @type var kwargs: untyped
124
- kwargs[:attach_service_name] = true unless kwargs.key?(:attach_service_name)
133
+ # @return [String, nil] Prefix to put on every Temporal metric. If unset, defaults to `temporal_`.
134
+ class MetricsOptions
135
+ # Create metrics options. Either `opentelemetry` or `prometheus` required, but not both.
136
+ #
137
+ # @param opentelemetry [OpenTelemetryMetricsOptions, nil] OpenTelemetry options if using OpenTelemetry. This is
138
+ # mutually exclusive with `prometheus` and `buffer`.
139
+ # @param prometheus [PrometheusMetricsOptions, nil] Prometheus options if using Prometheus. This is mutually
140
+ # exclusive with `opentelemetry` and `buffer`.
141
+ # @param buffer [MetricBuffer, nil] Metric buffer to send all metrics to. This is mutually exclusive with
142
+ # `prometheus` and `opentelemetry`.
143
+ # @param attach_service_name [Boolean] Whether to put the service_name on every metric.
144
+ # @param global_tags [Hash<String, String>, nil] Resource tags to be applied to all metrics.
145
+ # @param metric_prefix [String, nil] Prefix to put on every Temporal metric. If unset, defaults to `temporal_`.
146
+ def initialize(
147
+ opentelemetry: nil,
148
+ prometheus: nil,
149
+ buffer: nil,
150
+ attach_service_name: true,
151
+ global_tags: nil,
152
+ metric_prefix: nil
153
+ )
154
+ if [opentelemetry, prometheus, buffer].count { |v| !v.nil? } > 1
155
+ raise 'Can only have one of opentelemetry, prometheus, or buffer'
156
+ end
157
+
125
158
  super
126
159
  end
127
160
 
@@ -131,6 +164,7 @@ module Temporalio
131
164
  Internal::Bridge::Runtime::MetricsOptions.new(
132
165
  opentelemetry: opentelemetry&._to_bridge,
133
166
  prometheus: prometheus&._to_bridge,
167
+ buffered_with_size: buffer&._buffer_size,
134
168
  attach_service_name:,
135
169
  global_tags:,
136
170
  metric_prefix:
@@ -138,6 +172,16 @@ module Temporalio
138
172
  end
139
173
  end
140
174
 
175
+ OpenTelemetryMetricsOptions = Data.define(
176
+ :url,
177
+ :headers,
178
+ :metric_periodicity,
179
+ :metric_temporality,
180
+ :durations_as_seconds,
181
+ :http,
182
+ :histogram_bucket_overrides
183
+ )
184
+
141
185
  # Options for exporting metrics to OpenTelemetry.
142
186
  #
143
187
  # @!attribute url
@@ -152,25 +196,38 @@ module Temporalio
152
196
  # @!attribute durations_as_seconds
153
197
  # @return [Boolean] Whether to use float seconds instead of integer milliseconds for durations, default is
154
198
  # +false+.
155
- OpenTelemetryMetricsOptions = Struct.new(
156
- :url,
157
- :headers,
158
- :metric_periodicity,
159
- :metric_temporality,
160
- :durations_as_seconds,
161
- keyword_init: true
162
- ) do
199
+ # @!attribute http
200
+ # @return [Boolean] True if the protocol is HTTP, false if gRPC (the default).
201
+ # @!attribute histogram_bucket_overrides
202
+ # @return [Hash<String, Array<Numeric>>, nil] Override default histogram buckets. Key of the hash it the metric
203
+ # name, value is an array of floats for the set of buckets.
204
+ class OpenTelemetryMetricsOptions
163
205
  # OpenTelemetry metric temporality.
164
- module MetricTemporality # rubocop:disable Lint/ConstantDefinitionInBlock
206
+ module MetricTemporality
165
207
  CUMULATIVE = 1
166
208
  DELTA = 2
167
209
  end
168
210
 
169
- # @!visibility private
170
- def initialize(**kwargs)
171
- # @type var kwargs: untyped
172
- kwargs[:metric_temporality] = MetricTemporality::CUMULATIVE unless kwargs.key?(:metric_temporality)
173
- kwargs[:durations_as_seconds] = false unless kwargs.key?(:durations_as_seconds)
211
+ # Create OpenTelemetry options.
212
+ #
213
+ # @param url [String] URL for OpenTelemetry endpoint.
214
+ # @param headers [Hash<String, String>, nil] Headers for OpenTelemetry endpoint.
215
+ # @param metric_periodicity [Float, nil] How frequently metrics should be exported, unset uses internal default.
216
+ # @param metric_temporality [MetricTemporality] How frequently metrics should be exported.
217
+ # @param durations_as_seconds [Boolean] Whether to use float seconds instead of integer milliseconds for
218
+ # durations.
219
+ # @param http [Boolean] True if the protocol is HTTP, false if gRPC (the default).
220
+ # @param histogram_bucket_overrides [Hash<String, Array<Numeric>>, nil] Override default histogram buckets. Key of
221
+ # the hash it the metric name, value is an array of floats for the set of buckets.
222
+ def initialize(
223
+ url:,
224
+ headers: nil,
225
+ metric_periodicity: nil,
226
+ metric_temporality: MetricTemporality::CUMULATIVE,
227
+ durations_as_seconds: false,
228
+ http: false,
229
+ histogram_bucket_overrides: nil
230
+ )
174
231
  super
175
232
  end
176
233
 
@@ -186,35 +243,51 @@ module Temporalio
186
243
  when MetricTemporality::DELTA then true
187
244
  else raise 'Unrecognized metric temporality'
188
245
  end,
189
- durations_as_seconds:
246
+ durations_as_seconds:,
247
+ http:,
248
+ histogram_bucket_overrides:
190
249
  )
191
250
  end
192
251
  end
193
252
 
253
+ PrometheusMetricsOptions = Data.define(
254
+ :bind_address,
255
+ :counters_total_suffix,
256
+ :unit_suffix,
257
+ :durations_as_seconds,
258
+ :histogram_bucket_overrides
259
+ )
260
+
194
261
  # Options for exporting metrics to Prometheus.
195
262
  #
196
263
  # @!attribute bind_address
197
264
  # @return [String] Address to bind to for Prometheus endpoint.
198
265
  # @!attribute counters_total_suffix
199
- # @return [Boolean] If +true+, all counters will include a +_total+ suffix, default is +false+.
266
+ # @return [Boolean] If `true`, all counters will include a `_total` suffix.
200
267
  # @!attribute unit_suffix
201
- # @return [Boolean] If +true+, all histograms will include the unit in their name as a suffix, default is +false+.
268
+ # @return [Boolean] If `true`, all histograms will include the unit in their name as a suffix.
202
269
  # @!attribute durations_as_seconds
203
- # @return [Boolean] Whether to use float seconds instead of integer milliseconds for durations, default is
204
- # +false+.
205
- PrometheusMetricsOptions = Struct.new(
206
- :bind_address,
207
- :counters_total_suffix,
208
- :unit_suffix,
209
- :durations_as_seconds,
210
- keyword_init: true
211
- ) do
212
- # @!visibility private
213
- def initialize(**kwargs)
214
- # @type var kwargs: untyped
215
- kwargs[:counters_total_suffix] = false unless kwargs.key?(:counters_total_suffix)
216
- kwargs[:unit_suffix] = false unless kwargs.key?(:unit_suffix)
217
- kwargs[:durations_as_seconds] = false unless kwargs.key?(:durations_as_seconds)
270
+ # @return [Boolean] Whether to use float seconds instead of integer milliseconds for durations.
271
+ # @!attribute histogram_bucket_overrides
272
+ # @return [Hash<String, Array<Numeric>>, nil] Override default histogram buckets. Key of the hash it the metric
273
+ # name, value is an array of floats for the set of buckets.
274
+ class PrometheusMetricsOptions
275
+ # Create Prometheus options.
276
+ #
277
+ # @param bind_address [String] Address to bind to for Prometheus endpoint.
278
+ # @param counters_total_suffix [Boolean] If `true`, all counters will include a `_total` suffix.
279
+ # @param unit_suffix [Boolean] If `true`, all histograms will include the unit in their name as a suffix.
280
+ # @param durations_as_seconds [Boolean] Whether to use float seconds instead of integer milliseconds for
281
+ # durations.
282
+ # @param histogram_bucket_overrides [Hash<String, Array<Numeric>>, nil] Override default histogram buckets. Key of
283
+ # the hash it the metric name, value is an array of floats for the set of buckets.
284
+ def initialize(
285
+ bind_address:,
286
+ counters_total_suffix: false,
287
+ unit_suffix: false,
288
+ durations_as_seconds: false,
289
+ histogram_bucket_overrides: nil
290
+ )
218
291
  super
219
292
  end
220
293
 
@@ -225,7 +298,8 @@ module Temporalio
225
298
  bind_address:,
226
299
  counters_total_suffix:,
227
300
  unit_suffix:,
228
- durations_as_seconds:
301
+ durations_as_seconds:,
302
+ histogram_bucket_overrides:
229
303
  )
230
304
  end
231
305
  end
@@ -248,14 +322,21 @@ module Temporalio
248
322
  @default = runtime
249
323
  end
250
324
 
325
+ # @return [Metric::Meter] Metric meter that can create and record metric values.
326
+ attr_reader :metric_meter
327
+
251
328
  # Create new Runtime. For most users, this should only be done once globally. In addition to creating a Rust thread
252
329
  # pool, this also consumes a Ruby thread for its lifetime.
253
330
  #
254
331
  # @param telemetry [TelemetryOptions] Telemetry options to set.
255
332
  def initialize(telemetry: TelemetryOptions.new)
333
+ # Set runtime on the buffer which will fail if the buffer is used on another runtime
334
+ telemetry.metrics&.buffer&._set_runtime(self)
335
+
256
336
  @core_runtime = Internal::Bridge::Runtime.new(
257
337
  Internal::Bridge::Runtime::Options.new(telemetry: telemetry._to_bridge)
258
338
  )
339
+ @metric_meter = Internal::Metric::Meter.create_from_runtime(self) || Metric::Meter.null
259
340
  # We need a thread to run the command loop
260
341
  # TODO(cretz): Is this something users should be concerned about or need control over?
261
342
  Thread.new do
@@ -7,7 +7,7 @@ module Temporalio
7
7
  #
8
8
  # This is represented as a mapping of {SearchAttributes::Key} to object values. This is not a hash though it does have
9
9
  # a few hash-like methods and can be converted to a hash via {#to_h}. In some situations, such as in workflows, this
10
- # class is frozen.
10
+ # class is immutable for outside use.
11
11
  class SearchAttributes
12
12
  # Key for a search attribute.
13
13
  class Key
@@ -20,7 +20,7 @@ module Temporalio
20
20
  def initialize(name, type)
21
21
  raise ArgumentError, 'Invalid type' unless Api::Enums::V1::IndexedValueType.lookup(type)
22
22
 
23
- @name = name
23
+ @name = name.to_s
24
24
  @type = type
25
25
  end
26
26
 
@@ -104,28 +104,54 @@ module Temporalio
104
104
  @key = key
105
105
  @value = value
106
106
  end
107
+
108
+ # @!visibility private
109
+ def _to_proto_pair
110
+ SearchAttributes._to_proto_pair(key, value)
111
+ end
107
112
  end
108
113
 
109
114
  # @!visibility private
110
- def self.from_proto(proto)
111
- return nil unless proto
112
- raise ArgumentError, 'Expected proto search attribute' unless proto.is_a?(Api::Common::V1::SearchAttributes)
113
-
114
- SearchAttributes.new(proto.indexed_fields.map do |key_name, payload| # rubocop:disable Style/MapToHash
115
- key = Key.new(key_name, IndexedValueType::PROTO_VALUES[payload.metadata['type']])
116
- value = value_from_payload(payload)
117
- [key, value]
118
- end.to_h)
115
+ def self._from_proto(proto, disable_mutations: false, never_nil: false)
116
+ return nil unless proto || never_nil
117
+
118
+ attrs = if proto
119
+ unless proto.is_a?(Api::Common::V1::SearchAttributes)
120
+ raise ArgumentError, 'Expected proto search attribute'
121
+ end
122
+
123
+ SearchAttributes.new(proto.indexed_fields.map do |key_name, payload| # rubocop:disable Style/MapToHash
124
+ key = Key.new(key_name, IndexedValueType::PROTO_VALUES[payload.metadata['type']])
125
+ value = _value_from_payload(payload)
126
+ [key, value]
127
+ end.to_h)
128
+ else
129
+ SearchAttributes.new
130
+ end
131
+ attrs._disable_mutations = disable_mutations
132
+ attrs
119
133
  end
120
134
 
121
135
  # @!visibility private
122
- def self.value_from_payload(payload)
136
+ def self._value_from_payload(payload)
123
137
  value = Converters::PayloadConverter.default.from_payload(payload)
124
138
  # Time needs to be converted
125
139
  value = Time.iso8601(value) if payload.metadata['type'] == 'DateTime' && value.is_a?(String)
126
140
  value
127
141
  end
128
142
 
143
+ # @!visibility private
144
+ def self._to_proto_pair(key, value)
145
+ # We use a default converter, but if type is a time, we need ISO format
146
+ value = value.iso8601 if key.type == IndexedValueType::TIME && value.is_a?(Time)
147
+
148
+ # Convert to payload
149
+ payload = Converters::PayloadConverter.default.to_payload(value)
150
+ payload.metadata['type'] = IndexedValueType::PROTO_NAMES[key.type]
151
+
152
+ [key.name, payload]
153
+ end
154
+
129
155
  # Create a search attribute collection.
130
156
  #
131
157
  # @param existing [SearchAttributes, Hash<Key, Object>, nil] Existing collection. This can be another
@@ -149,6 +175,7 @@ module Temporalio
149
175
  # @param key [Key] A key to set. This must be a {Key} and the value must be proper for the {Key#type}.
150
176
  # @param value [Object, nil] The value to set. If `nil`, the key is removed. The value must be proper for the `key`.
151
177
  def []=(key, value)
178
+ _assert_mutations_enabled
152
179
  # Key must be a Key
153
180
  raise ArgumentError, 'Key must be a key' unless key.is_a?(Key)
154
181
 
@@ -162,33 +189,37 @@ module Temporalio
162
189
 
163
190
  # Get a search attribute value for a key.
164
191
  #
165
- # @param key [Key, String] The key to find. If this is a {Key}, it will use key equality (i.e. name and type) to
166
- # search. If this is a {::String}, the type is not checked when finding the proper key.
192
+ # @param key [Key, String, Symbol] The key to find. If this is a {Key}, it will use key equality (i.e. name and
193
+ # type) to search. If this is a {::String}, the type is not checked when finding the proper key.
167
194
  # @return [Object, nil] Value if found or `nil` if not.
168
195
  def [](key)
169
196
  # Key must be a Key or a string
170
- if key.is_a?(Key)
197
+ case key
198
+ when Key
171
199
  @raw_hash[key]
172
- elsif key.is_a?(String)
173
- @raw_hash.find { |hash_key, _| hash_key.name == key }&.last
200
+ when String, Symbol
201
+ @raw_hash.find { |hash_key, _| hash_key.name == key.to_s }&.last
174
202
  else
175
- raise ArgumentError, 'Key must be a key or string'
203
+ raise ArgumentError, 'Key must be a key or string/symbol'
176
204
  end
177
205
  end
178
206
 
179
207
  # Delete a search attribute key
180
208
  #
181
- # @param key [Key, String] The key to delete. Regardless of whether this is a {Key} or a {::String}, the key with
182
- # the matching name will be deleted. This means a {Key} with a matching name but different type may be deleted.
209
+ # @param key [Key, String, Symbol] The key to delete. Regardless of whether this is a {Key} or a {::String}, the key
210
+ # with the matching name will be deleted. This means a {Key} with a matching name but different type may be
211
+ # deleted.
183
212
  def delete(key)
213
+ _assert_mutations_enabled
184
214
  # Key must be a Key or a string, but we delete all values for the
185
215
  # name no matter what
186
- name = if key.is_a?(Key)
216
+ name = case key
217
+ when Key
187
218
  key.name
188
- elsif key.is_a?(String)
189
- key
219
+ when String, Symbol
220
+ key.to_s
190
221
  else
191
- raise ArgumentError, 'Key must be a key or string'
222
+ raise ArgumentError, 'Key must be a key or string/symbol'
192
223
  end
193
224
  @raw_hash.delete_if { |hash_key, _| hash_key.name == name }
194
225
  end
@@ -205,7 +236,9 @@ module Temporalio
205
236
 
206
237
  # @return [SearchAttributes] Copy of the search attributes.
207
238
  def dup
208
- SearchAttributes.new(self)
239
+ attrs = SearchAttributes.new(self)
240
+ attrs._disable_mutations = false
241
+ attrs
209
242
  end
210
243
 
211
244
  # @return [Boolean] Whether the set of attributes is empty.
@@ -218,6 +251,14 @@ module Temporalio
218
251
  @raw_hash.length
219
252
  end
220
253
 
254
+ # Check equality.
255
+ #
256
+ # @param other [SearchAttributes] To compare.
257
+ # @return [Boolean] Whether equal.
258
+ def ==(other)
259
+ other.is_a?(SearchAttributes) && @raw_hash == other._raw_hash
260
+ end
261
+
221
262
  alias size length
222
263
 
223
264
  # Return a new search attributes collection with updates applied.
@@ -225,6 +266,7 @@ module Temporalio
225
266
  # @param updates [Update] Updates created via {Key#value_set} or {Key#value_unset}.
226
267
  # @return [SearchAttributes] New collection.
227
268
  def update(*updates)
269
+ _assert_mutations_enabled
228
270
  attrs = dup
229
271
  attrs.update!(*updates)
230
272
  attrs
@@ -234,27 +276,41 @@ module Temporalio
234
276
  #
235
277
  # @param updates [Update] Updates created via {Key#value_set} or {Key#value_unset}.
236
278
  def update!(*updates)
279
+ _assert_mutations_enabled
237
280
  updates.each do |update|
238
281
  raise ArgumentError, 'Update must be an update' unless update.is_a?(Update)
239
282
 
240
- self[update.key] = update.value
283
+ if update.value.nil?
284
+ delete(update.key)
285
+ else
286
+ self[update.key] = update.value
287
+ end
241
288
  end
242
289
  end
243
290
 
244
291
  # @!visibility private
245
- def to_proto
246
- Api::Common::V1::SearchAttributes.new(
247
- indexed_fields: @raw_hash.to_h do |key, value|
248
- # We use a default converter, but if type is a time, we need ISO format
249
- value = value.iso8601 if key.type == IndexedValueType::TIME
292
+ def _raw_hash
293
+ @raw_hash
294
+ end
250
295
 
251
- # Convert to payload
252
- payload = Converters::PayloadConverter.default.to_payload(value)
253
- payload.metadata['type'] = IndexedValueType::PROTO_NAMES[key.type]
296
+ # @!visibility private
297
+ def _to_proto
298
+ Api::Common::V1::SearchAttributes.new(indexed_fields: _to_proto_hash)
299
+ end
254
300
 
255
- [key.name, payload]
256
- end
257
- )
301
+ # @!visibility private
302
+ def _to_proto_hash
303
+ @raw_hash.to_h { |key, value| SearchAttributes._to_proto_pair(key, value) }
304
+ end
305
+
306
+ # @!visibility private
307
+ def _assert_mutations_enabled
308
+ raise 'Search attribute mutations disabled' if @disable_mutations
309
+ end
310
+
311
+ # @!visibility private
312
+ def _disable_mutations=(value)
313
+ @disable_mutations = value
258
314
  end
259
315
 
260
316
  # Type for a search attribute key/value.