temporal-ruby 0.0.0 → 0.0.1.pre.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +19 -42
- data/lib/gen/temporal/api/command/v1/message_pb.rb +146 -0
- data/lib/gen/temporal/api/common/v1/message_pb.rb +67 -0
- data/lib/gen/temporal/api/enums/v1/command_type_pb.rb +35 -0
- data/lib/gen/temporal/api/enums/v1/common_pb.rb +34 -0
- data/lib/gen/temporal/api/enums/v1/event_type_pb.rb +62 -0
- data/lib/gen/temporal/api/enums/v1/failed_cause_pb.rb +60 -0
- data/lib/gen/temporal/api/enums/v1/namespace_pb.rb +31 -0
- data/lib/gen/temporal/api/enums/v1/query_pb.rb +31 -0
- data/lib/gen/temporal/api/enums/v1/task_queue_pb.rb +30 -0
- data/lib/gen/temporal/api/enums/v1/workflow_pb.rb +82 -0
- data/lib/gen/temporal/api/errordetails/v1/message_pb.rb +55 -0
- data/lib/gen/temporal/api/failure/v1/message_pb.rb +81 -0
- data/lib/gen/temporal/api/filter/v1/message_pb.rb +38 -0
- data/lib/gen/temporal/api/history/v1/message_pb.rb +423 -0
- data/lib/gen/temporal/api/namespace/v1/message_pb.rb +55 -0
- data/lib/gen/temporal/api/query/v1/message_pb.rb +36 -0
- data/lib/gen/temporal/api/replication/v1/message_pb.rb +27 -0
- data/lib/gen/temporal/api/taskqueue/v1/message_pb.rb +60 -0
- data/lib/gen/temporal/api/version/v1/message_pb.rb +28 -0
- data/lib/gen/temporal/api/workflow/v1/message_pb.rb +83 -0
- data/lib/gen/temporal/api/workflowservice/v1/request_response_pb.rb +538 -0
- data/lib/gen/temporal/api/workflowservice/v1/service_pb.rb +19 -0
- data/lib/gen/temporal/api/workflowservice/v1/service_services_pb.rb +223 -0
- data/lib/temporal-ruby.rb +1 -0
- data/lib/temporal.rb +137 -0
- data/lib/temporal/activity.rb +33 -0
- data/lib/temporal/activity/async_token.rb +34 -0
- data/lib/temporal/activity/context.rb +64 -0
- data/lib/temporal/activity/poller.rb +79 -0
- data/lib/temporal/activity/task_processor.rb +78 -0
- data/lib/temporal/activity/workflow_convenience_methods.rb +41 -0
- data/lib/temporal/client.rb +21 -0
- data/lib/temporal/client/errors.rb +8 -0
- data/lib/temporal/client/grpc_client.rb +345 -0
- data/lib/temporal/client/serializer.rb +31 -0
- data/lib/temporal/client/serializer/base.rb +23 -0
- data/lib/temporal/client/serializer/cancel_timer.rb +19 -0
- data/lib/temporal/client/serializer/complete_workflow.rb +20 -0
- data/lib/temporal/client/serializer/fail_workflow.rb +20 -0
- data/lib/temporal/client/serializer/failure.rb +29 -0
- data/lib/temporal/client/serializer/payload.rb +25 -0
- data/lib/temporal/client/serializer/record_marker.rb +23 -0
- data/lib/temporal/client/serializer/request_activity_cancellation.rb +19 -0
- data/lib/temporal/client/serializer/schedule_activity.rb +53 -0
- data/lib/temporal/client/serializer/start_child_workflow.rb +51 -0
- data/lib/temporal/client/serializer/start_timer.rb +20 -0
- data/lib/temporal/concerns/executable.rb +37 -0
- data/lib/temporal/concerns/typed.rb +40 -0
- data/lib/temporal/configuration.rb +44 -0
- data/lib/temporal/errors.rb +38 -0
- data/lib/temporal/executable_lookup.rb +25 -0
- data/lib/temporal/execution_options.rb +35 -0
- data/lib/temporal/json.rb +18 -0
- data/lib/temporal/metadata.rb +68 -0
- data/lib/temporal/metadata/activity.rb +27 -0
- data/lib/temporal/metadata/base.rb +17 -0
- data/lib/temporal/metadata/workflow.rb +22 -0
- data/lib/temporal/metadata/workflow_task.rb +25 -0
- data/lib/temporal/metrics.rb +37 -0
- data/lib/temporal/metrics_adapters/log.rb +33 -0
- data/lib/temporal/metrics_adapters/null.rb +9 -0
- data/lib/temporal/middleware/chain.rb +30 -0
- data/lib/temporal/middleware/entry.rb +9 -0
- data/lib/temporal/retry_policy.rb +27 -0
- data/lib/temporal/saga/concern.rb +23 -0
- data/lib/temporal/saga/result.rb +22 -0
- data/lib/temporal/saga/saga.rb +24 -0
- data/lib/temporal/testing.rb +50 -0
- data/lib/temporal/testing/future_registry.rb +27 -0
- data/lib/temporal/testing/local_activity_context.rb +17 -0
- data/lib/temporal/testing/local_workflow_context.rb +178 -0
- data/lib/temporal/testing/temporal_override.rb +121 -0
- data/lib/temporal/testing/workflow_execution.rb +44 -0
- data/lib/temporal/testing/workflow_override.rb +36 -0
- data/lib/temporal/thread_local_context.rb +14 -0
- data/lib/temporal/thread_pool.rb +63 -0
- data/lib/temporal/types.rb +7 -0
- data/lib/temporal/uuid.rb +19 -0
- data/lib/temporal/version.rb +1 -1
- data/lib/temporal/worker.rb +88 -0
- data/lib/temporal/workflow.rb +42 -0
- data/lib/temporal/workflow/command.rb +39 -0
- data/lib/temporal/workflow/command_state_machine.rb +48 -0
- data/lib/temporal/workflow/context.rb +243 -0
- data/lib/temporal/workflow/convenience_methods.rb +34 -0
- data/lib/temporal/workflow/dispatcher.rb +31 -0
- data/lib/temporal/workflow/execution_info.rb +51 -0
- data/lib/temporal/workflow/executor.rb +45 -0
- data/lib/temporal/workflow/future.rb +77 -0
- data/lib/temporal/workflow/history.rb +76 -0
- data/lib/temporal/workflow/history/event.rb +69 -0
- data/lib/temporal/workflow/history/event_target.rb +75 -0
- data/lib/temporal/workflow/history/window.rb +40 -0
- data/lib/temporal/workflow/poller.rb +67 -0
- data/lib/temporal/workflow/replay_aware_logger.rb +36 -0
- data/lib/temporal/workflow/state_manager.rb +342 -0
- data/lib/temporal/workflow/task_processor.rb +78 -0
- data/rbi/temporal-ruby.rbi +43 -0
- data/temporal.gemspec +10 -2
- metadata +186 -6
@@ -0,0 +1,38 @@
|
|
1
|
+
module Temporal
|
2
|
+
# Superclass for all Temporal errors
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
# Superclass for errors specific to Temporal worker itself
|
6
|
+
class InternalError < Error; end
|
7
|
+
|
8
|
+
# Superclass for misconfiguration/misuse on the client (user) side
|
9
|
+
class ClientError < Error; end
|
10
|
+
|
11
|
+
# Represents any timeout
|
12
|
+
class TimeoutError < ClientError; end
|
13
|
+
|
14
|
+
# A superclass for activity exceptions raised explicitly
|
15
|
+
# with the intent to propagate to a workflow
|
16
|
+
class ActivityException < ClientError; end
|
17
|
+
|
18
|
+
class ActivityNotRegistered < ClientError; end
|
19
|
+
class WorkflowNotRegistered < ClientError; end
|
20
|
+
|
21
|
+
class ApiError < Error; end
|
22
|
+
|
23
|
+
class NotFoundFailure < ApiError; end
|
24
|
+
class WorkflowExecutionAlreadyStartedFailure < ApiError
|
25
|
+
attr_reader :run_id
|
26
|
+
|
27
|
+
def initialize(message, run_id)
|
28
|
+
super(message)
|
29
|
+
@run_id = run_id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
class NamespaceNotActiveFailure < ApiError; end
|
33
|
+
class ClientVersionNotSupportedFailure < ApiError; end
|
34
|
+
class FeatureVersionNotSupportedFailure < ApiError; end
|
35
|
+
class NamespaceAlreadyExistsFailure < ApiError; end
|
36
|
+
class CancellationAlreadyRequestedFailure < ApiError; end
|
37
|
+
class QueryFailedFailure < ApiError; end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# This class is responsible for matching an executable (activity or workflow) name
|
2
|
+
# to a class implementing it.
|
3
|
+
#
|
4
|
+
# TODO: This class should be responsible for handling executable versions
|
5
|
+
# when these are implemented
|
6
|
+
#
|
7
|
+
module Temporal
|
8
|
+
class ExecutableLookup
|
9
|
+
def initialize
|
10
|
+
@executables = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(name, executable)
|
14
|
+
executables[name] = executable
|
15
|
+
end
|
16
|
+
|
17
|
+
def find(name)
|
18
|
+
executables[name]
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :executables
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'temporal/concerns/executable'
|
2
|
+
|
3
|
+
module Temporal
|
4
|
+
class ExecutionOptions
|
5
|
+
attr_reader :name, :namespace, :task_queue, :retry_policy, :timeouts, :headers
|
6
|
+
|
7
|
+
def initialize(object, options = {})
|
8
|
+
@name = options[:name] || object.to_s
|
9
|
+
@namespace = options[:namespace]
|
10
|
+
@task_queue = options[:task_queue] || options[:task_list]
|
11
|
+
@retry_policy = options[:retry_policy]
|
12
|
+
@timeouts = options[:timeouts] || {}
|
13
|
+
@headers = options[:headers] || {}
|
14
|
+
|
15
|
+
if object.singleton_class.included_modules.include?(Concerns::Executable)
|
16
|
+
@namespace ||= object.namespace
|
17
|
+
@task_queue ||= object.task_queue
|
18
|
+
@retry_policy ||= object.retry_policy
|
19
|
+
@timeouts = object.timeouts.merge(@timeouts) if object.timeouts
|
20
|
+
@headers = object.headers.merge(@headers) if object.headers
|
21
|
+
end
|
22
|
+
|
23
|
+
@namespace ||= Temporal.configuration.namespace
|
24
|
+
@task_queue ||= Temporal.configuration.task_queue
|
25
|
+
@timeouts = Temporal.configuration.timeouts.merge(@timeouts)
|
26
|
+
@headers = Temporal.configuration.headers.merge(@headers)
|
27
|
+
|
28
|
+
freeze
|
29
|
+
end
|
30
|
+
|
31
|
+
def task_list
|
32
|
+
@task_queue
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Helper class for serializing/deserializing JSON
|
2
|
+
require 'oj'
|
3
|
+
|
4
|
+
module Temporal
|
5
|
+
module JSON
|
6
|
+
OJ_OPTIONS = {
|
7
|
+
mode: :object
|
8
|
+
}.freeze
|
9
|
+
|
10
|
+
def self.serialize(value)
|
11
|
+
Oj.dump(value, OJ_OPTIONS)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.deserialize(value)
|
15
|
+
Oj.load(value.to_s, OJ_OPTIONS)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'temporal/errors'
|
2
|
+
require 'temporal/metadata/activity'
|
3
|
+
require 'temporal/metadata/workflow'
|
4
|
+
require 'temporal/metadata/workflow_task'
|
5
|
+
|
6
|
+
module Temporal
|
7
|
+
module Metadata
|
8
|
+
ACTIVITY_TYPE = :activity
|
9
|
+
WORKFLOW_TASK_TYPE = :workflow_task
|
10
|
+
WORKFLOW_TYPE = :workflow
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def generate(type, data, namespace = nil)
|
14
|
+
case type
|
15
|
+
when ACTIVITY_TYPE
|
16
|
+
activity_metadata_from(data, namespace)
|
17
|
+
when WORKFLOW_TASK_TYPE
|
18
|
+
workflow_task_metadata_from(data, namespace)
|
19
|
+
when WORKFLOW_TYPE
|
20
|
+
workflow_metadata_from(data)
|
21
|
+
else
|
22
|
+
raise InternalError, 'Unsupported metadata type'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def headers(fields)
|
29
|
+
fields.transform_values { |v| v[:data] }
|
30
|
+
end
|
31
|
+
|
32
|
+
def activity_metadata_from(task, namespace)
|
33
|
+
Metadata::Activity.new(
|
34
|
+
namespace: namespace,
|
35
|
+
id: task.activity_id,
|
36
|
+
name: task.activity_type.name,
|
37
|
+
task_token: task.task_token,
|
38
|
+
attempt: task.attempt,
|
39
|
+
workflow_run_id: task.workflow_execution.run_id,
|
40
|
+
workflow_id: task.workflow_execution.workflow_id,
|
41
|
+
workflow_name: task.workflow_type.name,
|
42
|
+
headers: headers(task.header&.fields.to_h)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def workflow_task_metadata_from(task, namespace)
|
47
|
+
Metadata::WorkflowTask.new(
|
48
|
+
namespace: namespace,
|
49
|
+
id: task.started_event_id,
|
50
|
+
task_token: task.task_token,
|
51
|
+
attempt: task.attempt,
|
52
|
+
workflow_run_id: task.workflow_execution.run_id,
|
53
|
+
workflow_id: task.workflow_execution.workflow_id,
|
54
|
+
workflow_name: task.workflow_type.name
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def workflow_metadata_from(event)
|
59
|
+
Metadata::Workflow.new(
|
60
|
+
name: event.workflow_type.name,
|
61
|
+
run_id: event.original_execution_run_id,
|
62
|
+
attempt: event.attempt,
|
63
|
+
headers: headers(event.header&.fields.to_h)
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'temporal/metadata/base'
|
2
|
+
|
3
|
+
module Temporal
|
4
|
+
module Metadata
|
5
|
+
class Activity < Base
|
6
|
+
attr_reader :namespace, :id, :name, :task_token, :attempt, :workflow_run_id, :workflow_id, :workflow_name, :headers
|
7
|
+
|
8
|
+
def initialize(namespace:, id:, name:, task_token:, attempt:, workflow_run_id:, workflow_id:, workflow_name:, headers: {})
|
9
|
+
@namespace = namespace
|
10
|
+
@id = id
|
11
|
+
@name = name
|
12
|
+
@task_token = task_token
|
13
|
+
@attempt = attempt
|
14
|
+
@workflow_run_id = workflow_run_id
|
15
|
+
@workflow_id = workflow_id
|
16
|
+
@workflow_name = workflow_name
|
17
|
+
@headers = headers
|
18
|
+
|
19
|
+
freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
def activity?
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'temporal/metadata/base'
|
2
|
+
|
3
|
+
module Temporal
|
4
|
+
module Metadata
|
5
|
+
class Workflow < Base
|
6
|
+
attr_reader :name, :run_id, :attempt, :headers
|
7
|
+
|
8
|
+
def initialize(name:, run_id:, attempt:, headers: {})
|
9
|
+
@name = name
|
10
|
+
@run_id = run_id
|
11
|
+
@attempt = attempt
|
12
|
+
@headers = headers
|
13
|
+
|
14
|
+
freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
def workflow?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'temporal/metadata/base'
|
2
|
+
|
3
|
+
module Temporal
|
4
|
+
module Metadata
|
5
|
+
class WorkflowTask < Base
|
6
|
+
attr_reader :namespace, :id, :task_token, :attempt, :workflow_run_id, :workflow_id, :workflow_name
|
7
|
+
|
8
|
+
def initialize(namespace:, id:, task_token:, attempt:, workflow_run_id:, workflow_id:, workflow_name:)
|
9
|
+
@namespace = namespace
|
10
|
+
@id = id
|
11
|
+
@task_token = task_token
|
12
|
+
@attempt = attempt
|
13
|
+
@workflow_run_id = workflow_run_id
|
14
|
+
@workflow_id = workflow_id
|
15
|
+
@workflow_name = workflow_name
|
16
|
+
|
17
|
+
freeze
|
18
|
+
end
|
19
|
+
|
20
|
+
def workflow_task?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Temporal
|
2
|
+
class Metrics
|
3
|
+
def initialize(adapter)
|
4
|
+
@adapter = adapter
|
5
|
+
end
|
6
|
+
|
7
|
+
def increment(key, tags = {})
|
8
|
+
count(key, 1, tags)
|
9
|
+
end
|
10
|
+
|
11
|
+
def decrement(key, tags = {})
|
12
|
+
count(key, -1, tags)
|
13
|
+
end
|
14
|
+
|
15
|
+
def count(key, count, tags = {})
|
16
|
+
adapter.count(key, count, tags)
|
17
|
+
rescue StandardError => error
|
18
|
+
Temporal.logger.error("Adapter failed to send count metrics for #{key}: #{error.inspect}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def gauge(key, value, tags = {})
|
22
|
+
adapter.gauge(key, value, tags)
|
23
|
+
rescue StandardError => error
|
24
|
+
Temporal.logger.error("Adapter failed to send gauge metrics for #{key}: #{error.inspect}")
|
25
|
+
end
|
26
|
+
|
27
|
+
def timing(key, time, tags = {})
|
28
|
+
adapter.timing(key, time, tags)
|
29
|
+
rescue StandardError => error
|
30
|
+
Temporal.logger.error("Adapter failed to send timing metrics for #{key}: #{error.inspect}")
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :adapter
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Temporal
|
2
|
+
module MetricsAdapters
|
3
|
+
class Log
|
4
|
+
def initialize(logger)
|
5
|
+
@logger = logger
|
6
|
+
end
|
7
|
+
|
8
|
+
def count(key, count, tags)
|
9
|
+
logger.debug(format_message(key, 'count', count, tags))
|
10
|
+
end
|
11
|
+
|
12
|
+
def gauge(key, value, tags)
|
13
|
+
logger.debug(format_message(key, 'gauge', value, tags))
|
14
|
+
end
|
15
|
+
|
16
|
+
def timing(key, time, tags)
|
17
|
+
logger.debug(format_message(key, 'timing', time, tags))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :logger
|
23
|
+
|
24
|
+
def format_message(key, type, value, tags)
|
25
|
+
tags_str = tags.map { |k, v| "#{k}:#{v}" }.join(',')
|
26
|
+
parts = [key, type, value]
|
27
|
+
parts << tags_str if !tags_str.empty?
|
28
|
+
|
29
|
+
parts.join(' | ')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Temporal
|
2
|
+
module Middleware
|
3
|
+
class Chain
|
4
|
+
def initialize(entries = [])
|
5
|
+
@middleware = entries.map(&:init_middleware)
|
6
|
+
end
|
7
|
+
|
8
|
+
def invoke(metadata)
|
9
|
+
result = nil
|
10
|
+
chain = middleware.dup
|
11
|
+
|
12
|
+
traverse_chain = lambda do
|
13
|
+
if chain.empty?
|
14
|
+
result = yield
|
15
|
+
else
|
16
|
+
chain.shift.call(metadata, &traverse_chain)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
traverse_chain.call
|
21
|
+
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :middleware
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'temporal/errors'
|
2
|
+
|
3
|
+
module Temporal
|
4
|
+
class RetryPolicy < Struct.new(:interval, :backoff, :max_interval, :max_attempts,
|
5
|
+
:expiration_interval, :non_retriable_errors, keyword_init: true)
|
6
|
+
|
7
|
+
class InvalidRetryPolicy < ClientError; end
|
8
|
+
|
9
|
+
def validate!
|
10
|
+
unless interval && backoff
|
11
|
+
raise InvalidRetryPolicy, 'interval and backoff must be set'
|
12
|
+
end
|
13
|
+
|
14
|
+
unless max_attempts || expiration_interval
|
15
|
+
raise InvalidRetryPolicy, 'max_attempts or expiration_interval must be set'
|
16
|
+
end
|
17
|
+
|
18
|
+
unless [interval, max_interval, expiration_interval].compact.all? { |arg| arg.is_a?(Integer) }
|
19
|
+
raise InvalidRetryPolicy, 'All intervals must be specified in whole seconds'
|
20
|
+
end
|
21
|
+
|
22
|
+
unless [interval, max_interval, expiration_interval].compact.all? { |arg| arg > 0 }
|
23
|
+
raise InvalidRetryPolicy, 'All intervals must be greater than 0'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'temporal/saga/saga'
|
2
|
+
require 'temporal/saga/result'
|
3
|
+
|
4
|
+
module Temporal
|
5
|
+
module Saga
|
6
|
+
module Concern
|
7
|
+
def run_saga(&block)
|
8
|
+
saga = Temporal::Saga::Saga.new(workflow)
|
9
|
+
|
10
|
+
block.call(saga)
|
11
|
+
|
12
|
+
Result.new(true)
|
13
|
+
rescue StandardError => error # TODO: is there a need for a specialized error here?
|
14
|
+
logger.error("Saga execution aborted: #{error.inspect}")
|
15
|
+
logger.debug(error.backtrace.join("\n"))
|
16
|
+
|
17
|
+
saga.compensate
|
18
|
+
|
19
|
+
Result.new(false, error)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|