temporalio 1.10.0-aarch64-linux → 1.11.0-aarch64-linux

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -2
  3. data/lib/temporalio/api/activity/v1/message.rb +1 -1
  4. data/lib/temporalio/api/common/v1/message.rb +2 -1
  5. data/lib/temporalio/api/schedule/v1/message.rb +1 -1
  6. data/lib/temporalio/api/taskqueue/v1/message.rb +2 -1
  7. data/lib/temporalio/api/worker/v1/message.rb +1 -1
  8. data/lib/temporalio/api/workflowservice/v1/request_response.rb +1 -1
  9. data/lib/temporalio/client/connection.rb +4 -4
  10. data/lib/temporalio/contrib/aws/s3_driver/aws_sdk_s3_storage_driver_client.rb +52 -0
  11. data/lib/temporalio/contrib/aws/s3_driver/s3_storage_driver.rb +211 -0
  12. data/lib/temporalio/contrib/aws/s3_driver/s3_storage_driver_client.rb +56 -0
  13. data/lib/temporalio/contrib/aws/s3_driver.rb +22 -0
  14. data/lib/temporalio/contrib/open_telemetry.rb +1 -1
  15. data/lib/temporalio/converters/external_storage_concurrency.rb +4 -6
  16. data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +1 -1
  17. data/lib/temporalio/converters/payload_converter/json_protobuf.rb +1 -1
  18. data/lib/temporalio/converters/storage_driver_limiter.rb +6 -8
  19. data/lib/temporalio/converters/storage_driver_retrieve_context.rb +1 -2
  20. data/lib/temporalio/converters/storage_driver_store_context.rb +1 -2
  21. data/lib/temporalio/env_config.rb +89 -8
  22. data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so +2 -2
  23. data/lib/temporalio/internal/bridge/3.4/temporalio_bridge.so +2 -2
  24. data/lib/temporalio/internal/bridge/4.0/temporalio_bridge.so +2 -2
  25. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +2 -2
  26. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +3 -1
  27. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +2 -1
  28. data/lib/temporalio/internal/bridge/testing.rb +4 -2
  29. data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +2 -2
  30. data/lib/temporalio/internal/worker/workflow_instance/context.rb +35 -24
  31. data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +6 -4
  32. data/lib/temporalio/internal/worker/workflow_instance/nexus_client.rb +3 -2
  33. data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +52 -19
  34. data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +8 -2
  35. data/lib/temporalio/internal/worker/workflow_instance.rb +47 -26
  36. data/lib/temporalio/internal/worker/workflow_worker.rb +1 -2
  37. data/lib/temporalio/search_attributes.rb +4 -2
  38. data/lib/temporalio/testing/workflow_environment.rb +10 -2
  39. data/lib/temporalio/version.rb +1 -1
  40. data/lib/temporalio/worker/interceptor.rb +16 -8
  41. data/lib/temporalio/worker/workflow_replayer.rb +1 -1
  42. data/lib/temporalio/worker.rb +19 -12
  43. data/lib/temporalio/workflow/child_workflow_handle.rb +4 -1
  44. data/lib/temporalio/workflow/event_group.rb +157 -0
  45. data/lib/temporalio/workflow/external_workflow_handle.rb +9 -2
  46. data/lib/temporalio/workflow/future.rb +1 -1
  47. data/lib/temporalio/workflow/info.rb +4 -0
  48. data/lib/temporalio/workflow/nexus_client.rb +11 -3
  49. data/lib/temporalio/workflow.rb +114 -22
  50. data/rbi/temporalio/api/activity/v1/message.rbi +71 -2
  51. data/rbi/temporalio/api/common/v1/message.rbi +95 -0
  52. data/rbi/temporalio/api/schedule/v1/message.rbi +44 -4
  53. data/rbi/temporalio/api/taskqueue/v1/message.rbi +67 -0
  54. data/rbi/temporalio/api/worker/v1/message.rbi +22 -2
  55. data/rbi/temporalio/api/workflowservice/v1/request_response.rbi +126 -12
  56. data/rbi/temporalio/contrib/aws/s3_driver/aws_sdk_s3_storage_driver_client.rbi +8 -0
  57. data/rbi/temporalio/contrib/aws/s3_driver/s3_storage_driver.rbi +27 -0
  58. data/rbi/temporalio/contrib/aws/s3_driver/s3_storage_driver_client.rbi +17 -0
  59. data/rbi/temporalio/contrib/aws/s3_driver.rbi +5 -0
  60. data/rbi/temporalio/env_config.rbi +39 -4
  61. data/rbi/temporalio/internal/bridge/api/activity_result/activity_result.rbi +0 -19
  62. data/rbi/temporalio/internal/bridge/api/activity_task/activity_task.rbi +213 -2
  63. data/rbi/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rbi +50 -4
  64. data/rbi/temporalio/internal/bridge/testing.rbi +17 -4
  65. data/rbi/temporalio/internal/bridge/worker.rbi +4 -4
  66. data/rbi/temporalio/internal/worker/workflow_instance/context.rbi +33 -24
  67. data/rbi/temporalio/internal/worker/workflow_instance/nexus_client.rbi +4 -2
  68. data/rbi/temporalio/internal/worker/workflow_instance/outbound_implementation.rbi +17 -6
  69. data/rbi/temporalio/internal/worker/workflow_instance/scheduler.rbi +1 -1
  70. data/rbi/temporalio/internal/worker/workflow_instance.rbi +5 -2
  71. data/rbi/temporalio/testing/workflow_environment.rbi +4 -0
  72. data/rbi/temporalio/worker/interceptor.rbi +24 -0
  73. data/rbi/temporalio/worker.rbi +8 -8
  74. data/rbi/temporalio/workflow/child_workflow_handle.rbi +3 -2
  75. data/rbi/temporalio/workflow/event_group.rbi +104 -0
  76. data/rbi/temporalio/workflow/external_workflow_handle.rbi +5 -4
  77. data/rbi/temporalio/workflow/info.rbi +3 -0
  78. data/rbi/temporalio/workflow/nexus_client.rbi +8 -4
  79. data/rbi/temporalio/workflow.rbi +70 -21
  80. data/sig/temporalio/api/activity/v1/message.rbs +31 -0
  81. data/sig/temporalio/api/common/v1/message.rbs +35 -0
  82. data/sig/temporalio/api/schedule/v1/message.rbs +20 -0
  83. data/sig/temporalio/api/taskqueue/v1/message.rbs +24 -0
  84. data/sig/temporalio/api/worker/v1/message.rbs +10 -0
  85. data/sig/temporalio/api/workflowservice/v1/request_response.rbs +58 -0
  86. data/sig/temporalio/contrib/aws/s3_driver/aws_sdk_s3_storage_driver_client.rbs +13 -0
  87. data/sig/temporalio/contrib/aws/s3_driver/s3_storage_driver.rbs +68 -0
  88. data/sig/temporalio/contrib/aws/s3_driver/s3_storage_driver_client.rbs +14 -0
  89. data/sig/temporalio/contrib/aws/s3_driver.rbs +8 -0
  90. data/sig/temporalio/env_config.rbs +20 -2
  91. data/sig/temporalio/internal/bridge/api/activity_result/activity_result.rbs +6 -41
  92. data/sig/temporalio/internal/bridge/api/activity_task/activity_task.rbs +99 -0
  93. data/sig/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rbs +22 -0
  94. data/sig/temporalio/internal/bridge/testing.rbs +7 -3
  95. data/sig/temporalio/internal/bridge/worker.rbs +4 -4
  96. data/sig/temporalio/internal/worker/workflow_instance/context.rbs +19 -13
  97. data/sig/temporalio/internal/worker/workflow_instance/nexus_client.rbs +2 -1
  98. data/sig/temporalio/internal/worker/workflow_instance/outbound_implementation.rbs +8 -5
  99. data/sig/temporalio/internal/worker/workflow_instance/scheduler.rbs +1 -1
  100. data/sig/temporalio/internal/worker/workflow_instance.rbs +2 -1
  101. data/sig/temporalio/testing/workflow_environment.rbs +8 -4
  102. data/sig/temporalio/worker/interceptor.rbs +24 -8
  103. data/sig/temporalio/worker.rbs +12 -12
  104. data/sig/temporalio/workflow/child_workflow_handle.rbs +2 -1
  105. data/sig/temporalio/workflow/event_group.rbs +37 -0
  106. data/sig/temporalio/workflow/external_workflow_handle.rbs +3 -2
  107. data/sig/temporalio/workflow/info.rbs +2 -0
  108. data/sig/temporalio/workflow/nexus_client.rbs +4 -2
  109. data/sig/temporalio/workflow.rbs +27 -11
  110. metadata +17 -2
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'temporalio/api'
5
+ require 'temporalio/cancellation'
6
+ require 'temporalio/contrib/aws/s3_driver/s3_storage_driver_client'
7
+ require 'temporalio/converters/storage_driver'
8
+ require 'temporalio/converters/storage_driver_activity_info'
9
+ require 'temporalio/converters/storage_driver_claim'
10
+ require 'temporalio/converters/storage_driver_workflow_info'
11
+
12
+ module Temporalio
13
+ module Contrib
14
+ module Aws
15
+ module S3Driver
16
+ # Storage driver that stores and retrieves payloads in Amazon S3.
17
+ #
18
+ # Payloads are content addressed by a SHA-256 hash of their serialized bytes, so storing the same payload twice
19
+ # for the same execution uploads it once. Object keys are built from the namespace and the workflow or activity
20
+ # the payload is stored for, percent-encoded to S3's safe character set.
21
+ #
22
+ # @note WARNING: This API is experimental and may change in the future.
23
+ #
24
+ # @!visibility private
25
+ class S3StorageDriver < Converters::StorageDriver
26
+ # Driver type shared by every S3 driver instance across SDK languages.
27
+ DRIVER_TYPE = 'aws.s3driver'
28
+
29
+ # Default maximum serialized payload size, in bytes.
30
+ DEFAULT_MAX_PAYLOAD_SIZE = 50 * 1024 * 1024
31
+
32
+ CLAIM_BUCKET = 'bucket'
33
+ CLAIM_KEY = 'key'
34
+ CLAIM_HASH_ALGORITHM = 'hash_algorithm'
35
+ CLAIM_HASH_VALUE = 'hash_value'
36
+ NULL_SEGMENT = 'null'
37
+ private_constant :CLAIM_BUCKET, :CLAIM_KEY, :CLAIM_HASH_ALGORITHM, :CLAIM_HASH_VALUE, :NULL_SEGMENT
38
+
39
+ StoredObject = Data.define(:payload, :bucket, :key, :hash_value, :data)
40
+ private_constant :StoredObject
41
+
42
+ # @return [String] Driver instance name.
43
+ attr_reader :name
44
+
45
+ # Create an S3 storage driver.
46
+ #
47
+ # @param client [S3StorageDriverClient] Client that performs the S3 requests, for example an
48
+ # {AwsSdkS3StorageDriverClient}.
49
+ # @param bucket [String, Proc] Bucket name, or a proc called with the {Converters::StorageDriverStoreContext}
50
+ # and the payload that returns the bucket name for that payload.
51
+ # @param driver_name [String] Driver instance name written into history. Only change this when registering
52
+ # multiple S3 drivers with different configurations on the same external storage.
53
+ # @param max_payload_size [Integer] Maximum serialized payload size, in bytes. Storing a larger payload
54
+ # raises.
55
+ def initialize(client:, bucket:, driver_name: DRIVER_TYPE, max_payload_size: DEFAULT_MAX_PAYLOAD_SIZE)
56
+ super()
57
+ unless max_payload_size.is_a?(Integer) && max_payload_size.positive?
58
+ raise ArgumentError, "max_payload_size must be a positive integer, got #{max_payload_size.inspect}"
59
+ end
60
+
61
+ @client = client
62
+ @bucket = bucket.is_a?(String) ? proc { bucket } : bucket
63
+ @name = driver_name
64
+ @max_payload_size = max_payload_size
65
+ end
66
+
67
+ # @return [String] Always {DRIVER_TYPE}.
68
+ def type
69
+ DRIVER_TYPE
70
+ end
71
+
72
+ # (see Converters::StorageDriver#store)
73
+ def store(context, payloads)
74
+ context_segments = context_key_segments(context.target)
75
+ objects = payloads.map { |payload| stored_object(context, payload, context_segments) }
76
+ uploads = objects.uniq { |object| [object.bucket, object.key] }
77
+ run_concurrently(uploads, context.cancellation) do |object, cancellation|
78
+ context.limiter.permit(object.payload) do
79
+ cancellation.check!
80
+ upload_if_absent(object)
81
+ end
82
+ end
83
+ objects.map do |object|
84
+ Converters::StorageDriverClaim.new(
85
+ claim_data: {
86
+ CLAIM_BUCKET => object.bucket,
87
+ CLAIM_KEY => object.key,
88
+ CLAIM_HASH_ALGORITHM => 'sha256',
89
+ CLAIM_HASH_VALUE => object.hash_value
90
+ }
91
+ )
92
+ end
93
+ end
94
+
95
+ # (see Converters::StorageDriver#retrieve)
96
+ def retrieve(context, claims)
97
+ claims.each { |claim| validate_claim(claim) }
98
+ run_concurrently(claims.to_a, context.cancellation) do |claim, cancellation|
99
+ bucket = claim.claim_data.fetch(CLAIM_BUCKET)
100
+ key = claim.claim_data.fetch(CLAIM_KEY)
101
+ expected_hash = claim.claim_data.fetch(CLAIM_HASH_VALUE)
102
+ data = context.limiter.permit(claim) do
103
+ cancellation.check!
104
+ download(bucket, key)
105
+ end
106
+ actual_hash = Digest::SHA256.hexdigest(data)
107
+ if actual_hash != expected_hash
108
+ raise "S3StorageDriver integrity check failed [bucket=#{bucket}, key=#{key}]: " \
109
+ "expected sha256:#{expected_hash}, got sha256:#{actual_hash}"
110
+ end
111
+ Api::Common::V1::Payload.decode(data)
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ def stored_object(context, payload, context_segments)
118
+ bucket = @bucket.call(context, payload)
119
+ data = payload.to_proto
120
+ if data.bytesize > @max_payload_size
121
+ raise ArgumentError, "Payload size #{data.bytesize} bytes exceeds the configured max_payload_size " \
122
+ "of #{@max_payload_size} bytes"
123
+ end
124
+ hash_value = Digest::SHA256.hexdigest(data)
125
+ StoredObject.new(payload:, bucket:, key: "v0#{context_segments}/d/sha256/#{hash_value}", hash_value:, data:)
126
+ end
127
+
128
+ def upload_if_absent(object)
129
+ return if @client.object_exists?(bucket: object.bucket, key: object.key)
130
+
131
+ @client.put_object(bucket: object.bucket, key: object.key, data: object.data)
132
+ rescue StandardError
133
+ raise "S3StorageDriver store failed [bucket=#{object.bucket}, key=#{object.key}#{client_context}]"
134
+ end
135
+
136
+ def download(bucket, key)
137
+ @client.get_object(bucket:, key:)
138
+ rescue StandardError
139
+ raise "S3StorageDriver retrieve failed [bucket=#{bucket}, key=#{key}#{client_context}]"
140
+ end
141
+
142
+ def validate_claim(claim)
143
+ bucket, key, hash_algorithm, hash_value =
144
+ claim.claim_data.values_at(CLAIM_BUCKET, CLAIM_KEY, CLAIM_HASH_ALGORITHM, CLAIM_HASH_VALUE)
145
+ if bucket.nil? || bucket.empty? || key.nil? || key.empty?
146
+ raise ArgumentError, 'S3StorageDriver claim is missing required location information: ' \
147
+ "claim_data must contain 'bucket' and 'key'"
148
+ end
149
+ if hash_algorithm.nil? || hash_algorithm.empty? || hash_value.nil? || hash_value.empty?
150
+ raise ArgumentError, 'S3StorageDriver claim is missing required content hash information ' \
151
+ "[bucket=#{bucket}, key=#{key}]: claim_data must contain 'hash_algorithm' and " \
152
+ "'hash_value'"
153
+ end
154
+ return if hash_algorithm == 'sha256'
155
+
156
+ raise ArgumentError, "S3StorageDriver unsupported hash algorithm [bucket=#{bucket}, key=#{key}]: " \
157
+ "expected sha256, got #{hash_algorithm}"
158
+ end
159
+
160
+ # Each request blocks on I/O, so every item gets its own thread. The first failure cancels the rest so they
161
+ # stop before making further requests. Requests already in flight run to completion, since a blocking S3
162
+ # call cannot be safely interrupted.
163
+ def run_concurrently(items, parent_cancellation, &block)
164
+ cancellation, cancel_proc = Cancellation.new(parent_cancellation)
165
+ mutex = Mutex.new
166
+ first_error = nil #: StandardError?
167
+ threads = items.map do |item|
168
+ Thread.new do
169
+ block.call(item, cancellation)
170
+ rescue StandardError => e
171
+ mutex.synchronize { first_error ||= e }
172
+ cancel_proc.call
173
+ nil
174
+ end
175
+ end
176
+ results = threads.map(&:value)
177
+ raise first_error if first_error
178
+
179
+ results
180
+ end
181
+
182
+ def context_key_segments(target)
183
+ case target
184
+ when Converters::StorageDriverWorkflowInfo
185
+ "/ns/#{encode_key_segment(target.namespace)}/wt/#{encode_key_segment(target.type)}" \
186
+ "/wi/#{encode_key_segment(target.id)}/ri/#{encode_key_segment(target.run_id)}"
187
+ when Converters::StorageDriverActivityInfo
188
+ "/ns/#{encode_key_segment(target.namespace)}/at/#{encode_key_segment(target.type)}" \
189
+ "/ai/#{encode_key_segment(target.id)}/ri/#{encode_key_segment(target.run_id)}"
190
+ else
191
+ ''
192
+ end
193
+ end
194
+
195
+ # Escapes every UTF-8 byte outside S3's safe character set so keys are portable across S3 implementations
196
+ # and identical to the keys other SDK languages build.
197
+ def encode_key_segment(value)
198
+ return NULL_SEGMENT if value.nil? || value.empty?
199
+
200
+ encoded = value.b.gsub(/[^A-Za-z0-9!\-_.*'()]/n) { |byte| format('%%%02X', byte.ord) }
201
+ encoded.force_encoding(Encoding::UTF_8)
202
+ end
203
+
204
+ def client_context
205
+ @client.describe.map { |key, value| ", #{key}=#{value}" }.join
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Temporalio
4
+ module Contrib
5
+ module Aws
6
+ module S3Driver
7
+ # S3 object operations used by {S3StorageDriver}.
8
+ #
9
+ # This is kept separate from any particular S3 library so the driver does not depend on one. Use
10
+ # {AwsSdkS3StorageDriverClient} for the official +aws-sdk-s3+ gem, or subclass this to bring another client.
11
+ #
12
+ # Implementations are called concurrently from multiple threads and must be thread safe.
13
+ #
14
+ # @note WARNING: This API is experimental and may change in the future.
15
+ #
16
+ # @!visibility private
17
+ class S3StorageDriverClient
18
+ # Upload data to the given bucket and key.
19
+ #
20
+ # @param bucket [String] Bucket name.
21
+ # @param key [String] Object key.
22
+ # @param data [String] Binary data to upload.
23
+ def put_object(bucket:, key:, data:)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ # Check whether an object exists at the given bucket and key.
28
+ #
29
+ # @param bucket [String] Bucket name.
30
+ # @param key [String] Object key.
31
+ # @return [Boolean] Whether the object exists.
32
+ def object_exists?(bucket:, key:)
33
+ raise NotImplementedError
34
+ end
35
+
36
+ # Download the object at the given bucket and key.
37
+ #
38
+ # @param bucket [String] Bucket name.
39
+ # @param key [String] Object key.
40
+ # @return [String] Binary object data.
41
+ def get_object(bucket:, key:)
42
+ raise NotImplementedError
43
+ end
44
+
45
+ # Client-specific diagnostic information, such as the region, that the driver appends to error messages to
46
+ # help diagnose misconfiguration.
47
+ #
48
+ # @return [Hash<String, String>] Diagnostic information. Empty by default.
49
+ def describe
50
+ {}
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/contrib/aws/s3_driver/s3_storage_driver'
4
+ require 'temporalio/contrib/aws/s3_driver/s3_storage_driver_client'
5
+
6
+ module Temporalio
7
+ module Contrib
8
+ # Integrations with Amazon Web Services.
9
+ module Aws
10
+ # External storage driver for Amazon S3.
11
+ #
12
+ # This does not load the +aws-sdk-s3+ gem, so a custom {S3StorageDriverClient} can be used without it. Require
13
+ # +temporalio/contrib/aws/s3_driver/aws_sdk_s3_storage_driver_client+ to use the official AWS client.
14
+ #
15
+ # @note WARNING: This API is experimental and may change in the future.
16
+ #
17
+ # @!visibility private
18
+ module S3Driver
19
+ end
20
+ end
21
+ end
22
+ end
@@ -549,7 +549,7 @@ module Temporalio
549
549
  return nil
550
550
  end
551
551
 
552
- span = root.tracer.start_span(name, attributes:, links:, start_timestamp: time, kind:) # steep:ignore
552
+ span = root.tracer.start_span(name, attributes:, links:, start_timestamp: time, kind:)
553
553
  # Record exception and set status if present
554
554
  if exception
555
555
  span.record_exception(exception)
@@ -14,11 +14,11 @@ module Temporalio
14
14
  #
15
15
  # @!attribute [r] max_driver_operations
16
16
  # @return [Integer] Maximum requests in flight at once across every driver registered on the owning
17
- # {ExternalStorage}. Each worker and each client gets its own budget of this size.
17
+ # {ExternalStorage}. Each worker and each client gets its own concurrency limit of this size.
18
18
  # @!attribute [r] max_operations_per_message
19
19
  # @return [Integer] Maximum requests in flight at once on behalf of a single message, such as one workflow task
20
- # activation, activity task, Nexus operation, or client request. Every message gets its own budget of this
21
- # size, which stops a message carrying many large payloads from starving the rest.
20
+ # activation, activity task, Nexus operation, or client request. Every message gets its own concurrency
21
+ # limit of this size, which stops a message carrying many large payloads from starving the rest.
22
22
  #
23
23
  # @!visibility private
24
24
  class ExternalStorageConcurrency
@@ -28,7 +28,7 @@ module Temporalio
28
28
  # Shared with every other SDK.
29
29
  DEFAULT_MAX_OPERATIONS_PER_MESSAGE = 8
30
30
 
31
- # Create concurrency limits.
31
+ # Create concurrency limits. Each must be at least 1.
32
32
  #
33
33
  # @param max_driver_operations [Integer] Maximum requests in flight across the configuration.
34
34
  # @param max_operations_per_message [Integer] Maximum requests in flight for one message.
@@ -36,8 +36,6 @@ module Temporalio
36
36
  max_driver_operations: DEFAULT_MAX_DRIVER_OPERATIONS,
37
37
  max_operations_per_message: DEFAULT_MAX_OPERATIONS_PER_MESSAGE
38
38
  )
39
- # A budget of zero would stop every storage operation rather than disabling the limit, so it is rejected
40
- # here instead of deadlocking the first offload.
41
39
  unless max_driver_operations.is_a?(Integer) && max_driver_operations >= 1
42
40
  raise ArgumentError, 'max_driver_operations must be an Integer of at least 1'
43
41
  end
@@ -24,7 +24,7 @@ module Temporalio
24
24
  Api::Common::V1::Payload.new(
25
25
  metadata: {
26
26
  'encoding' => ENCODING,
27
- 'messageType' => value.class.descriptor.name # steep:ignore NoMethod
27
+ 'messageType' => value.class.descriptor.name
28
28
  },
29
29
  data: value.to_proto
30
30
  )
@@ -23,7 +23,7 @@ module Temporalio
23
23
  Api::Common::V1::Payload.new(
24
24
  metadata: {
25
25
  'encoding' => ENCODING,
26
- 'messageType' => value.class.descriptor.name # steep:ignore NoMethod
26
+ 'messageType' => value.class.descriptor.name
27
27
  },
28
28
  data: value.to_json.b
29
29
  )
@@ -6,9 +6,8 @@ module Temporalio
6
6
  # {StorageDriverStoreContext} and {StorageDriverRetrieveContext}; a driver calls it rather than
7
7
  # implementing it.
8
8
  #
9
- # A driver is handed a whole batch and decides for itself how many requests to make of it, so the SDK cannot
10
- # bound that fan out on the driver's behalf. The limit is cooperative: a driver that never calls {#permit} makes
11
- # requests that no budget counts, and the SDK warns when it detects that.
9
+ # The SDK cannot see the requests a driver makes, so the limit works only if the driver takes a permit for
10
+ # each request.
12
11
  #
13
12
  # @note WARNING: This API is experimental and may change in the future.
14
13
  #
@@ -17,12 +16,11 @@ module Temporalio
17
16
  # Hold a permit for the duration of the given block, waiting for one to be available before the block runs
18
17
  # and releasing it when the block returns or raises.
19
18
  #
20
- # Take one permit per request, not one around a whole batch: wrapping a whole fan out in a single permit
21
- # satisfies this signature while quietly defeating the limit. Taking a permit while already holding one can
22
- # deadlock once the nesting reaches the configured limit, so a driver must not nest these.
19
+ # Wrapping a whole fan out in a single permit satisfies this signature while quietly defeating the limit.
20
+ # Do not take more than one permit for a given request: at the limit, every holder would be waiting for a
21
+ # permit only another holder can release.
23
22
  #
24
- # Waiting for a permit observes the cancellation on the context this limiter came from, so a driver that is
25
- # waiting when the SDK abandons the operation stops waiting.
23
+ # Waiting for a permit observes the cancellation on the context this limiter came from.
26
24
  #
27
25
  # @param item [Api::Common::V1::Payload, StorageDriverClaim] What this one request covers: a payload when
28
26
  # storing, a claim when retrieving.
@@ -21,8 +21,7 @@ module Temporalio
21
21
  # @!attribute [r] cancellation
22
22
  # @return [Cancellation] Cancelled when the SDK abandons this retrieve operation.
23
23
  # @!attribute [r] limiter
24
- # @return [StorageDriverLimiter] Bounds how many retrieve requests this driver may have in flight. Take one
25
- # permit per request.
24
+ # @return [StorageDriverLimiter] Limiter for this driver's retrieve requests.
26
25
  #
27
26
  # @!visibility private
28
27
  class StorageDriverRetrieveContext; end # rubocop:disable Lint/EmptyClass
@@ -22,8 +22,7 @@ module Temporalio
22
22
  # @!attribute [r] cancellation
23
23
  # @return [Cancellation] Cancelled when the SDK abandons this store operation.
24
24
  # @!attribute [r] limiter
25
- # @return [StorageDriverLimiter] Bounds how many store requests this driver may have in flight. Take one permit
26
- # per request.
25
+ # @return [StorageDriverLimiter] Limiter for this driver's store requests.
27
26
  #
28
27
  # @!visibility private
29
28
  class StorageDriverStoreContext; end # rubocop:disable Lint/EmptyClass
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pathname'
4
+ require 'temporalio/error'
4
5
  require 'temporalio/internal/bridge'
5
6
 
6
7
  module Temporalio
@@ -136,7 +137,12 @@ module Temporalio
136
137
  # @return [Boolean, ClientConfigTLS, nil] TLS configuration
137
138
  # @!attribute [r] grpc_meta
138
139
  # @return [Hash] gRPC metadata
139
- ClientConfigProfile = Data.define(:address, :namespace, :api_key, :tls, :grpc_meta)
140
+ # @!attribute [r] ext
141
+ # @return [Hash<String, Hash<String, Object>>] Raw extension config from +profile.<profile>.ext+, keyed by
142
+ # extension name. Each extension decodes its own table. TOML datetimes are ISO 8601 strings. Not modified by
143
+ # environment variables. This should be treated as read-only, since copies of a
144
+ # profile may share it. WARNING: Extensions are experimental.
145
+ ClientConfigProfile = Data.define(:address, :namespace, :api_key, :tls, :grpc_meta, :ext)
140
146
 
141
147
  # A client configuration profile loaded from environment and files.
142
148
  #
@@ -153,7 +159,8 @@ module Temporalio
153
159
  namespace: hash[:namespace],
154
160
  api_key: hash[:api_key],
155
161
  tls: ClientConfigTLS.from_h(hash[:tls]),
156
- grpc_meta: hash[:grpc_meta] || {}
162
+ grpc_meta: hash[:grpc_meta] || {},
163
+ ext: hash[:ext] || {}
157
164
  )
158
165
  end
159
166
 
@@ -185,7 +192,7 @@ module Temporalio
185
192
  end
186
193
 
187
194
  # Create a ClientConfigProfile instance with defaults
188
- def initialize(address: nil, namespace: nil, api_key: nil, tls: nil, grpc_meta: {})
195
+ def initialize(address: nil, namespace: nil, api_key: nil, tls: nil, grpc_meta: {}, ext: {})
189
196
  super
190
197
  end
191
198
 
@@ -196,8 +203,9 @@ module Temporalio
196
203
  address: address,
197
204
  namespace: namespace,
198
205
  api_key: api_key,
199
- tls: tls&.to_h&.then { |tls_hash| tls_hash.empty? ? nil : tls_hash }, # steep:ignore
200
- grpc_meta: grpc_meta && grpc_meta.empty? ? nil : grpc_meta
206
+ tls: tls&.to_h&.then { |tls_hash| tls_hash.empty? ? nil : tls_hash },
207
+ grpc_meta: grpc_meta && grpc_meta.empty? ? nil : grpc_meta,
208
+ ext: ext && ext.empty? ? nil : ext
201
209
  }.compact
202
210
  end
203
211
 
@@ -222,6 +230,46 @@ module Temporalio
222
230
  end
223
231
  end
224
232
 
233
+ # Extension mixin to include for extending client configuration with extra TOML properties and environment
234
+ # variables, and applying them to the client created from the configuration.
235
+ #
236
+ # Each extension owns a TOML table under +profile.<profile>.ext.<name>+, e.g.:
237
+ #
238
+ # [profile.default.ext.my_extension]
239
+ # some_field = "value"
240
+ #
241
+ # Extensions are recommended to use a +TEMPORAL_EXT_<NAME>_+ prefix for any environment variables they read.
242
+ #
243
+ # WARNING: Extensions are experimental.
244
+ module ClientConfigExtension
245
+ # @abstract
246
+ # @return [String] The extension's table key under +profile.<profile>.ext+. Must be unique among loaded
247
+ # extensions.
248
+ def name
249
+ raise NotImplementedError
250
+ end
251
+
252
+ # Read this extension's config from the profile and env, and return a plugin that applies it.
253
+ #
254
+ # @abstract
255
+ # @param request [ClientConfigExtensionRequest] Loaded profile and env.
256
+ # @return [Client::Plugin, nil] Plugin, or nil if there's nothing to apply.
257
+ def client_plugin(request)
258
+ raise NotImplementedError
259
+ end
260
+ end
261
+
262
+ # Input to {ClientConfigExtension#client_plugin}.
263
+ #
264
+ # WARNING: Extensions are experimental.
265
+ #
266
+ # @!attribute [r] profile
267
+ # @return [ClientConfigProfile] Loaded profile, with environment variable overrides applied.
268
+ # @!attribute [r] env
269
+ # @return [Hash<String, String>, nil] Environment variables to read extension env vars from, or nil if
270
+ # environment variable loading is disabled.
271
+ ClientConfigExtensionRequest = Data.define(:profile, :env)
272
+
225
273
  # Client configuration loaded from TOML and environment variables.
226
274
  #
227
275
  # This contains a mapping of profile names to client profiles.
@@ -273,7 +321,7 @@ module Temporalio
273
321
  # Load a single client profile and convert to connect config
274
322
  #
275
323
  # This is a convenience function that combines loading a profile and
276
- # converting it to a connect config hash.
324
+ # converting it to a connect config hash, plus applying extensions.
277
325
  #
278
326
  # @param profile [String, nil] The profile to load from the config
279
327
  # @param config_source [Pathname, String, nil] Configuration source
@@ -281,6 +329,8 @@ module Temporalio
281
329
  # @param disable_env [Boolean] If true, environment variable loading and overriding is disabled
282
330
  # @param config_file_strict [Boolean] If true, will error on unrecognized keys
283
331
  # @param override_env_vars [Hash, nil] Environment variables to use for loading and overrides
332
+ # @param extensions [Array<ClientConfigExtension>] Extensions to call, in order, with the loaded profile. Plugins
333
+ # they return are included as +plugins+ in the keyword args. WARNING: Extensions are experimental.
284
334
  # @return [Array] Tuple of [positional_args, keyword_args] that can be splatted to Client.connect
285
335
  def self.load_client_connect_options(
286
336
  profile: nil,
@@ -288,8 +338,10 @@ module Temporalio
288
338
  disable_file: false,
289
339
  disable_env: false,
290
340
  config_file_strict: false,
291
- override_env_vars: nil
341
+ override_env_vars: nil,
342
+ extensions: []
292
343
  )
344
+ _validate_extensions!(extensions)
293
345
  prof = ClientConfigProfile.load(
294
346
  profile: profile,
295
347
  config_source: config_source,
@@ -298,7 +350,36 @@ module Temporalio
298
350
  config_file_strict: config_file_strict,
299
351
  override_env_vars: override_env_vars
300
352
  )
301
- prof.to_client_connect_options
353
+ positional_args, keyword_args = prof.to_client_connect_options
354
+ return [positional_args, keyword_args] if extensions.empty?
355
+
356
+ request = ClientConfigExtensionRequest.new(
357
+ profile: prof,
358
+ env: disable_env ? nil : override_env_vars || ENV.to_h
359
+ )
360
+ plugins = extensions.filter_map do |extension|
361
+ extension.client_plugin(request)
362
+ rescue StandardError => e
363
+ raise Error, "client config extension \"#{extension.name}\": #{e.message}"
364
+ end
365
+ keyword_args[:plugins] = plugins unless plugins.empty?
366
+ [positional_args, keyword_args]
367
+ end
368
+
369
+ # @!visibility private
370
+ def self._validate_extensions!(extensions)
371
+ names = {}
372
+ extensions.each_with_index do |extension, index|
373
+ unless extension.is_a?(ClientConfigExtension)
374
+ raise ArgumentError, "#{extension.class} does not implement EnvConfig::ClientConfigExtension"
375
+ end
376
+
377
+ name = extension.name
378
+ raise ArgumentError, "client config extension at index #{index} has empty name" if name.empty?
379
+ raise ArgumentError, "duplicate client config extension name \"#{name}\"" if names.key?(name)
380
+
381
+ names[name] = true
382
+ end
302
383
  end
303
384
 
304
385
  # Create a ClientConfig instance with defaults
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so
3
- size: 60194616 bytes
4
- sha256: 3dfa451ca0923589581e70f1a453071a425a86a48cf4396975ce4c91cbaf5cde
3
+ size: 60792376 bytes
4
+ sha256: 64e0b93ba1b349c4ff16e698d85dfa739ef59bd3ede9ffe88c59876f4310a975
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/lib/temporalio/internal/bridge/3.4/temporalio_bridge.so
3
- size: 60226176 bytes
4
- sha256: c49918bce855ba71cbd0fbfcda4e2a349557121bf893259e940a4e982765b388
3
+ size: 60765352 bytes
4
+ sha256: 110fbbb2611d8ecb7b4d52920f7f731982d798aade656b3a7d002a274f0ac779
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/lib/temporalio/internal/bridge/4.0/temporalio_bridge.so
3
- size: 60224768 bytes
4
- sha256: 0db3ed99c0bcf8a52171f6c0a9c523bdc0adbbb2c7e4f0ea376db5b074c6206c
3
+ size: 60747264 bytes
4
+ sha256: c241ac97006c83bc01a8b7159ca346305f332c1f34416b866208ec8dad74dac6
@@ -7,10 +7,11 @@ require 'google/protobuf'
7
7
  require 'google/protobuf/duration_pb'
8
8
  require 'google/protobuf/timestamp_pb'
9
9
  require 'temporalio/api/common/v1/message'
10
+ require 'temporalio/api/enums/v1/failed_cause'
10
11
  require 'temporalio/api/failure/v1/message'
11
12
 
12
13
 
13
- descriptor_data = "\n7temporal/sdk/core/activity_result/activity_result.proto\x12\x17\x63oresdk.activity_result\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$temporal/api/common/v1/message.proto\x1a%temporal/api/failure/v1/message.proto\"\x95\x02\n\x17\x41\x63tivityExecutionResult\x12\x35\n\tcompleted\x18\x01 \x01(\x0b\x32 .coresdk.activity_result.SuccessH\x00\x12\x32\n\x06\x66\x61iled\x18\x02 \x01(\x0b\x32 .coresdk.activity_result.FailureH\x00\x12:\n\tcancelled\x18\x03 \x01(\x0b\x32%.coresdk.activity_result.CancellationH\x00\x12I\n\x13will_complete_async\x18\x04 \x01(\x0b\x32*.coresdk.activity_result.WillCompleteAsyncH\x00\x42\x08\n\x06status\"\xfc\x01\n\x12\x41\x63tivityResolution\x12\x35\n\tcompleted\x18\x01 \x01(\x0b\x32 .coresdk.activity_result.SuccessH\x00\x12\x32\n\x06\x66\x61iled\x18\x02 \x01(\x0b\x32 .coresdk.activity_result.FailureH\x00\x12:\n\tcancelled\x18\x03 \x01(\x0b\x32%.coresdk.activity_result.CancellationH\x00\x12\x35\n\x07\x62\x61\x63koff\x18\x04 \x01(\x0b\x32\".coresdk.activity_result.DoBackoffH\x00\x42\x08\n\x06status\":\n\x07Success\x12/\n\x06result\x18\x01 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload\"}\n\x07\x46\x61ilure\x12\x31\n\x07\x66\x61ilure\x18\x01 \x01(\x0b\x32 .temporal.api.failure.v1.Failure\x12?\n\x05\x63\x61use\x18\x02 \x01(\x0e\x32\x30.coresdk.activity_result.ActivityTaskFailedCause\"A\n\x0c\x43\x61ncellation\x12\x31\n\x07\x66\x61ilure\x18\x01 \x01(\x0b\x32 .temporal.api.failure.v1.Failure\"\x13\n\x11WillCompleteAsync\"\x8d\x01\n\tDoBackoff\x12\x0f\n\x07\x61ttempt\x18\x01 \x01(\r\x12\x33\n\x10\x62\x61\x63koff_duration\x18\x02 \x01(\x0b\x32\x19.google.protobuf.Duration\x12:\n\x16original_schedule_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp*\xf3\x01\n\x17\x41\x63tivityTaskFailedCause\x12*\n&ACTIVITY_TASK_FAILED_CAUSE_UNSPECIFIED\x10\x00\x12\x31\n-ACTIVITY_TASK_FAILED_CAUSE_PAYLOADS_TOO_LARGE\x10\x01\x12\x37\n3ACTIVITY_TASK_FAILED_CAUSE_EXTERNAL_STORAGE_FAILURE\x10\x02\x12@\n<ACTIVITY_TASK_FAILED_CAUSE_ACTIVITY_WORKER_UNHANDLED_FAILURE\x10\x03\x42\x34\xea\x02\x31Temporalio::Internal::Bridge::Api::ActivityResultb\x06proto3"
14
+ descriptor_data = "\n7temporal/sdk/core/activity_result/activity_result.proto\x12\x17\x63oresdk.activity_result\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$temporal/api/common/v1/message.proto\x1a(temporal/api/enums/v1/failed_cause.proto\x1a%temporal/api/failure/v1/message.proto\"\x95\x02\n\x17\x41\x63tivityExecutionResult\x12\x35\n\tcompleted\x18\x01 \x01(\x0b\x32 .coresdk.activity_result.SuccessH\x00\x12\x32\n\x06\x66\x61iled\x18\x02 \x01(\x0b\x32 .coresdk.activity_result.FailureH\x00\x12:\n\tcancelled\x18\x03 \x01(\x0b\x32%.coresdk.activity_result.CancellationH\x00\x12I\n\x13will_complete_async\x18\x04 \x01(\x0b\x32*.coresdk.activity_result.WillCompleteAsyncH\x00\x42\x08\n\x06status\"\xfc\x01\n\x12\x41\x63tivityResolution\x12\x35\n\tcompleted\x18\x01 \x01(\x0b\x32 .coresdk.activity_result.SuccessH\x00\x12\x32\n\x06\x66\x61iled\x18\x02 \x01(\x0b\x32 .coresdk.activity_result.FailureH\x00\x12:\n\tcancelled\x18\x03 \x01(\x0b\x32%.coresdk.activity_result.CancellationH\x00\x12\x35\n\x07\x62\x61\x63koff\x18\x04 \x01(\x0b\x32\".coresdk.activity_result.DoBackoffH\x00\x42\x08\n\x06status\":\n\x07Success\x12/\n\x06result\x18\x01 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload\"{\n\x07\x46\x61ilure\x12\x31\n\x07\x66\x61ilure\x18\x01 \x01(\x0b\x32 .temporal.api.failure.v1.Failure\x12=\n\x05\x63\x61use\x18\x02 \x01(\x0e\x32..temporal.api.enums.v1.ActivityTaskFailedCause\"A\n\x0c\x43\x61ncellation\x12\x31\n\x07\x66\x61ilure\x18\x01 \x01(\x0b\x32 .temporal.api.failure.v1.Failure\"\x13\n\x11WillCompleteAsync\"\x8d\x01\n\tDoBackoff\x12\x0f\n\x07\x61ttempt\x18\x01 \x01(\r\x12\x33\n\x10\x62\x61\x63koff_duration\x18\x02 \x01(\x0b\x32\x19.google.protobuf.Duration\x12:\n\x16original_schedule_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampB4\xea\x02\x31Temporalio::Internal::Bridge::Api::ActivityResultb\x06proto3"
14
15
 
15
16
  pool = ::Google::Protobuf::DescriptorPool.generated_pool
16
17
  pool.add_serialized_file(descriptor_data)
@@ -27,7 +28,6 @@ module Temporalio
27
28
  Cancellation = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("coresdk.activity_result.Cancellation").msgclass
28
29
  WillCompleteAsync = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("coresdk.activity_result.WillCompleteAsync").msgclass
29
30
  DoBackoff = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("coresdk.activity_result.DoBackoff").msgclass
30
- ActivityTaskFailedCause = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("coresdk.activity_result.ActivityTaskFailedCause").enummodule
31
31
  end
32
32
  end
33
33
  end