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,34 @@
|
|
1
|
+
# This module provides a set of methods for imitating direct Child Workflow calls
|
2
|
+
# from within Workflows:
|
3
|
+
#
|
4
|
+
# class TestWorkflow < Cadence::Workflow
|
5
|
+
# def execute
|
6
|
+
# ChildWorkflow.execute!('foo', 'bar')
|
7
|
+
# end
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# This is analogous to calling:
|
11
|
+
#
|
12
|
+
# workflow.execute_workflow(ChildWorkflow, 'foo', 'bar')
|
13
|
+
#
|
14
|
+
require 'cadence/thread_local_context'
|
15
|
+
|
16
|
+
module Cadence
|
17
|
+
class Workflow
|
18
|
+
module ConvenienceMethods
|
19
|
+
def execute(*input, **args)
|
20
|
+
context = Cadence::ThreadLocalContext.get
|
21
|
+
raise 'Called Workflow#execute outside of a Workflow context' unless context
|
22
|
+
|
23
|
+
context.execute_workflow(self, *input, **args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute!(*input, **args)
|
27
|
+
context = Cadence::ThreadLocalContext.get
|
28
|
+
raise 'Called Workflow#execute! outside of a Workflow context' unless context
|
29
|
+
|
30
|
+
context.execute_workflow!(self, *input, **args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Cadence
|
2
|
+
class Workflow
|
3
|
+
module Decision
|
4
|
+
# TODO: Move these classes into their own directories under workflow/decision/*
|
5
|
+
ScheduleActivity = Struct.new(:activity_type, :activity_id, :input, :domain, :task_list, :retry_policy, :timeouts, :headers, keyword_init: true)
|
6
|
+
StartChildWorkflow = Struct.new(:workflow_type, :workflow_id, :input, :domain, :task_list, :retry_policy, :timeouts, :headers, keyword_init: true)
|
7
|
+
RequestActivityCancellation = Struct.new(:activity_id, keyword_init: true)
|
8
|
+
RecordMarker = Struct.new(:name, :details, keyword_init: true)
|
9
|
+
StartTimer = Struct.new(:timeout, :timer_id, keyword_init: true)
|
10
|
+
CancelTimer = Struct.new(:timer_id, keyword_init: true)
|
11
|
+
CompleteWorkflow = Struct.new(:result, keyword_init: true)
|
12
|
+
FailWorkflow = Struct.new(:reason, :details, keyword_init: true)
|
13
|
+
|
14
|
+
# only these decisions are supported right now
|
15
|
+
SCHEDULE_ACTIVITY_TYPE = :schedule_activity
|
16
|
+
START_CHILD_WORKFLOW_TYPE = :start_child_workflow
|
17
|
+
RECORD_MARKER_TYPE = :record_marker
|
18
|
+
START_TIMER_TYPE = :start_timer
|
19
|
+
CANCEL_TIMER_TYPE = :cancel_timer
|
20
|
+
COMPLETE_WORKFLOW_TYPE = :complete_workflow
|
21
|
+
FAIL_WORKFLOW_TYPE = :fail_workflow
|
22
|
+
|
23
|
+
DECISION_CLASS_MAP = {
|
24
|
+
SCHEDULE_ACTIVITY_TYPE => ScheduleActivity,
|
25
|
+
START_CHILD_WORKFLOW_TYPE => StartChildWorkflow,
|
26
|
+
RECORD_MARKER_TYPE => RecordMarker,
|
27
|
+
START_TIMER_TYPE => StartTimer,
|
28
|
+
CANCEL_TIMER_TYPE => CancelTimer,
|
29
|
+
COMPLETE_WORKFLOW_TYPE => CompleteWorkflow,
|
30
|
+
FAIL_WORKFLOW_TYPE => FailWorkflow
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
def self.generate(type, **args)
|
34
|
+
decision_class = DECISION_CLASS_MAP[type]
|
35
|
+
decision_class.new(**args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Cadence
|
2
|
+
class Workflow
|
3
|
+
class DecisionStateMachine
|
4
|
+
NEW_STATE = :new
|
5
|
+
REQUESTED_STATE = :requested
|
6
|
+
SCHEDULED_STATE = :scheduled
|
7
|
+
STARTED_STATE = :started
|
8
|
+
COMPLETED_STATE = :completed
|
9
|
+
CANCELED_STATE = :canceled
|
10
|
+
FAILED_STATE = :failed
|
11
|
+
TIMED_OUT_STATE = :timed_out
|
12
|
+
|
13
|
+
attr_reader :state
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@state = NEW_STATE
|
17
|
+
end
|
18
|
+
|
19
|
+
def requested
|
20
|
+
@state = REQUESTED_STATE
|
21
|
+
end
|
22
|
+
|
23
|
+
def schedule
|
24
|
+
@state = SCHEDULED_STATE
|
25
|
+
end
|
26
|
+
|
27
|
+
def start
|
28
|
+
@state = STARTED_STATE
|
29
|
+
end
|
30
|
+
|
31
|
+
def complete
|
32
|
+
@state = COMPLETED_STATE
|
33
|
+
end
|
34
|
+
|
35
|
+
def cancel
|
36
|
+
@state = CANCELED_STATE
|
37
|
+
end
|
38
|
+
|
39
|
+
def fail
|
40
|
+
@state = FAILED_STATE
|
41
|
+
end
|
42
|
+
|
43
|
+
def time_out
|
44
|
+
@state = TIMED_OUT_STATE
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'cadence/workflow/executor'
|
2
|
+
require 'cadence/workflow/history'
|
3
|
+
require 'cadence/workflow/serializer'
|
4
|
+
require 'cadence/metadata'
|
5
|
+
|
6
|
+
module Cadence
|
7
|
+
class Workflow
|
8
|
+
class DecisionTaskProcessor
|
9
|
+
MAX_FAILED_ATTEMPTS = 50
|
10
|
+
|
11
|
+
def initialize(task, domain, workflow_lookup, client, middleware_chain)
|
12
|
+
@task = task
|
13
|
+
@domain = domain
|
14
|
+
@task_token = task.taskToken
|
15
|
+
@workflow_name = task.workflowType.name
|
16
|
+
@workflow_class = workflow_lookup.find(workflow_name)
|
17
|
+
@client = client
|
18
|
+
@middleware_chain = middleware_chain
|
19
|
+
end
|
20
|
+
|
21
|
+
def process
|
22
|
+
start_time = Time.now
|
23
|
+
|
24
|
+
Cadence.logger.info("Processing a decision task for #{workflow_name}")
|
25
|
+
Cadence.metrics.timing('decision_task.queue_time', queue_time_ms, workflow: workflow_name)
|
26
|
+
|
27
|
+
unless workflow_class
|
28
|
+
fail_task('Workflow does not exist')
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
history = fetch_full_history
|
33
|
+
# TODO: For sticky workflows we need to cache the Executor instance
|
34
|
+
executor = Workflow::Executor.new(workflow_class, history)
|
35
|
+
metadata = Metadata.generate(Metadata::DECISION_TYPE, task, domain)
|
36
|
+
|
37
|
+
decisions = middleware_chain.invoke(metadata) do
|
38
|
+
executor.run
|
39
|
+
end
|
40
|
+
|
41
|
+
complete_task(decisions)
|
42
|
+
rescue StandardError => error
|
43
|
+
fail_task(error.inspect)
|
44
|
+
Cadence.logger.debug(error.backtrace.join("\n"))
|
45
|
+
ensure
|
46
|
+
time_diff_ms = ((Time.now - start_time) * 1000).round
|
47
|
+
Cadence.metrics.timing('decision_task.latency', time_diff_ms, workflow: workflow_name)
|
48
|
+
Cadence.logger.debug("Decision task processed in #{time_diff_ms}ms")
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_reader :task, :domain, :task_token, :workflow_name, :workflow_class, :client, :middleware_chain
|
54
|
+
|
55
|
+
def queue_time_ms
|
56
|
+
((task.startedTimestamp - task.scheduledTimestamp) / 1_000_000).round
|
57
|
+
end
|
58
|
+
|
59
|
+
def serialize_decisions(decisions)
|
60
|
+
decisions.map { |(_, decision)| Workflow::Serializer.serialize(decision) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def fetch_full_history
|
64
|
+
events = task.history.events.to_a
|
65
|
+
next_page_token = task.nextPageToken
|
66
|
+
|
67
|
+
while next_page_token do
|
68
|
+
response = client.get_workflow_execution_history(
|
69
|
+
domain: domain,
|
70
|
+
workflow_id: task.workflowExecution.workflowId,
|
71
|
+
run_id: task.workflowExecution.runId,
|
72
|
+
next_page_token: next_page_token
|
73
|
+
)
|
74
|
+
|
75
|
+
events += response.history.events.to_a
|
76
|
+
next_page_token = response.nextPageToken
|
77
|
+
end
|
78
|
+
|
79
|
+
Workflow::History.new(events)
|
80
|
+
end
|
81
|
+
|
82
|
+
def complete_task(decisions)
|
83
|
+
Cadence.logger.info("Decision task for #{workflow_name} completed")
|
84
|
+
|
85
|
+
client.respond_decision_task_completed(
|
86
|
+
task_token: task_token,
|
87
|
+
decisions: serialize_decisions(decisions)
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def fail_task(message)
|
92
|
+
Cadence.logger.error("Decision task for #{workflow_name} failed with: #{message}")
|
93
|
+
|
94
|
+
# Stop from getting into infinite loop if the error persists
|
95
|
+
return if task.attempt >= MAX_FAILED_ATTEMPTS
|
96
|
+
|
97
|
+
client.respond_decision_task_failed(
|
98
|
+
task_token: task_token,
|
99
|
+
cause: CadenceThrift::DecisionTaskFailedCause::UNHANDLED_DECISION,
|
100
|
+
details: message
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Cadence
|
2
|
+
class Workflow
|
3
|
+
class Dispatcher
|
4
|
+
WILDCARD = '*'.freeze
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@handlers = Hash.new { |hash, key| hash[key] = [] }
|
8
|
+
end
|
9
|
+
|
10
|
+
def register_handler(target, event_name, &handler)
|
11
|
+
handlers[target] << [event_name, handler]
|
12
|
+
end
|
13
|
+
|
14
|
+
def dispatch(target, event_name, args = nil)
|
15
|
+
handlers_for(target, event_name).each do |handler|
|
16
|
+
handler.call(*args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :handlers
|
23
|
+
|
24
|
+
def handlers_for(target, event_name)
|
25
|
+
handlers[target]
|
26
|
+
.select { |(name, _)| name == event_name || name == WILDCARD }
|
27
|
+
.map(&:last)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'cadence/utils'
|
2
|
+
|
3
|
+
module Cadence
|
4
|
+
class Workflow
|
5
|
+
class ExecutionInfo < Struct.new(:workflow, :workflow_id, :run_id, :start_time, :close_time, :status, :history_length, keyword_init: true)
|
6
|
+
RUNNING_STATUS = :RUNNING
|
7
|
+
COMPLETED_STATUS = :COMPLETED
|
8
|
+
FAILED_STATUS = :FAILED
|
9
|
+
CANCELED_STATUS = :CANCELED
|
10
|
+
TERMINATED_STATUS = :TERMINATED
|
11
|
+
CONTINUED_AS_NEW_STATUS = :CONTINUED_AS_NEW
|
12
|
+
TIMED_OUT_STATUS = :TIMED_OUT
|
13
|
+
|
14
|
+
VALID_STATUSES = [
|
15
|
+
RUNNING_STATUS,
|
16
|
+
COMPLETED_STATUS,
|
17
|
+
FAILED_STATUS,
|
18
|
+
CANCELED_STATUS,
|
19
|
+
TERMINATED_STATUS,
|
20
|
+
CONTINUED_AS_NEW_STATUS,
|
21
|
+
TIMED_OUT_STATUS
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
def self.generate_from(response)
|
25
|
+
status = ::CadenceThrift::WorkflowExecutionCloseStatus::VALUE_MAP[response.closeStatus]
|
26
|
+
|
27
|
+
new(
|
28
|
+
workflow: response.type.name,
|
29
|
+
workflow_id: response.execution.workflowId,
|
30
|
+
run_id: response.execution.runId,
|
31
|
+
start_time: Utils.time_from_nanos(response.startTime),
|
32
|
+
close_time: Utils.time_from_nanos(response.closeTime),
|
33
|
+
status: status&.to_sym || RUNNING_STATUS,
|
34
|
+
history_length: response.historyLength,
|
35
|
+
).freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
VALID_STATUSES.each do |status|
|
39
|
+
define_method("#{status.downcase}?") do
|
40
|
+
self.status == status
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'fiber'
|
2
|
+
|
3
|
+
require 'cadence/workflow/dispatcher'
|
4
|
+
require 'cadence/workflow/state_manager'
|
5
|
+
require 'cadence/workflow/context'
|
6
|
+
require 'cadence/workflow/history/event_target'
|
7
|
+
|
8
|
+
module Cadence
|
9
|
+
class Workflow
|
10
|
+
class Executor
|
11
|
+
def initialize(workflow_class, history)
|
12
|
+
@workflow_class = workflow_class
|
13
|
+
@dispatcher = Dispatcher.new
|
14
|
+
@state_manager = StateManager.new(dispatcher)
|
15
|
+
@history = history
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
dispatcher.register_handler(
|
20
|
+
History::EventTarget.workflow,
|
21
|
+
'started',
|
22
|
+
&method(:execute_workflow)
|
23
|
+
)
|
24
|
+
|
25
|
+
while window = history.next_window
|
26
|
+
state_manager.apply(window)
|
27
|
+
end
|
28
|
+
|
29
|
+
return state_manager.decisions
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :workflow_class, :dispatcher, :state_manager, :history
|
35
|
+
|
36
|
+
def execute_workflow(input, metadata)
|
37
|
+
context = Workflow::Context.new(state_manager, dispatcher, metadata)
|
38
|
+
|
39
|
+
Fiber.new do
|
40
|
+
workflow_class.execute_in_context(context, input)
|
41
|
+
end.resume
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'fiber'
|
2
|
+
|
3
|
+
module Cadence
|
4
|
+
class Workflow
|
5
|
+
class Future
|
6
|
+
attr_reader :target, :callbacks
|
7
|
+
|
8
|
+
def initialize(target, context, cancelation_id: nil)
|
9
|
+
@target = target
|
10
|
+
@context = context
|
11
|
+
@cancelation_id = cancelation_id
|
12
|
+
@callbacks = []
|
13
|
+
@ready = false
|
14
|
+
@result = nil
|
15
|
+
@failure = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def finished?
|
19
|
+
ready? || failed?
|
20
|
+
end
|
21
|
+
|
22
|
+
def ready?
|
23
|
+
@ready
|
24
|
+
end
|
25
|
+
|
26
|
+
def failed?
|
27
|
+
!!@failure
|
28
|
+
end
|
29
|
+
|
30
|
+
def wait
|
31
|
+
return if finished?
|
32
|
+
context.wait_for(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def get
|
36
|
+
wait
|
37
|
+
failure || result
|
38
|
+
end
|
39
|
+
|
40
|
+
def set(result)
|
41
|
+
raise 'can not fulfil a failed future' if failed?
|
42
|
+
|
43
|
+
@result = result
|
44
|
+
@ready = true
|
45
|
+
end
|
46
|
+
|
47
|
+
def fail(reason, details)
|
48
|
+
raise 'can not fail a fulfilled future' if ready?
|
49
|
+
|
50
|
+
@failure = [reason, details]
|
51
|
+
end
|
52
|
+
|
53
|
+
def done(&block)
|
54
|
+
# do nothing
|
55
|
+
return if failed?
|
56
|
+
|
57
|
+
if ready?
|
58
|
+
block.call(result)
|
59
|
+
else
|
60
|
+
callbacks << block
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def cancel
|
65
|
+
return false if finished?
|
66
|
+
|
67
|
+
context.cancel(target, cancelation_id)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
attr_reader :context, :cancelation_id, :result, :failure
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'cadence/workflow/history/event'
|
2
|
+
require 'cadence/workflow/history/window'
|
3
|
+
|
4
|
+
module Cadence
|
5
|
+
class Workflow
|
6
|
+
class History
|
7
|
+
attr_reader :events
|
8
|
+
|
9
|
+
def initialize(events)
|
10
|
+
@events = events.map { |event| History::Event.new(event) }
|
11
|
+
@iterator = @events.each
|
12
|
+
end
|
13
|
+
|
14
|
+
def last_completed_decision_task
|
15
|
+
events.select { |event| event.type == 'DecisionTaskCompleted' }.last
|
16
|
+
end
|
17
|
+
|
18
|
+
# It is very important to replay the History window by window in order to
|
19
|
+
# simulate the exact same state the workflow was in when it processed the
|
20
|
+
# decision task for the first time.
|
21
|
+
#
|
22
|
+
# A history window consists of 3 parts:
|
23
|
+
#
|
24
|
+
# 1. Events that happened since the last window (timer fired, activity completed, etc)
|
25
|
+
# 2. A decision task related events (decision task started, completed, failed, etc)
|
26
|
+
# 3. Commands issued by the last decision task (^) (schedule activity, start timer, etc)
|
27
|
+
#
|
28
|
+
def next_window
|
29
|
+
return unless peek_event
|
30
|
+
|
31
|
+
window = History::Window.new
|
32
|
+
|
33
|
+
while event = next_event
|
34
|
+
window.add(event)
|
35
|
+
|
36
|
+
break if event.type == 'DecisionTaskCompleted'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Find the end of the window by exhausting all the commands
|
40
|
+
window.add(next_event) while command?(peek_event)
|
41
|
+
|
42
|
+
window.freeze
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
COMMAND_EVENT_TYPES = %w[
|
48
|
+
ActivityTaskScheduled
|
49
|
+
ActivityTaskCancelRequested
|
50
|
+
TimerStarted
|
51
|
+
CancelTimerFailed
|
52
|
+
TimerCanceled
|
53
|
+
WorkflowExecutionCancelRequested
|
54
|
+
StartChildWorkflowExecutionInitiated
|
55
|
+
SignalExternalWorkflowExecutionInitiated
|
56
|
+
RequestCancelActivityTaskFailed
|
57
|
+
RequestCancelExternalWorkflowExecutionInitiated
|
58
|
+
MarkerRecorded
|
59
|
+
].freeze
|
60
|
+
|
61
|
+
attr_reader :iterator
|
62
|
+
|
63
|
+
def next_event
|
64
|
+
iterator.next rescue nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def peek_event
|
68
|
+
iterator.peek rescue nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def command?(event)
|
72
|
+
COMMAND_EVENT_TYPES.include?(event&.type)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|