temporalio 0.2.0-x86_64-darwin → 0.3.0-x86_64-darwin
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +2 -0
- data/Gemfile +3 -3
- data/Rakefile +10 -296
- data/lib/temporalio/activity/complete_async_error.rb +1 -1
- data/lib/temporalio/activity/context.rb +5 -2
- data/lib/temporalio/activity/definition.rb +163 -65
- data/lib/temporalio/activity/info.rb +22 -21
- data/lib/temporalio/activity.rb +2 -59
- data/lib/temporalio/api/activity/v1/message.rb +25 -0
- data/lib/temporalio/api/cloud/account/v1/message.rb +28 -0
- data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +34 -1
- data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +1 -1
- data/lib/temporalio/api/cloud/identity/v1/message.rb +6 -1
- data/lib/temporalio/api/cloud/namespace/v1/message.rb +8 -1
- data/lib/temporalio/api/cloud/nexus/v1/message.rb +31 -0
- data/lib/temporalio/api/cloud/operation/v1/message.rb +2 -1
- data/lib/temporalio/api/cloud/region/v1/message.rb +2 -1
- data/lib/temporalio/api/cloud/resource/v1/message.rb +23 -0
- data/lib/temporalio/api/cloud/sink/v1/message.rb +24 -0
- data/lib/temporalio/api/cloud/usage/v1/message.rb +31 -0
- data/lib/temporalio/api/common/v1/message.rb +7 -1
- data/lib/temporalio/api/enums/v1/event_type.rb +1 -1
- data/lib/temporalio/api/enums/v1/failed_cause.rb +1 -1
- data/lib/temporalio/api/enums/v1/reset.rb +1 -1
- data/lib/temporalio/api/history/v1/message.rb +1 -1
- data/lib/temporalio/api/nexus/v1/message.rb +2 -2
- data/lib/temporalio/api/operatorservice/v1/service.rb +1 -1
- data/lib/temporalio/api/payload_visitor.rb +1513 -0
- data/lib/temporalio/api/schedule/v1/message.rb +2 -1
- data/lib/temporalio/api/testservice/v1/request_response.rb +31 -0
- data/lib/temporalio/api/testservice/v1/service.rb +23 -0
- data/lib/temporalio/api/workflow/v1/message.rb +1 -1
- data/lib/temporalio/api/workflowservice/v1/request_response.rb +17 -2
- data/lib/temporalio/api/workflowservice/v1/service.rb +1 -1
- data/lib/temporalio/api.rb +1 -0
- data/lib/temporalio/cancellation.rb +34 -14
- data/lib/temporalio/client/async_activity_handle.rb +12 -37
- data/lib/temporalio/client/connection/cloud_service.rb +309 -231
- data/lib/temporalio/client/connection/operator_service.rb +36 -84
- data/lib/temporalio/client/connection/service.rb +6 -5
- data/lib/temporalio/client/connection/test_service.rb +111 -0
- data/lib/temporalio/client/connection/workflow_service.rb +264 -441
- data/lib/temporalio/client/connection.rb +90 -44
- data/lib/temporalio/client/interceptor.rb +160 -60
- data/lib/temporalio/client/schedule.rb +967 -0
- data/lib/temporalio/client/schedule_handle.rb +126 -0
- data/lib/temporalio/client/workflow_execution.rb +7 -10
- data/lib/temporalio/client/workflow_handle.rb +38 -95
- data/lib/temporalio/client/workflow_update_handle.rb +3 -5
- data/lib/temporalio/client.rb +122 -42
- data/lib/temporalio/common_enums.rb +17 -0
- data/lib/temporalio/converters/data_converter.rb +4 -7
- data/lib/temporalio/converters/failure_converter.rb +5 -3
- data/lib/temporalio/converters/payload_converter/composite.rb +4 -0
- data/lib/temporalio/converters/payload_converter.rb +6 -8
- data/lib/temporalio/converters/raw_value.rb +20 -0
- data/lib/temporalio/error/failure.rb +1 -1
- data/lib/temporalio/error.rb +10 -2
- data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/{3.1 → 3.4}/temporalio_bridge.bundle +0 -0
- data/lib/temporalio/internal/bridge/api/core_interface.rb +5 -1
- data/lib/temporalio/internal/bridge/api/nexus/nexus.rb +33 -0
- data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +5 -1
- data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +4 -1
- data/lib/temporalio/internal/bridge/client.rb +11 -6
- data/lib/temporalio/internal/bridge/testing.rb +20 -0
- data/lib/temporalio/internal/bridge/worker.rb +2 -0
- data/lib/temporalio/internal/bridge.rb +1 -1
- data/lib/temporalio/internal/client/implementation.rb +245 -70
- data/lib/temporalio/internal/metric.rb +122 -0
- data/lib/temporalio/internal/proto_utils.rb +86 -7
- data/lib/temporalio/internal/worker/activity_worker.rb +52 -24
- data/lib/temporalio/internal/worker/multi_runner.rb +51 -7
- data/lib/temporalio/internal/worker/workflow_instance/child_workflow_handle.rb +54 -0
- data/lib/temporalio/internal/worker/workflow_instance/context.rb +329 -0
- data/lib/temporalio/internal/worker/workflow_instance/details.rb +44 -0
- data/lib/temporalio/internal/worker/workflow_instance/external_workflow_handle.rb +32 -0
- data/lib/temporalio/internal/worker/workflow_instance/externally_immutable_hash.rb +22 -0
- data/lib/temporalio/internal/worker/workflow_instance/handler_execution.rb +25 -0
- data/lib/temporalio/internal/worker/workflow_instance/handler_hash.rb +41 -0
- data/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb +97 -0
- data/lib/temporalio/internal/worker/workflow_instance/inbound_implementation.rb +62 -0
- data/lib/temporalio/internal/worker/workflow_instance/outbound_implementation.rb +415 -0
- data/lib/temporalio/internal/worker/workflow_instance/replay_safe_logger.rb +37 -0
- data/lib/temporalio/internal/worker/workflow_instance/replay_safe_metric.rb +40 -0
- data/lib/temporalio/internal/worker/workflow_instance/scheduler.rb +163 -0
- data/lib/temporalio/internal/worker/workflow_instance.rb +730 -0
- data/lib/temporalio/internal/worker/workflow_worker.rb +196 -0
- data/lib/temporalio/metric.rb +109 -0
- data/lib/temporalio/retry_policy.rb +37 -14
- data/lib/temporalio/runtime.rb +118 -75
- data/lib/temporalio/search_attributes.rb +80 -37
- data/lib/temporalio/testing/activity_environment.rb +2 -2
- data/lib/temporalio/testing/workflow_environment.rb +251 -5
- data/lib/temporalio/version.rb +1 -1
- data/lib/temporalio/worker/activity_executor/thread_pool.rb +9 -217
- data/lib/temporalio/worker/activity_executor.rb +3 -3
- data/lib/temporalio/worker/interceptor.rb +340 -66
- data/lib/temporalio/worker/thread_pool.rb +237 -0
- data/lib/temporalio/worker/workflow_executor/thread_pool.rb +230 -0
- data/lib/temporalio/worker/workflow_executor.rb +26 -0
- data/lib/temporalio/worker.rb +201 -30
- data/lib/temporalio/workflow/activity_cancellation_type.rb +20 -0
- data/lib/temporalio/workflow/child_workflow_cancellation_type.rb +21 -0
- data/lib/temporalio/workflow/child_workflow_handle.rb +43 -0
- data/lib/temporalio/workflow/definition.rb +566 -0
- data/lib/temporalio/workflow/external_workflow_handle.rb +41 -0
- data/lib/temporalio/workflow/future.rb +151 -0
- data/lib/temporalio/workflow/handler_unfinished_policy.rb +13 -0
- data/lib/temporalio/workflow/info.rb +82 -0
- data/lib/temporalio/workflow/parent_close_policy.rb +19 -0
- data/lib/temporalio/workflow/update_info.rb +20 -0
- data/lib/temporalio/workflow.rb +523 -0
- data/lib/temporalio.rb +4 -0
- data/temporalio.gemspec +2 -2
- metadata +52 -6
| @@ -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  | 
| 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. | 
| 111 | 
            -
                  return nil unless proto
         | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 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. | 
| 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 | 
| 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 | 
            -
                   | 
| 197 | 
            +
                  case key
         | 
| 198 | 
            +
                  when Key
         | 
| 171 199 | 
             
                    @raw_hash[key]
         | 
| 172 | 
            -
                   | 
| 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 | 
| 182 | 
            -
                #   the matching name will be deleted. This means a {Key} with a matching name but different type may be | 
| 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 =  | 
| 216 | 
            +
                  name = case key
         | 
| 217 | 
            +
                         when Key
         | 
| 187 218 | 
             
                           key.name
         | 
| 188 | 
            -
                          | 
| 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.
         | 
| @@ -225,6 +258,7 @@ module Temporalio | |
| 225 258 | 
             
                # @param updates [Update] Updates created via {Key#value_set} or {Key#value_unset}.
         | 
| 226 259 | 
             
                # @return [SearchAttributes] New collection.
         | 
| 227 260 | 
             
                def update(*updates)
         | 
| 261 | 
            +
                  _assert_mutations_enabled
         | 
| 228 262 | 
             
                  attrs = dup
         | 
| 229 263 | 
             
                  attrs.update!(*updates)
         | 
| 230 264 | 
             
                  attrs
         | 
| @@ -234,27 +268,36 @@ module Temporalio | |
| 234 268 | 
             
                #
         | 
| 235 269 | 
             
                # @param updates [Update] Updates created via {Key#value_set} or {Key#value_unset}.
         | 
| 236 270 | 
             
                def update!(*updates)
         | 
| 271 | 
            +
                  _assert_mutations_enabled
         | 
| 237 272 | 
             
                  updates.each do |update|
         | 
| 238 273 | 
             
                    raise ArgumentError, 'Update must be an update' unless update.is_a?(Update)
         | 
| 239 274 |  | 
| 240 | 
            -
                     | 
| 275 | 
            +
                    if update.value.nil?
         | 
| 276 | 
            +
                      delete(update.key)
         | 
| 277 | 
            +
                    else
         | 
| 278 | 
            +
                      self[update.key] = update.value
         | 
| 279 | 
            +
                    end
         | 
| 241 280 | 
             
                  end
         | 
| 242 281 | 
             
                end
         | 
| 243 282 |  | 
| 244 283 | 
             
                # @!visibility private
         | 
| 245 | 
            -
                def  | 
| 246 | 
            -
                  Api::Common::V1::SearchAttributes.new(
         | 
| 247 | 
            -
             | 
| 248 | 
            -
             | 
| 249 | 
            -
             | 
| 284 | 
            +
                def _to_proto
         | 
| 285 | 
            +
                  Api::Common::V1::SearchAttributes.new(indexed_fields: _to_proto_hash)
         | 
| 286 | 
            +
                end
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                # @!visibility private
         | 
| 289 | 
            +
                def _to_proto_hash
         | 
| 290 | 
            +
                  @raw_hash.to_h { |key, value| SearchAttributes._to_proto_pair(key, value) }
         | 
| 291 | 
            +
                end
         | 
| 250 292 |  | 
| 251 | 
            -
             | 
| 252 | 
            -
             | 
| 253 | 
            -
             | 
| 293 | 
            +
                # @!visibility private
         | 
| 294 | 
            +
                def _assert_mutations_enabled
         | 
| 295 | 
            +
                  raise 'Search attribute mutations disabled' if @disable_mutations
         | 
| 296 | 
            +
                end
         | 
| 254 297 |  | 
| 255 | 
            -
             | 
| 256 | 
            -
             | 
| 257 | 
            -
                   | 
| 298 | 
            +
                # @!visibility private
         | 
| 299 | 
            +
                def _disable_mutations=(value)
         | 
| 300 | 
            +
                  @disable_mutations = value
         | 
| 258 301 | 
             
                end
         | 
| 259 302 |  | 
| 260 303 | 
             
                # Type for a search attribute key/value.
         | 
| @@ -67,11 +67,11 @@ module Temporalio | |
| 67 67 |  | 
| 68 68 | 
             
                  # Run an activity and returns its result or raises its exception.
         | 
| 69 69 | 
             
                  #
         | 
| 70 | 
            -
                  # @param activity [Activity, Class<Activity>, Activity::Definition] Activity to run.
         | 
| 70 | 
            +
                  # @param activity [Activity::Definition, Class<Activity::Definition>, Activity::Definition::Info] Activity to run.
         | 
| 71 71 | 
             
                  # @param args [Array<Object>] Arguments to the activity.
         | 
| 72 72 | 
             
                  # @return Activity result.
         | 
| 73 73 | 
             
                  def run(activity, *args)
         | 
| 74 | 
            -
                    defn = Activity::Definition.from_activity(activity)
         | 
| 74 | 
            +
                    defn = Activity::Definition::Info.from_activity(activity)
         | 
| 75 75 | 
             
                    executor = @activity_executors[defn.executor]
         | 
| 76 76 | 
             
                    raise ArgumentError, "Unknown executor: #{defn.executor}" if executor.nil?
         | 
| 77 77 |  | 
| @@ -1,8 +1,14 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            require 'delegate'
         | 
| 4 | 
            +
            require 'temporalio/api'
         | 
| 5 | 
            +
            require 'temporalio/api/testservice/v1/request_response'
         | 
| 3 6 | 
             
            require 'temporalio/client'
         | 
| 7 | 
            +
            require 'temporalio/client/connection/test_service'
         | 
| 8 | 
            +
            require 'temporalio/client/workflow_handle'
         | 
| 4 9 | 
             
            require 'temporalio/converters'
         | 
| 5 10 | 
             
            require 'temporalio/internal/bridge/testing'
         | 
| 11 | 
            +
            require 'temporalio/internal/proto_utils'
         | 
| 6 12 | 
             
            require 'temporalio/runtime'
         | 
| 7 13 | 
             
            require 'temporalio/version'
         | 
| 8 14 |  | 
| @@ -63,7 +69,8 @@ module Temporalio | |
| 63 69 | 
             
                    dev_server_log_level: 'warn',
         | 
| 64 70 | 
             
                    dev_server_download_version: 'default',
         | 
| 65 71 | 
             
                    dev_server_download_dest_dir: nil,
         | 
| 66 | 
            -
                    dev_server_extra_args: []
         | 
| 72 | 
            +
                    dev_server_extra_args: [],
         | 
| 73 | 
            +
                    &
         | 
| 67 74 | 
             
                  )
         | 
| 68 75 | 
             
                    server_options = Internal::Bridge::Testing::EphemeralServer::StartDevServerOptions.new(
         | 
| 69 76 | 
             
                      existing_path: dev_server_existing_path,
         | 
| @@ -80,7 +87,96 @@ module Temporalio | |
| 80 87 | 
             
                      log_level: dev_server_log_level,
         | 
| 81 88 | 
             
                      extra_args: dev_server_extra_args
         | 
| 82 89 | 
             
                    )
         | 
| 83 | 
            -
                     | 
| 90 | 
            +
                    _with_core_server(
         | 
| 91 | 
            +
                      core_server: Internal::Bridge::Testing::EphemeralServer.start_dev_server(
         | 
| 92 | 
            +
                        runtime._core_runtime, server_options
         | 
| 93 | 
            +
                      ),
         | 
| 94 | 
            +
                      namespace:,
         | 
| 95 | 
            +
                      data_converter:,
         | 
| 96 | 
            +
                      interceptors:,
         | 
| 97 | 
            +
                      logger:,
         | 
| 98 | 
            +
                      default_workflow_query_reject_condition:,
         | 
| 99 | 
            +
                      runtime:,
         | 
| 100 | 
            +
                      supports_time_skipping: false,
         | 
| 101 | 
            +
                      & # steep:ignore
         | 
| 102 | 
            +
                    )
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  # Start a time-skipping test server. This server can skip time but may not have all of the Temporal features of
         | 
| 106 | 
            +
                  # the {start_local} form. By default, the server is downloaded to tmp if not already present. The test server is
         | 
| 107 | 
            +
                  # run as a child process. All options that start with +test_server_+ are for this specific implementation and
         | 
| 108 | 
            +
                  # therefore are not stable and may be changed as the underlying implementation changes.
         | 
| 109 | 
            +
                  #
         | 
| 110 | 
            +
                  # If a block is given it is passed the environment and the environment is shut down after. If a block is not
         | 
| 111 | 
            +
                  # given, the environment is returned and {shutdown} needs to be called manually.
         | 
| 112 | 
            +
                  #
         | 
| 113 | 
            +
                  # @param data_converter [Converters::DataConverter] Data converter for the client.
         | 
| 114 | 
            +
                  # @param interceptors [Array<Client::Interceptor>] Interceptors for the client.
         | 
| 115 | 
            +
                  # @param logger [Logger] Logger for the client.
         | 
| 116 | 
            +
                  # @param default_workflow_query_reject_condition [WorkflowQueryRejectCondition, nil] Default rejection condition
         | 
| 117 | 
            +
                  #   for the client.
         | 
| 118 | 
            +
                  # @param port [Integer, nil] Port to bind on, or +nil+ for random.
         | 
| 119 | 
            +
                  # @param runtime [Runtime] Runtime for the server and client.
         | 
| 120 | 
            +
                  # @param test_server_existing_path [String, nil] Existing CLI path to use instead of downloading and caching to
         | 
| 121 | 
            +
                  #   tmp.
         | 
| 122 | 
            +
                  # @param test_server_download_version [String] Version of test server to download and cache.
         | 
| 123 | 
            +
                  # @param test_server_download_dest_dir [String, nil] Where to download. Defaults to tmp.
         | 
| 124 | 
            +
                  # @param test_server_extra_args [Array<String>] Any extra arguments for the test server.
         | 
| 125 | 
            +
                  #
         | 
| 126 | 
            +
                  # @yield [environment] If a block is given, it is called with the environment and upon complete the environment is
         | 
| 127 | 
            +
                  #   shutdown.
         | 
| 128 | 
            +
                  # @yieldparam environment [WorkflowEnvironment] Environment that is shut down upon block completion.
         | 
| 129 | 
            +
                  #
         | 
| 130 | 
            +
                  # @return [WorkflowEnvironment, Object] Started local server environment with client if there was no block given,
         | 
| 131 | 
            +
                  #   or block result if block was given.
         | 
| 132 | 
            +
                  def self.start_time_skipping(
         | 
| 133 | 
            +
                    data_converter: Converters::DataConverter.default,
         | 
| 134 | 
            +
                    interceptors: [],
         | 
| 135 | 
            +
                    logger: Logger.new($stdout, level: Logger::WARN),
         | 
| 136 | 
            +
                    default_workflow_query_reject_condition: nil,
         | 
| 137 | 
            +
                    port: nil,
         | 
| 138 | 
            +
                    runtime: Runtime.default,
         | 
| 139 | 
            +
                    test_server_existing_path: nil,
         | 
| 140 | 
            +
                    test_server_download_version: 'default',
         | 
| 141 | 
            +
                    test_server_download_dest_dir: nil,
         | 
| 142 | 
            +
                    test_server_extra_args: [],
         | 
| 143 | 
            +
                    &
         | 
| 144 | 
            +
                  )
         | 
| 145 | 
            +
                    server_options = Internal::Bridge::Testing::EphemeralServer::StartTestServerOptions.new(
         | 
| 146 | 
            +
                      existing_path: test_server_existing_path,
         | 
| 147 | 
            +
                      sdk_name: 'sdk-ruby',
         | 
| 148 | 
            +
                      sdk_version: VERSION,
         | 
| 149 | 
            +
                      download_version: test_server_download_version,
         | 
| 150 | 
            +
                      download_dest_dir: test_server_download_dest_dir,
         | 
| 151 | 
            +
                      port:,
         | 
| 152 | 
            +
                      extra_args: test_server_extra_args
         | 
| 153 | 
            +
                    )
         | 
| 154 | 
            +
                    _with_core_server(
         | 
| 155 | 
            +
                      core_server: Internal::Bridge::Testing::EphemeralServer.start_test_server(
         | 
| 156 | 
            +
                        runtime._core_runtime, server_options
         | 
| 157 | 
            +
                      ),
         | 
| 158 | 
            +
                      namespace: 'default',
         | 
| 159 | 
            +
                      data_converter:,
         | 
| 160 | 
            +
                      interceptors:,
         | 
| 161 | 
            +
                      logger:,
         | 
| 162 | 
            +
                      default_workflow_query_reject_condition:,
         | 
| 163 | 
            +
                      runtime:,
         | 
| 164 | 
            +
                      supports_time_skipping: true,
         | 
| 165 | 
            +
                      & # steep:ignore
         | 
| 166 | 
            +
                    )
         | 
| 167 | 
            +
                  end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                  # @!visibility private
         | 
| 170 | 
            +
                  def self._with_core_server(
         | 
| 171 | 
            +
                    core_server:,
         | 
| 172 | 
            +
                    namespace:,
         | 
| 173 | 
            +
                    data_converter:,
         | 
| 174 | 
            +
                    interceptors:,
         | 
| 175 | 
            +
                    logger:,
         | 
| 176 | 
            +
                    default_workflow_query_reject_condition:,
         | 
| 177 | 
            +
                    runtime:,
         | 
| 178 | 
            +
                    supports_time_skipping:
         | 
| 179 | 
            +
                  )
         | 
| 84 180 | 
             
                    # Try to connect, shutdown if we can't
         | 
| 85 181 | 
             
                    begin
         | 
| 86 182 | 
             
                      client = Client.connect(
         | 
| @@ -92,8 +188,8 @@ module Temporalio | |
| 92 188 | 
             
                        default_workflow_query_reject_condition:,
         | 
| 93 189 | 
             
                        runtime:
         | 
| 94 190 | 
             
                      )
         | 
| 95 | 
            -
                      server = Ephemeral.new(client, core_server)
         | 
| 96 | 
            -
                    rescue  | 
| 191 | 
            +
                      server = Ephemeral.new(client, core_server, supports_time_skipping:)
         | 
| 192 | 
            +
                    rescue Exception # rubocop:disable Lint/RescueException
         | 
| 97 193 | 
             
                      core_server.shutdown
         | 
| 98 194 | 
             
                      raise
         | 
| 99 195 | 
             
                    end
         | 
| @@ -120,18 +216,168 @@ module Temporalio | |
| 120 216 | 
             
                    # Do nothing by default
         | 
| 121 217 | 
             
                  end
         | 
| 122 218 |  | 
| 219 | 
            +
                  # @return [Boolean] Whether this environment supports time skipping.
         | 
| 220 | 
            +
                  def supports_time_skipping?
         | 
| 221 | 
            +
                    false
         | 
| 222 | 
            +
                  end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                  # Advanced time.
         | 
| 225 | 
            +
                  #
         | 
| 226 | 
            +
                  # If this server supports time skipping, this will immediately advance time and return. If it does not, this is
         | 
| 227 | 
            +
                  # a standard {::sleep}.
         | 
| 228 | 
            +
                  #
         | 
| 229 | 
            +
                  # @param duration [Float] Duration seconds.
         | 
| 230 | 
            +
                  def sleep(duration)
         | 
| 231 | 
            +
                    Kernel.sleep(duration)
         | 
| 232 | 
            +
                  end
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                  # Current time of the environment.
         | 
| 235 | 
            +
                  #
         | 
| 236 | 
            +
                  # If this server supports time skipping, this will be the current time as known to the environment. If it does
         | 
| 237 | 
            +
                  # not, this is a standard {::Time.now}.
         | 
| 238 | 
            +
                  #
         | 
| 239 | 
            +
                  # @return [Time] Current time.
         | 
| 240 | 
            +
                  def current_time
         | 
| 241 | 
            +
                    Time.now
         | 
| 242 | 
            +
                  end
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                  # Run a block with automatic time skipping disabled. This just runs the block for environments that don't support
         | 
| 245 | 
            +
                  # time skipping.
         | 
| 246 | 
            +
                  #
         | 
| 247 | 
            +
                  # @yield Block to run.
         | 
| 248 | 
            +
                  # @return [Object] Result of the block.
         | 
| 249 | 
            +
                  def auto_time_skipping_disabled(&)
         | 
| 250 | 
            +
                    raise 'Block required' unless block_given?
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                    yield
         | 
| 253 | 
            +
                  end
         | 
| 254 | 
            +
             | 
| 123 255 | 
             
                  # @!visibility private
         | 
| 124 256 | 
             
                  class Ephemeral < WorkflowEnvironment
         | 
| 125 | 
            -
                    def initialize(client, core_server)
         | 
| 257 | 
            +
                    def initialize(client, core_server, supports_time_skipping:)
         | 
| 258 | 
            +
                      # Add our interceptor at the end of the existing interceptors that skips time
         | 
| 259 | 
            +
                      client_options = client.options.with(
         | 
| 260 | 
            +
                        interceptors: client.options.interceptors + [TimeSkippingClientInterceptor.new(self)]
         | 
| 261 | 
            +
                      )
         | 
| 262 | 
            +
                      client = Client.new(**client_options.to_h) # steep:ignore
         | 
| 126 263 | 
             
                      super(client)
         | 
| 264 | 
            +
             | 
| 265 | 
            +
                      @auto_time_skipping = true
         | 
| 127 266 | 
             
                      @core_server = core_server
         | 
| 267 | 
            +
                      @test_service = Client::Connection::TestService.new(client.connection) if supports_time_skipping
         | 
| 128 268 | 
             
                    end
         | 
| 129 269 |  | 
| 130 270 | 
             
                    # @!visibility private
         | 
| 131 271 | 
             
                    def shutdown
         | 
| 132 272 | 
             
                      @core_server.shutdown
         | 
| 133 273 | 
             
                    end
         | 
| 274 | 
            +
             | 
| 275 | 
            +
                    # @!visibility private
         | 
| 276 | 
            +
                    def supports_time_skipping?
         | 
| 277 | 
            +
                      !@test_service.nil?
         | 
| 278 | 
            +
                    end
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                    # @!visibility private
         | 
| 281 | 
            +
                    def sleep(duration)
         | 
| 282 | 
            +
                      return super unless supports_time_skipping?
         | 
| 283 | 
            +
             | 
| 284 | 
            +
                      @test_service.unlock_time_skipping_with_sleep(
         | 
| 285 | 
            +
                        Api::TestService::V1::SleepRequest.new(duration: Internal::ProtoUtils.seconds_to_duration(duration))
         | 
| 286 | 
            +
                      )
         | 
| 287 | 
            +
                    end
         | 
| 288 | 
            +
             | 
| 289 | 
            +
                    # @!visibility private
         | 
| 290 | 
            +
                    def current_time
         | 
| 291 | 
            +
                      return super unless supports_time_skipping?
         | 
| 292 | 
            +
             | 
| 293 | 
            +
                      resp = @test_service.get_current_time(Google::Protobuf::Empty.new)
         | 
| 294 | 
            +
                      Internal::ProtoUtils.timestamp_to_time(resp.time) or raise 'Time missing'
         | 
| 295 | 
            +
                    end
         | 
| 296 | 
            +
             | 
| 297 | 
            +
                    # @!visibility private
         | 
| 298 | 
            +
                    def auto_time_skipping_disabled(&)
         | 
| 299 | 
            +
                      raise 'Block required' unless block_given?
         | 
| 300 | 
            +
                      return super unless supports_time_skipping?
         | 
| 301 | 
            +
             | 
| 302 | 
            +
                      already_disabled = @auto_time_skipping
         | 
| 303 | 
            +
                      @auto_time_skipping = false
         | 
| 304 | 
            +
                      begin
         | 
| 305 | 
            +
                        yield
         | 
| 306 | 
            +
                      ensure
         | 
| 307 | 
            +
                        @auto_time_skipping = true unless already_disabled
         | 
| 308 | 
            +
                      end
         | 
| 309 | 
            +
                    end
         | 
| 310 | 
            +
             | 
| 311 | 
            +
                    # @!visibility private
         | 
| 312 | 
            +
                    def time_skipping_unlocked(&)
         | 
| 313 | 
            +
                      # If disabled or unsupported, no locking/unlocking, just run and return
         | 
| 314 | 
            +
                      return yield if !supports_time_skipping? || !@auto_time_skipping
         | 
| 315 | 
            +
             | 
| 316 | 
            +
                      # Unlock to start time skipping, lock again to stop it
         | 
| 317 | 
            +
                      @test_service.unlock_time_skipping(Api::TestService::V1::UnlockTimeSkippingRequest.new)
         | 
| 318 | 
            +
                      user_code_success = false
         | 
| 319 | 
            +
                      begin
         | 
| 320 | 
            +
                        result = yield
         | 
| 321 | 
            +
                        user_code_success = true
         | 
| 322 | 
            +
                        result
         | 
| 323 | 
            +
                      ensure
         | 
| 324 | 
            +
                        # Lock it back
         | 
| 325 | 
            +
                        begin
         | 
| 326 | 
            +
                          @test_service.lock_time_skipping(Api::TestService::V1::LockTimeSkippingRequest.new)
         | 
| 327 | 
            +
                        rescue StandardError => e
         | 
| 328 | 
            +
                          # Re-raise if user code succeeded, otherwise swallow
         | 
| 329 | 
            +
                          raise if user_code_success
         | 
| 330 | 
            +
             | 
| 331 | 
            +
                          client.options.logger.error('Failed locking time skipping after error')
         | 
| 332 | 
            +
                          client.options.logger.error(e)
         | 
| 333 | 
            +
                        end
         | 
| 334 | 
            +
                      end
         | 
| 335 | 
            +
                    end
         | 
| 336 | 
            +
                  end
         | 
| 337 | 
            +
             | 
| 338 | 
            +
                  private_constant :Ephemeral
         | 
| 339 | 
            +
             | 
| 340 | 
            +
                  # @!visibility private
         | 
| 341 | 
            +
                  class TimeSkippingClientInterceptor
         | 
| 342 | 
            +
                    include Client::Interceptor
         | 
| 343 | 
            +
             | 
| 344 | 
            +
                    def initialize(env)
         | 
| 345 | 
            +
                      @env = env
         | 
| 346 | 
            +
                    end
         | 
| 347 | 
            +
             | 
| 348 | 
            +
                    # @!visibility private
         | 
| 349 | 
            +
                    def intercept_client(next_interceptor)
         | 
| 350 | 
            +
                      Outbound.new(next_interceptor, @env)
         | 
| 351 | 
            +
                    end
         | 
| 352 | 
            +
             | 
| 353 | 
            +
                    # @!visibility private
         | 
| 354 | 
            +
                    class Outbound < Client::Interceptor::Outbound
         | 
| 355 | 
            +
                      def initialize(next_interceptor, env)
         | 
| 356 | 
            +
                        super(next_interceptor)
         | 
| 357 | 
            +
                        @env = env
         | 
| 358 | 
            +
                      end
         | 
| 359 | 
            +
             | 
| 360 | 
            +
                      # @!visibility private
         | 
| 361 | 
            +
                      def start_workflow(input)
         | 
| 362 | 
            +
                        TimeSkippingWorkflowHandle.new(super, @env)
         | 
| 363 | 
            +
                      end
         | 
| 364 | 
            +
                    end
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                    # @!visibility private
         | 
| 367 | 
            +
                    class TimeSkippingWorkflowHandle < SimpleDelegator
         | 
| 368 | 
            +
                      def initialize(handle, env)
         | 
| 369 | 
            +
                        super(handle) # steep:ignore
         | 
| 370 | 
            +
                        @env = env
         | 
| 371 | 
            +
                      end
         | 
| 372 | 
            +
             | 
| 373 | 
            +
                      # @!visibility private
         | 
| 374 | 
            +
                      def result(follow_runs: true, rpc_options: nil)
         | 
| 375 | 
            +
                        @env.time_skipping_unlocked { super(follow_runs:, rpc_options:) }
         | 
| 376 | 
            +
                      end
         | 
| 377 | 
            +
                    end
         | 
| 134 378 | 
             
                  end
         | 
| 379 | 
            +
             | 
| 380 | 
            +
                  private_constant :TimeSkippingClientInterceptor
         | 
| 135 381 | 
             
                end
         | 
| 136 382 | 
             
              end
         | 
| 137 383 | 
             
            end
         | 
    
        data/lib/temporalio/version.rb
    CHANGED