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,44 @@
|
|
1
|
+
require 'cadence/testing/future_registry'
|
2
|
+
|
3
|
+
module Cadence
|
4
|
+
module Testing
|
5
|
+
class WorkflowExecution
|
6
|
+
attr_reader :status
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@status = Workflow::ExecutionInfo::RUNNING_STATUS
|
10
|
+
@futures = FutureRegistry.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(&block)
|
14
|
+
@fiber = Fiber.new(&block)
|
15
|
+
resume
|
16
|
+
end
|
17
|
+
|
18
|
+
def resume
|
19
|
+
fiber.resume
|
20
|
+
@status = Workflow::ExecutionInfo::COMPLETED_STATUS unless fiber.alive?
|
21
|
+
rescue StandardError
|
22
|
+
@status = Workflow::ExecutionInfo::FAILED_STATUS
|
23
|
+
end
|
24
|
+
|
25
|
+
def register_future(token, future)
|
26
|
+
futures.register(token, future)
|
27
|
+
end
|
28
|
+
|
29
|
+
def complete_activity(token, result)
|
30
|
+
futures.complete(token, result)
|
31
|
+
resume
|
32
|
+
end
|
33
|
+
|
34
|
+
def fail_activity(token, error)
|
35
|
+
futures.fail(token, error)
|
36
|
+
resume
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :fiber, :futures
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'cadence/testing/local_workflow_context'
|
3
|
+
require 'cadence/testing/workflow_execution'
|
4
|
+
|
5
|
+
module Cadence
|
6
|
+
module Testing
|
7
|
+
module WorkflowOverride
|
8
|
+
def disabled_releases
|
9
|
+
@disabled_releases ||= Set.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def allow_all_releases
|
13
|
+
disabled_releases.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
def allow_release(release_name)
|
17
|
+
disabled_releases.delete(release_name.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
def disable_release(release_name)
|
21
|
+
disabled_releases << release_name.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def execute_locally(*input)
|
25
|
+
workflow_id = SecureRandom.uuid
|
26
|
+
run_id = SecureRandom.uuid
|
27
|
+
execution = WorkflowExecution.new
|
28
|
+
context = Cadence::Testing::LocalWorkflowContext.new(
|
29
|
+
execution, workflow_id, run_id, disabled_releases
|
30
|
+
)
|
31
|
+
|
32
|
+
execute_in_context(context, input)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Provides context for Cadence::Activity::WorkflowConvenienceMethods
|
2
|
+
module Cadence
|
3
|
+
module ThreadLocalContext
|
4
|
+
WORKFLOW_CONTEXT_KEY = :cadence_workflow_context
|
5
|
+
|
6
|
+
def self.get
|
7
|
+
Thread.current[WORKFLOW_CONTEXT_KEY]
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.set(context)
|
11
|
+
Thread.current[WORKFLOW_CONTEXT_KEY] = context
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# This class implements a very simple ThreadPool with the ability to
|
2
|
+
# block until at least one thread becomes available. This allows Pollers
|
3
|
+
# to only poll when there's an available thread in the pool.
|
4
|
+
#
|
5
|
+
# NOTE: There's a minor race condition that can occur between calling
|
6
|
+
# #wait_for_available_threads and #schedule, but should be rare
|
7
|
+
#
|
8
|
+
module Cadence
|
9
|
+
class ThreadPool
|
10
|
+
attr_reader :size
|
11
|
+
|
12
|
+
def initialize(size)
|
13
|
+
@size = size
|
14
|
+
@queue = Queue.new
|
15
|
+
@mutex = Mutex.new
|
16
|
+
@availability = ConditionVariable.new
|
17
|
+
@available_threads = size
|
18
|
+
@pool = Array.new(size) do |i|
|
19
|
+
Thread.new { poll }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def wait_for_available_threads
|
24
|
+
@mutex.synchronize do
|
25
|
+
while @available_threads <= 0
|
26
|
+
@availability.wait(@mutex)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def schedule(&block)
|
32
|
+
@mutex.synchronize do
|
33
|
+
@available_threads -= 1
|
34
|
+
@queue << block
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def shutdown
|
39
|
+
size.times do
|
40
|
+
schedule { throw EXIT_SYMBOL }
|
41
|
+
end
|
42
|
+
|
43
|
+
@pool.each(&:join)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
EXIT_SYMBOL = :exit
|
49
|
+
|
50
|
+
def poll
|
51
|
+
catch(EXIT_SYMBOL) do
|
52
|
+
loop do
|
53
|
+
run(@queue.pop)
|
54
|
+
@mutex.synchronize do
|
55
|
+
@available_threads += 1
|
56
|
+
@availability.signal
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def run(job)
|
63
|
+
job.call if job
|
64
|
+
rescue Exception
|
65
|
+
# Make sure we don't loose a thread
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Cadence
|
2
|
+
module Utils
|
3
|
+
NANO = 10**9
|
4
|
+
MILLI = 10**3
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def time_from_nanos(timestamp)
|
8
|
+
seconds, nanoseconds = timestamp.divmod(NANO)
|
9
|
+
Time.at(seconds, nanoseconds, :nsec)
|
10
|
+
end
|
11
|
+
|
12
|
+
def time_to_nanos(time)
|
13
|
+
time.to_f * NANO
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/cadence/uuid.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# This is a simple UUIDv5 (SHA1) implementation adopted from:
|
2
|
+
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/digest/uuid.rb#L18
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
module Cadence
|
6
|
+
module UUID
|
7
|
+
def self.v5(uuid_namespace, name)
|
8
|
+
hash = Digest::SHA1.new
|
9
|
+
hash.update(uuid_namespace)
|
10
|
+
hash.update(name)
|
11
|
+
|
12
|
+
ary = hash.digest.unpack("NnnnnN")
|
13
|
+
ary[2] = (ary[2] & 0x0FFF) | (5 << 12)
|
14
|
+
ary[3] = (ary[3] & 0x3FFF) | 0x8000
|
15
|
+
|
16
|
+
"%08x-%04x-%04x-%04x-%04x%08x" % ary
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/cadence/version.rb
CHANGED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'cadence/client'
|
2
|
+
require 'cadence/workflow/poller'
|
3
|
+
require 'cadence/activity/poller'
|
4
|
+
require 'cadence/execution_options'
|
5
|
+
require 'cadence/executable_lookup'
|
6
|
+
require 'cadence/middleware/entry'
|
7
|
+
|
8
|
+
module Cadence
|
9
|
+
class Worker
|
10
|
+
def initialize(options = {})
|
11
|
+
@options = options
|
12
|
+
@workflows = Hash.new { |hash, key| hash[key] = ExecutableLookup.new }
|
13
|
+
@activities = Hash.new { |hash, key| hash[key] = ExecutableLookup.new }
|
14
|
+
@pollers = []
|
15
|
+
@decision_middleware = []
|
16
|
+
@activity_middleware = []
|
17
|
+
@shutting_down = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def register_workflow(workflow_class, options = {})
|
21
|
+
execution_options = ExecutionOptions.new(workflow_class, options)
|
22
|
+
key = [execution_options.domain, execution_options.task_list]
|
23
|
+
|
24
|
+
@workflows[key].add(execution_options.name, workflow_class)
|
25
|
+
end
|
26
|
+
|
27
|
+
def register_activity(activity_class, options = {})
|
28
|
+
execution_options = ExecutionOptions.new(activity_class, options)
|
29
|
+
key = [execution_options.domain, execution_options.task_list]
|
30
|
+
|
31
|
+
@activities[key].add(execution_options.name, activity_class)
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_decision_middleware(middleware_class, *args)
|
35
|
+
@decision_middleware << Middleware::Entry.new(middleware_class, args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_activity_middleware(middleware_class, *args)
|
39
|
+
@activity_middleware << Middleware::Entry.new(middleware_class, args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def start
|
43
|
+
workflows.each_pair do |(domain, task_list), lookup|
|
44
|
+
pollers << workflow_poller_for(domain, task_list, lookup)
|
45
|
+
end
|
46
|
+
|
47
|
+
activities.each_pair do |(domain, task_list), lookup|
|
48
|
+
pollers << activity_poller_for(domain, task_list, lookup)
|
49
|
+
end
|
50
|
+
|
51
|
+
trap_signals
|
52
|
+
|
53
|
+
pollers.each(&:start)
|
54
|
+
|
55
|
+
# keep the main worker thread alive
|
56
|
+
sleep(1) while !shutting_down?
|
57
|
+
end
|
58
|
+
|
59
|
+
def stop
|
60
|
+
@shutting_down = true
|
61
|
+
|
62
|
+
Thread.new do
|
63
|
+
pollers.each(&:stop)
|
64
|
+
pollers.each(&:wait)
|
65
|
+
end.join
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
attr_reader :options, :activities, :workflows, :pollers,
|
71
|
+
:decision_middleware, :activity_middleware
|
72
|
+
|
73
|
+
def shutting_down?
|
74
|
+
@shutting_down
|
75
|
+
end
|
76
|
+
|
77
|
+
def workflow_poller_for(domain, task_list, lookup)
|
78
|
+
Workflow::Poller.new(domain, task_list, lookup.freeze, decision_middleware, options)
|
79
|
+
end
|
80
|
+
|
81
|
+
def activity_poller_for(domain, task_list, lookup)
|
82
|
+
Activity::Poller.new(domain, task_list, lookup.freeze, activity_middleware, options)
|
83
|
+
end
|
84
|
+
|
85
|
+
def trap_signals
|
86
|
+
%w[TERM INT].each do |signal|
|
87
|
+
Signal.trap(signal) { stop }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'cadence/concerns/executable'
|
2
|
+
require 'cadence/workflow/convenience_methods'
|
3
|
+
require 'cadence/thread_local_context'
|
4
|
+
|
5
|
+
module Cadence
|
6
|
+
class Workflow
|
7
|
+
extend Concerns::Executable
|
8
|
+
extend ConvenienceMethods
|
9
|
+
|
10
|
+
def self.execute_in_context(context, input)
|
11
|
+
Cadence::ThreadLocalContext.set(context)
|
12
|
+
|
13
|
+
workflow = new(context)
|
14
|
+
result = workflow.execute(*input)
|
15
|
+
|
16
|
+
context.complete(result)
|
17
|
+
rescue StandardError, ScriptError => error
|
18
|
+
Cadence.logger.error("Workflow execution failed with: #{error.inspect}")
|
19
|
+
Cadence.logger.debug(error.backtrace.join("\n"))
|
20
|
+
|
21
|
+
context.fail(error.class.name, error.message)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(context)
|
25
|
+
@context = context
|
26
|
+
end
|
27
|
+
|
28
|
+
def execute
|
29
|
+
raise NotImplementedError, '#execute method must be implemented by a subclass'
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def workflow
|
35
|
+
@context
|
36
|
+
end
|
37
|
+
|
38
|
+
def logger
|
39
|
+
workflow.logger
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
require 'cadence/execution_options'
|
4
|
+
require 'cadence/errors'
|
5
|
+
require 'cadence/thread_local_context'
|
6
|
+
require 'cadence/workflow/history/event_target'
|
7
|
+
require 'cadence/workflow/decision'
|
8
|
+
require 'cadence/workflow/future'
|
9
|
+
require 'cadence/workflow/replay_aware_logger'
|
10
|
+
require 'cadence/workflow/state_manager'
|
11
|
+
|
12
|
+
# This context class is available in the workflow implementation
|
13
|
+
# and provides context and methods for interacting with Cadence
|
14
|
+
#
|
15
|
+
module Cadence
|
16
|
+
class Workflow
|
17
|
+
class Context
|
18
|
+
def initialize(state_manager, dispatcher, metadata)
|
19
|
+
@state_manager = state_manager
|
20
|
+
@dispatcher = dispatcher
|
21
|
+
@metadata = metadata
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger
|
25
|
+
@logger ||= ReplayAwareLogger.new(Cadence.logger)
|
26
|
+
@logger.replay = state_manager.replay?
|
27
|
+
@logger
|
28
|
+
end
|
29
|
+
|
30
|
+
def headers
|
31
|
+
metadata.headers
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_release?(release_name)
|
35
|
+
state_manager.release?(release_name.to_s)
|
36
|
+
end
|
37
|
+
|
38
|
+
def execute_activity(activity_class, *input, **args)
|
39
|
+
options = args.delete(:options) || {}
|
40
|
+
input << args unless args.empty?
|
41
|
+
|
42
|
+
execution_options = ExecutionOptions.new(activity_class, options)
|
43
|
+
|
44
|
+
decision = Decision::ScheduleActivity.new(
|
45
|
+
activity_id: options[:activity_id],
|
46
|
+
activity_type: execution_options.name,
|
47
|
+
input: input,
|
48
|
+
domain: execution_options.domain,
|
49
|
+
task_list: execution_options.task_list,
|
50
|
+
retry_policy: execution_options.retry_policy,
|
51
|
+
timeouts: execution_options.timeouts,
|
52
|
+
headers: execution_options.headers
|
53
|
+
)
|
54
|
+
|
55
|
+
target, cancelation_id = schedule_decision(decision)
|
56
|
+
future = Future.new(target, self, cancelation_id: cancelation_id)
|
57
|
+
|
58
|
+
dispatcher.register_handler(target, 'completed') do |result|
|
59
|
+
future.set(result)
|
60
|
+
future.callbacks.each { |callback| call_in_fiber(callback, result) }
|
61
|
+
end
|
62
|
+
|
63
|
+
dispatcher.register_handler(target, 'failed') do |reason, details|
|
64
|
+
future.fail(reason, details)
|
65
|
+
end
|
66
|
+
|
67
|
+
future
|
68
|
+
end
|
69
|
+
|
70
|
+
def execute_activity!(activity_class, *input, **args)
|
71
|
+
future = execute_activity(activity_class, *input, **args)
|
72
|
+
result = future.get
|
73
|
+
|
74
|
+
if future.failed?
|
75
|
+
reason, details = result
|
76
|
+
|
77
|
+
error_class = safe_constantize(reason) || Cadence::ActivityException
|
78
|
+
|
79
|
+
raise error_class, details
|
80
|
+
end
|
81
|
+
|
82
|
+
result
|
83
|
+
end
|
84
|
+
|
85
|
+
# TODO: how to handle failures?
|
86
|
+
def execute_local_activity(activity_class, *input, **args)
|
87
|
+
input << args unless args.empty?
|
88
|
+
|
89
|
+
side_effect do
|
90
|
+
# TODO: this probably requires a local context implementation
|
91
|
+
context = Activity::Context.new(nil, nil)
|
92
|
+
activity_class.execute_in_context(context, input)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def execute_workflow(workflow_class, *input, **args)
|
97
|
+
options = args.delete(:options) || {}
|
98
|
+
input << args unless args.empty?
|
99
|
+
|
100
|
+
execution_options = ExecutionOptions.new(workflow_class, options)
|
101
|
+
|
102
|
+
decision = Decision::StartChildWorkflow.new(
|
103
|
+
workflow_id: options[:workflow_id] || SecureRandom.uuid,
|
104
|
+
workflow_type: execution_options.name,
|
105
|
+
input: input,
|
106
|
+
domain: execution_options.domain,
|
107
|
+
task_list: execution_options.task_list,
|
108
|
+
retry_policy: execution_options.retry_policy,
|
109
|
+
timeouts: execution_options.timeouts,
|
110
|
+
headers: execution_options.headers
|
111
|
+
)
|
112
|
+
|
113
|
+
target, cancelation_id = schedule_decision(decision)
|
114
|
+
future = Future.new(target, self, cancelation_id: cancelation_id)
|
115
|
+
|
116
|
+
dispatcher.register_handler(target, 'completed') do |result|
|
117
|
+
future.set(result)
|
118
|
+
future.callbacks.each { |callback| call_in_fiber(callback, result) }
|
119
|
+
end
|
120
|
+
|
121
|
+
dispatcher.register_handler(target, 'failed') do |reason, details|
|
122
|
+
future.fail(reason, details)
|
123
|
+
end
|
124
|
+
|
125
|
+
future
|
126
|
+
end
|
127
|
+
|
128
|
+
def execute_workflow!(workflow_class, *input, **args)
|
129
|
+
future = execute_workflow(workflow_class, *input, **args)
|
130
|
+
result = future.get
|
131
|
+
|
132
|
+
if future.failed?
|
133
|
+
reason, details = result
|
134
|
+
|
135
|
+
error_class = safe_constantize(reason) || StandardError.new(details)
|
136
|
+
|
137
|
+
raise error_class, details
|
138
|
+
end
|
139
|
+
|
140
|
+
result
|
141
|
+
end
|
142
|
+
|
143
|
+
def side_effect(&block)
|
144
|
+
marker = state_manager.next_side_effect
|
145
|
+
return marker.last if marker
|
146
|
+
|
147
|
+
result = block.call
|
148
|
+
decision = Decision::RecordMarker.new(name: StateManager::SIDE_EFFECT_MARKER, details: result)
|
149
|
+
schedule_decision(decision)
|
150
|
+
|
151
|
+
result
|
152
|
+
end
|
153
|
+
|
154
|
+
def sleep(timeout)
|
155
|
+
start_timer(timeout).wait
|
156
|
+
end
|
157
|
+
|
158
|
+
def sleep_until(end_time)
|
159
|
+
delay = (end_time.to_time - now).to_i
|
160
|
+
sleep(delay) if delay > 0
|
161
|
+
end
|
162
|
+
|
163
|
+
def start_timer(timeout, timer_id = nil)
|
164
|
+
decision = Decision::StartTimer.new(timeout: timeout, timer_id: timer_id)
|
165
|
+
target, cancelation_id = schedule_decision(decision)
|
166
|
+
future = Future.new(target, self, cancelation_id: cancelation_id)
|
167
|
+
|
168
|
+
dispatcher.register_handler(target, 'fired') do |result|
|
169
|
+
future.set(result)
|
170
|
+
future.callbacks.each { |callback| call_in_fiber(callback, result) }
|
171
|
+
end
|
172
|
+
|
173
|
+
dispatcher.register_handler(target, 'canceled') do |reason, details|
|
174
|
+
future.fail(reason, details)
|
175
|
+
end
|
176
|
+
|
177
|
+
future
|
178
|
+
end
|
179
|
+
|
180
|
+
def cancel_timer(timer_id)
|
181
|
+
decision = Decision::CancelTimer.new(timer_id: timer_id)
|
182
|
+
schedule_decision(decision)
|
183
|
+
end
|
184
|
+
|
185
|
+
# TODO: check if workflow can be completed
|
186
|
+
def complete(result = nil)
|
187
|
+
decision = Decision::CompleteWorkflow.new(result: result)
|
188
|
+
schedule_decision(decision)
|
189
|
+
end
|
190
|
+
|
191
|
+
# TODO: check if workflow can be failed
|
192
|
+
def fail(reason, details = nil)
|
193
|
+
decision = Decision::FailWorkflow.new(reason: reason, details: details)
|
194
|
+
schedule_decision(decision)
|
195
|
+
end
|
196
|
+
|
197
|
+
def wait_for_all(*futures)
|
198
|
+
futures.each(&:wait)
|
199
|
+
|
200
|
+
return
|
201
|
+
end
|
202
|
+
|
203
|
+
def wait_for(future)
|
204
|
+
fiber = Fiber.current
|
205
|
+
|
206
|
+
dispatcher.register_handler(future.target, Dispatcher::WILDCARD) do
|
207
|
+
fiber.resume if future.finished?
|
208
|
+
end
|
209
|
+
|
210
|
+
Fiber.yield
|
211
|
+
|
212
|
+
return
|
213
|
+
end
|
214
|
+
|
215
|
+
def now
|
216
|
+
state_manager.local_time
|
217
|
+
end
|
218
|
+
|
219
|
+
def on_signal(&block)
|
220
|
+
target = History::EventTarget.workflow
|
221
|
+
|
222
|
+
dispatcher.register_handler(target, 'signaled') do |signal, input|
|
223
|
+
call_in_fiber(block, signal, input)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def cancel_activity(activity_id)
|
228
|
+
decision = Decision::RequestActivityCancellation.new(activity_id: activity_id)
|
229
|
+
|
230
|
+
schedule_decision(decision)
|
231
|
+
end
|
232
|
+
|
233
|
+
def cancel(target, cancelation_id)
|
234
|
+
case target.type
|
235
|
+
when History::EventTarget::ACTIVITY_TYPE
|
236
|
+
cancel_activity(cancelation_id)
|
237
|
+
when History::EventTarget::TIMER_TYPE
|
238
|
+
cancel_timer(cancelation_id)
|
239
|
+
else
|
240
|
+
raise "#{target} can not be canceled"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
|
246
|
+
attr_reader :state_manager, :dispatcher, :metadata
|
247
|
+
|
248
|
+
def schedule_decision(decision)
|
249
|
+
state_manager.schedule(decision)
|
250
|
+
end
|
251
|
+
|
252
|
+
def call_in_fiber(block, *args)
|
253
|
+
Fiber.new do
|
254
|
+
Cadence::ThreadLocalContext.set(self)
|
255
|
+
block.call(*args)
|
256
|
+
end.resume
|
257
|
+
end
|
258
|
+
|
259
|
+
def safe_constantize(const)
|
260
|
+
Object.const_get(const) if Object.const_defined?(const)
|
261
|
+
rescue NameError
|
262
|
+
nil
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|