cadence-ruby 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +456 -0
- data/cadence.gemspec +9 -2
- data/lib/cadence-ruby.rb +1 -0
- data/lib/cadence.rb +176 -0
- data/lib/cadence/activity.rb +33 -0
- data/lib/cadence/activity/async_token.rb +34 -0
- data/lib/cadence/activity/context.rb +64 -0
- data/lib/cadence/activity/poller.rb +89 -0
- data/lib/cadence/activity/task_processor.rb +73 -0
- data/lib/cadence/activity/workflow_convenience_methods.rb +41 -0
- data/lib/cadence/client.rb +21 -0
- data/lib/cadence/client/errors.rb +8 -0
- data/lib/cadence/client/thrift_client.rb +380 -0
- data/lib/cadence/concerns/executable.rb +33 -0
- data/lib/cadence/concerns/typed.rb +40 -0
- data/lib/cadence/configuration.rb +36 -0
- data/lib/cadence/errors.rb +21 -0
- data/lib/cadence/executable_lookup.rb +25 -0
- data/lib/cadence/execution_options.rb +32 -0
- data/lib/cadence/json.rb +18 -0
- data/lib/cadence/metadata.rb +73 -0
- data/lib/cadence/metadata/activity.rb +28 -0
- data/lib/cadence/metadata/base.rb +17 -0
- data/lib/cadence/metadata/decision.rb +25 -0
- data/lib/cadence/metadata/workflow.rb +23 -0
- data/lib/cadence/metrics.rb +37 -0
- data/lib/cadence/metrics_adapters/log.rb +33 -0
- data/lib/cadence/metrics_adapters/null.rb +9 -0
- data/lib/cadence/middleware/chain.rb +30 -0
- data/lib/cadence/middleware/entry.rb +9 -0
- data/lib/cadence/retry_policy.rb +27 -0
- data/lib/cadence/saga/concern.rb +37 -0
- data/lib/cadence/saga/result.rb +22 -0
- data/lib/cadence/saga/saga.rb +24 -0
- data/lib/cadence/testing.rb +50 -0
- data/lib/cadence/testing/cadence_override.rb +112 -0
- data/lib/cadence/testing/future_registry.rb +27 -0
- data/lib/cadence/testing/local_activity_context.rb +17 -0
- data/lib/cadence/testing/local_workflow_context.rb +207 -0
- data/lib/cadence/testing/workflow_execution.rb +44 -0
- data/lib/cadence/testing/workflow_override.rb +36 -0
- data/lib/cadence/thread_local_context.rb +14 -0
- data/lib/cadence/thread_pool.rb +68 -0
- data/lib/cadence/types.rb +7 -0
- data/lib/cadence/utils.rb +17 -0
- data/lib/cadence/uuid.rb +19 -0
- data/lib/cadence/version.rb +1 -1
- data/lib/cadence/worker.rb +91 -0
- data/lib/cadence/workflow.rb +42 -0
- data/lib/cadence/workflow/context.rb +266 -0
- data/lib/cadence/workflow/convenience_methods.rb +34 -0
- data/lib/cadence/workflow/decision.rb +39 -0
- data/lib/cadence/workflow/decision_state_machine.rb +48 -0
- data/lib/cadence/workflow/decision_task_processor.rb +105 -0
- data/lib/cadence/workflow/dispatcher.rb +31 -0
- data/lib/cadence/workflow/execution_info.rb +45 -0
- data/lib/cadence/workflow/executor.rb +45 -0
- data/lib/cadence/workflow/future.rb +75 -0
- data/lib/cadence/workflow/history.rb +76 -0
- data/lib/cadence/workflow/history/event.rb +71 -0
- data/lib/cadence/workflow/history/event_target.rb +79 -0
- data/lib/cadence/workflow/history/window.rb +40 -0
- data/lib/cadence/workflow/poller.rb +74 -0
- data/lib/cadence/workflow/replay_aware_logger.rb +36 -0
- data/lib/cadence/workflow/serializer.rb +31 -0
- data/lib/cadence/workflow/serializer/base.rb +22 -0
- data/lib/cadence/workflow/serializer/cancel_timer.rb +19 -0
- data/lib/cadence/workflow/serializer/complete_workflow.rb +20 -0
- data/lib/cadence/workflow/serializer/fail_workflow.rb +21 -0
- data/lib/cadence/workflow/serializer/record_marker.rb +21 -0
- data/lib/cadence/workflow/serializer/request_activity_cancellation.rb +19 -0
- data/lib/cadence/workflow/serializer/schedule_activity.rb +54 -0
- data/lib/cadence/workflow/serializer/start_child_workflow.rb +52 -0
- data/lib/cadence/workflow/serializer/start_timer.rb +20 -0
- data/lib/cadence/workflow/state_manager.rb +324 -0
- data/lib/gen/thrift/cadence_constants.rb +11 -0
- data/lib/gen/thrift/cadence_types.rb +11 -0
- data/lib/gen/thrift/shared_constants.rb +11 -0
- data/lib/gen/thrift/shared_types.rb +4600 -0
- data/lib/gen/thrift/workflow_service.rb +3142 -0
- data/rbi/cadence-ruby.rbi +39 -0
- metadata +152 -5
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'cadence/retry_policy'
|
2
|
+
|
3
|
+
module Cadence
|
4
|
+
module Concerns
|
5
|
+
module Executable
|
6
|
+
def domain(*args)
|
7
|
+
return @domain if args.empty?
|
8
|
+
@domain = args.first
|
9
|
+
end
|
10
|
+
|
11
|
+
def task_list(*args)
|
12
|
+
return @task_list if args.empty?
|
13
|
+
@task_list = args.first
|
14
|
+
end
|
15
|
+
|
16
|
+
def retry_policy(*args)
|
17
|
+
return @retry_policy if args.empty?
|
18
|
+
@retry_policy = Cadence::RetryPolicy.new(args.first)
|
19
|
+
@retry_policy.validate!
|
20
|
+
end
|
21
|
+
|
22
|
+
def timeouts(*args)
|
23
|
+
return @timeouts if args.empty?
|
24
|
+
@timeouts = args.first
|
25
|
+
end
|
26
|
+
|
27
|
+
def headers(*args)
|
28
|
+
return @headers if args.empty?
|
29
|
+
@headers = args.first
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'dry-struct'
|
2
|
+
require 'cadence/types'
|
3
|
+
|
4
|
+
module Cadence
|
5
|
+
module Concerns
|
6
|
+
module Typed
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
attr_reader :input_class
|
13
|
+
|
14
|
+
def execute_in_context(context, input)
|
15
|
+
input = input_class[*input] if input_class
|
16
|
+
|
17
|
+
super(context, input)
|
18
|
+
end
|
19
|
+
|
20
|
+
def input(klass = nil, &block)
|
21
|
+
if klass
|
22
|
+
unless klass.is_a?(Dry::Types::Type)
|
23
|
+
raise 'Unsupported input class. Use one of the provided Cadence::Types'
|
24
|
+
end
|
25
|
+
@input_class = klass
|
26
|
+
else
|
27
|
+
@input_class = generate_struct
|
28
|
+
@input_class.instance_eval(&block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def generate_struct
|
35
|
+
Class.new(Dry::Struct::Value) { transform_keys(&:to_sym) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'cadence/metrics_adapters/null'
|
3
|
+
|
4
|
+
module Cadence
|
5
|
+
class Configuration
|
6
|
+
attr_reader :timeouts
|
7
|
+
attr_accessor :client_type, :host, :port, :logger, :metrics_adapter, :domain, :task_list, :headers
|
8
|
+
|
9
|
+
DEFAULT_TIMEOUTS = {
|
10
|
+
execution: 60, # End-to-end workflow time
|
11
|
+
task: 10, # Decision task processing time
|
12
|
+
schedule_to_close: nil, # End-to-end activity time (default: schedule_to_start + start_to_close)
|
13
|
+
schedule_to_start: 10, # Queue time for an activity
|
14
|
+
start_to_close: 30, # Time spent processing an activity
|
15
|
+
heartbeat: nil # Max time between heartbeats (off by default)
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
DEFAULT_HEADERS = {}.freeze
|
19
|
+
DEFAULT_DOMAIN = 'default-domain'.freeze
|
20
|
+
DEFAULT_TASK_LIST = 'default-task-list'.freeze
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@client_type = :thrift
|
24
|
+
@logger = Logger.new(STDOUT, progname: 'cadence_client')
|
25
|
+
@metrics_adapter = MetricsAdapters::Null.new
|
26
|
+
@timeouts = DEFAULT_TIMEOUTS
|
27
|
+
@domain = DEFAULT_DOMAIN
|
28
|
+
@task_list = DEFAULT_TASK_LIST
|
29
|
+
@headers = DEFAULT_HEADERS
|
30
|
+
end
|
31
|
+
|
32
|
+
def timeouts=(new_timeouts)
|
33
|
+
@timeouts = DEFAULT_TIMEOUTS.merge(new_timeouts)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Cadence
|
2
|
+
# Superclass for all Cadence errors
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
# Superclass for errors specific to Cadence worker itself
|
6
|
+
class InternalError < Error; end
|
7
|
+
|
8
|
+
# Indicates a non-deterministic workflow execution, might be due to
|
9
|
+
# a non-deterministic workflow implementation or the gem's bug
|
10
|
+
class NonDeterministicWorkflowError < InternalError; end
|
11
|
+
|
12
|
+
# Superclass for misconfiguration/misuse on the client (user) side
|
13
|
+
class ClientError < Error; end
|
14
|
+
|
15
|
+
# Represents any timeout
|
16
|
+
class TimeoutError < ClientError; end
|
17
|
+
|
18
|
+
# A superclass for activity exceptions raised explicitly
|
19
|
+
# with the itent to propagate to a workflow
|
20
|
+
class ActivityException < ClientError; end
|
21
|
+
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 Cadence
|
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,32 @@
|
|
1
|
+
require 'cadence/concerns/executable'
|
2
|
+
|
3
|
+
module Cadence
|
4
|
+
class ExecutionOptions
|
5
|
+
attr_reader :name, :domain, :task_list, :retry_policy, :timeouts, :headers
|
6
|
+
|
7
|
+
def initialize(object, options = {})
|
8
|
+
@name = options[:name] || object.to_s
|
9
|
+
@domain = options[:domain]
|
10
|
+
@task_list = options[:task_list]
|
11
|
+
@retry_policy = options[:retry_policy]
|
12
|
+
@cron_schedule = options[:cron_schedule]
|
13
|
+
@timeouts = options[:timeouts] || {}
|
14
|
+
@headers = options[:headers] || {}
|
15
|
+
|
16
|
+
if object.singleton_class.included_modules.include?(Concerns::Executable)
|
17
|
+
@domain ||= object.domain
|
18
|
+
@task_list ||= object.task_list
|
19
|
+
@retry_policy ||= object.retry_policy
|
20
|
+
@timeouts = object.timeouts.merge(@timeouts) if object.timeouts
|
21
|
+
@headers = object.headers.merge(@headers) if object.headers
|
22
|
+
end
|
23
|
+
|
24
|
+
@domain ||= Cadence.configuration.domain
|
25
|
+
@task_list ||= Cadence.configuration.task_list
|
26
|
+
@timeouts = Cadence.configuration.timeouts.merge(@timeouts)
|
27
|
+
@headers = Cadence.configuration.headers.merge(@headers)
|
28
|
+
|
29
|
+
freeze
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/cadence/json.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Helper class for serializing/deserializing JSON
|
2
|
+
require 'oj'
|
3
|
+
|
4
|
+
module Cadence
|
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,73 @@
|
|
1
|
+
require 'cadence/errors'
|
2
|
+
require 'cadence/metadata/activity'
|
3
|
+
require 'cadence/metadata/decision'
|
4
|
+
require 'cadence/metadata/workflow'
|
5
|
+
|
6
|
+
module Cadence
|
7
|
+
module Metadata
|
8
|
+
ACTIVITY_TYPE = :activity
|
9
|
+
DECISION_TYPE = :decision
|
10
|
+
WORKFLOW_TYPE = :workflow
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def generate(type, data, domain = nil)
|
14
|
+
case type
|
15
|
+
when ACTIVITY_TYPE
|
16
|
+
activity_metadata_from(data, domain)
|
17
|
+
when DECISION_TYPE
|
18
|
+
decision_metadata_from(data, domain)
|
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 activity_metadata_from(task, domain)
|
29
|
+
Metadata::Activity.new(
|
30
|
+
domain: domain,
|
31
|
+
id: task.activityId,
|
32
|
+
name: task.activityType.name,
|
33
|
+
task_token: task.taskToken,
|
34
|
+
attempt: task.attempt,
|
35
|
+
workflow_run_id: task.workflowExecution.runId,
|
36
|
+
workflow_id: task.workflowExecution.workflowId,
|
37
|
+
workflow_name: task.workflowType.name,
|
38
|
+
headers: task.header&.fields || {},
|
39
|
+
timeouts: {
|
40
|
+
start_to_close: task.startToCloseTimeoutSeconds,
|
41
|
+
schedule_to_close: task.scheduleToCloseTimeoutSeconds,
|
42
|
+
heartbeat: task.heartbeatTimeoutSeconds
|
43
|
+
}
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def decision_metadata_from(task, domain)
|
48
|
+
Metadata::Decision.new(
|
49
|
+
domain: domain,
|
50
|
+
id: task.startedEventId,
|
51
|
+
task_token: task.taskToken,
|
52
|
+
attempt: task.attempt,
|
53
|
+
workflow_run_id: task.workflowExecution.runId,
|
54
|
+
workflow_id: task.workflowExecution.workflowId,
|
55
|
+
workflow_name: task.workflowType.name
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def workflow_metadata_from(event)
|
60
|
+
Metadata::Workflow.new(
|
61
|
+
name: event.workflowType.name,
|
62
|
+
run_id: event.originalExecutionRunId,
|
63
|
+
attempt: event.attempt,
|
64
|
+
headers: event.header&.fields || {},
|
65
|
+
timeouts: {
|
66
|
+
execution: event.executionStartToCloseTimeoutSeconds,
|
67
|
+
task: event.taskStartToCloseTimeoutSeconds
|
68
|
+
}
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'cadence/metadata/base'
|
2
|
+
|
3
|
+
module Cadence
|
4
|
+
module Metadata
|
5
|
+
class Activity < Base
|
6
|
+
attr_reader :domain, :id, :name, :task_token, :attempt, :workflow_run_id, :workflow_id, :workflow_name, :headers, :timeouts
|
7
|
+
|
8
|
+
def initialize(domain:, id:, name:, task_token:, attempt:, workflow_run_id:, workflow_id:, workflow_name:, timeouts:, headers: {})
|
9
|
+
@domain = domain
|
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
|
+
@timeouts = timeouts
|
18
|
+
@headers = headers
|
19
|
+
|
20
|
+
freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
def activity?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'cadence/metadata/base'
|
2
|
+
|
3
|
+
module Cadence
|
4
|
+
module Metadata
|
5
|
+
class Decision < Base
|
6
|
+
attr_reader :domain, :id, :task_token, :attempt, :workflow_run_id, :workflow_id, :workflow_name, :timeouts
|
7
|
+
|
8
|
+
def initialize(domain:, id:, task_token:, attempt:, workflow_run_id:, workflow_id:, workflow_name:)
|
9
|
+
@domain = domain
|
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 decision?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'cadence/metadata/base'
|
2
|
+
|
3
|
+
module Cadence
|
4
|
+
module Metadata
|
5
|
+
class Workflow < Base
|
6
|
+
attr_reader :name, :run_id, :attempt, :headers, :timeouts
|
7
|
+
|
8
|
+
def initialize(name:, run_id:, attempt:, timeouts:, headers: {})
|
9
|
+
@name = name
|
10
|
+
@run_id = run_id
|
11
|
+
@attempt = attempt
|
12
|
+
@headers = headers
|
13
|
+
@timeouts = timeouts
|
14
|
+
|
15
|
+
freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
def workflow?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Cadence
|
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
|
+
Cadence.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
|
+
Cadence.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
|
+
Cadence.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 Cadence
|
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
|