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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +456 -0
  3. data/cadence.gemspec +9 -2
  4. data/lib/cadence-ruby.rb +1 -0
  5. data/lib/cadence.rb +176 -0
  6. data/lib/cadence/activity.rb +33 -0
  7. data/lib/cadence/activity/async_token.rb +34 -0
  8. data/lib/cadence/activity/context.rb +64 -0
  9. data/lib/cadence/activity/poller.rb +89 -0
  10. data/lib/cadence/activity/task_processor.rb +73 -0
  11. data/lib/cadence/activity/workflow_convenience_methods.rb +41 -0
  12. data/lib/cadence/client.rb +21 -0
  13. data/lib/cadence/client/errors.rb +8 -0
  14. data/lib/cadence/client/thrift_client.rb +380 -0
  15. data/lib/cadence/concerns/executable.rb +33 -0
  16. data/lib/cadence/concerns/typed.rb +40 -0
  17. data/lib/cadence/configuration.rb +36 -0
  18. data/lib/cadence/errors.rb +21 -0
  19. data/lib/cadence/executable_lookup.rb +25 -0
  20. data/lib/cadence/execution_options.rb +32 -0
  21. data/lib/cadence/json.rb +18 -0
  22. data/lib/cadence/metadata.rb +73 -0
  23. data/lib/cadence/metadata/activity.rb +28 -0
  24. data/lib/cadence/metadata/base.rb +17 -0
  25. data/lib/cadence/metadata/decision.rb +25 -0
  26. data/lib/cadence/metadata/workflow.rb +23 -0
  27. data/lib/cadence/metrics.rb +37 -0
  28. data/lib/cadence/metrics_adapters/log.rb +33 -0
  29. data/lib/cadence/metrics_adapters/null.rb +9 -0
  30. data/lib/cadence/middleware/chain.rb +30 -0
  31. data/lib/cadence/middleware/entry.rb +9 -0
  32. data/lib/cadence/retry_policy.rb +27 -0
  33. data/lib/cadence/saga/concern.rb +37 -0
  34. data/lib/cadence/saga/result.rb +22 -0
  35. data/lib/cadence/saga/saga.rb +24 -0
  36. data/lib/cadence/testing.rb +50 -0
  37. data/lib/cadence/testing/cadence_override.rb +112 -0
  38. data/lib/cadence/testing/future_registry.rb +27 -0
  39. data/lib/cadence/testing/local_activity_context.rb +17 -0
  40. data/lib/cadence/testing/local_workflow_context.rb +207 -0
  41. data/lib/cadence/testing/workflow_execution.rb +44 -0
  42. data/lib/cadence/testing/workflow_override.rb +36 -0
  43. data/lib/cadence/thread_local_context.rb +14 -0
  44. data/lib/cadence/thread_pool.rb +68 -0
  45. data/lib/cadence/types.rb +7 -0
  46. data/lib/cadence/utils.rb +17 -0
  47. data/lib/cadence/uuid.rb +19 -0
  48. data/lib/cadence/version.rb +1 -1
  49. data/lib/cadence/worker.rb +91 -0
  50. data/lib/cadence/workflow.rb +42 -0
  51. data/lib/cadence/workflow/context.rb +266 -0
  52. data/lib/cadence/workflow/convenience_methods.rb +34 -0
  53. data/lib/cadence/workflow/decision.rb +39 -0
  54. data/lib/cadence/workflow/decision_state_machine.rb +48 -0
  55. data/lib/cadence/workflow/decision_task_processor.rb +105 -0
  56. data/lib/cadence/workflow/dispatcher.rb +31 -0
  57. data/lib/cadence/workflow/execution_info.rb +45 -0
  58. data/lib/cadence/workflow/executor.rb +45 -0
  59. data/lib/cadence/workflow/future.rb +75 -0
  60. data/lib/cadence/workflow/history.rb +76 -0
  61. data/lib/cadence/workflow/history/event.rb +71 -0
  62. data/lib/cadence/workflow/history/event_target.rb +79 -0
  63. data/lib/cadence/workflow/history/window.rb +40 -0
  64. data/lib/cadence/workflow/poller.rb +74 -0
  65. data/lib/cadence/workflow/replay_aware_logger.rb +36 -0
  66. data/lib/cadence/workflow/serializer.rb +31 -0
  67. data/lib/cadence/workflow/serializer/base.rb +22 -0
  68. data/lib/cadence/workflow/serializer/cancel_timer.rb +19 -0
  69. data/lib/cadence/workflow/serializer/complete_workflow.rb +20 -0
  70. data/lib/cadence/workflow/serializer/fail_workflow.rb +21 -0
  71. data/lib/cadence/workflow/serializer/record_marker.rb +21 -0
  72. data/lib/cadence/workflow/serializer/request_activity_cancellation.rb +19 -0
  73. data/lib/cadence/workflow/serializer/schedule_activity.rb +54 -0
  74. data/lib/cadence/workflow/serializer/start_child_workflow.rb +52 -0
  75. data/lib/cadence/workflow/serializer/start_timer.rb +20 -0
  76. data/lib/cadence/workflow/state_manager.rb +324 -0
  77. data/lib/gen/thrift/cadence_constants.rb +11 -0
  78. data/lib/gen/thrift/cadence_types.rb +11 -0
  79. data/lib/gen/thrift/shared_constants.rb +11 -0
  80. data/lib/gen/thrift/shared_types.rb +4600 -0
  81. data/lib/gen/thrift/workflow_service.rb +3142 -0
  82. data/rbi/cadence-ruby.rbi +39 -0
  83. metadata +152 -5
@@ -0,0 +1,19 @@
1
+ require 'cadence/workflow/serializer/base'
2
+
3
+ module Cadence
4
+ class Workflow
5
+ module Serializer
6
+ class RequestActivityCancellation < Base
7
+ def to_thrift
8
+ CadenceThrift::Decision.new(
9
+ decisionType: CadenceThrift::DecisionType::RequestCancelActivityTask,
10
+ requestCancelActivityTaskDecisionAttributes:
11
+ CadenceThrift::RequestCancelActivityTaskDecisionAttributes.new(
12
+ activityId: object.activity_id.to_s
13
+ )
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,54 @@
1
+ require 'cadence/workflow/serializer/base'
2
+ require 'cadence/json'
3
+
4
+ module Cadence
5
+ class Workflow
6
+ module Serializer
7
+ class ScheduleActivity < Base
8
+ def to_thrift
9
+ CadenceThrift::Decision.new(
10
+ decisionType: CadenceThrift::DecisionType::ScheduleActivityTask,
11
+ scheduleActivityTaskDecisionAttributes:
12
+ CadenceThrift::ScheduleActivityTaskDecisionAttributes.new(
13
+ activityId: object.activity_id.to_s,
14
+ activityType: CadenceThrift::ActivityType.new(name: object.activity_type),
15
+ input: JSON.serialize(object.input),
16
+ domain: object.domain,
17
+ taskList: CadenceThrift::TaskList.new(name: object.task_list),
18
+ scheduleToCloseTimeoutSeconds: object.timeouts[:schedule_to_close],
19
+ scheduleToStartTimeoutSeconds: object.timeouts[:schedule_to_start],
20
+ startToCloseTimeoutSeconds: object.timeouts[:start_to_close],
21
+ heartbeatTimeoutSeconds: object.timeouts[:heartbeat],
22
+ retryPolicy: serialize_retry_policy(object.retry_policy),
23
+ header: serialize_headers(object.headers)
24
+ )
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ def serialize_retry_policy(retry_policy)
31
+ return unless retry_policy
32
+
33
+ non_retriable_errors = Array(retry_policy.non_retriable_errors).map(&:name)
34
+ options = {
35
+ initialIntervalInSeconds: retry_policy.interval,
36
+ backoffCoefficient: retry_policy.backoff,
37
+ maximumIntervalInSeconds: retry_policy.max_interval,
38
+ maximumAttempts: retry_policy.max_attempts,
39
+ nonRetriableErrorReasons: non_retriable_errors,
40
+ expirationIntervalInSeconds: retry_policy.expiration_interval
41
+ }.compact
42
+
43
+ CadenceThrift::RetryPolicy.new(options)
44
+ end
45
+
46
+ def serialize_headers(headers)
47
+ return unless headers
48
+
49
+ CadenceThrift::Header.new(fields: object.headers)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,52 @@
1
+ require 'cadence/workflow/serializer/base'
2
+ require 'cadence/json'
3
+
4
+ module Cadence
5
+ class Workflow
6
+ module Serializer
7
+ class StartChildWorkflow < Base
8
+ def to_thrift
9
+ CadenceThrift::Decision.new(
10
+ decisionType: CadenceThrift::DecisionType::StartChildWorkflowExecution,
11
+ startChildWorkflowExecutionDecisionAttributes:
12
+ CadenceThrift::StartChildWorkflowExecutionDecisionAttributes.new(
13
+ domain: object.domain,
14
+ workflowId: object.workflow_id.to_s,
15
+ workflowType: CadenceThrift::WorkflowType.new(name: object.workflow_type),
16
+ taskList: CadenceThrift::TaskList.new(name: object.task_list),
17
+ input: JSON.serialize(object.input),
18
+ executionStartToCloseTimeoutSeconds: object.timeouts[:execution],
19
+ taskStartToCloseTimeoutSeconds: object.timeouts[:task],
20
+ retryPolicy: serialize_retry_policy(object.retry_policy),
21
+ header: serialize_headers(object.headers)
22
+ )
23
+ )
24
+ end
25
+
26
+ private
27
+
28
+ def serialize_retry_policy(retry_policy)
29
+ return unless retry_policy
30
+
31
+ non_retriable_errors = Array(retry_policy.non_retriable_errors).map(&:name)
32
+ options = {
33
+ initialIntervalInSeconds: retry_policy.interval,
34
+ backoffCoefficient: retry_policy.backoff,
35
+ maximumIntervalInSeconds: retry_policy.max_interval,
36
+ maximumAttempts: retry_policy.max_attempts,
37
+ nonRetriableErrorReasons: non_retriable_errors,
38
+ expirationIntervalInSeconds: retry_policy.expiration_interval
39
+ }.compact
40
+
41
+ CadenceThrift::RetryPolicy.new(options)
42
+ end
43
+
44
+ def serialize_headers(headers)
45
+ return unless headers
46
+
47
+ CadenceThrift::Header.new(fields: object.headers)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,20 @@
1
+ require 'cadence/workflow/serializer/base'
2
+
3
+ module Cadence
4
+ class Workflow
5
+ module Serializer
6
+ class StartTimer < Base
7
+ def to_thrift
8
+ CadenceThrift::Decision.new(
9
+ decisionType: CadenceThrift::DecisionType::StartTimer,
10
+ startTimerDecisionAttributes:
11
+ CadenceThrift::StartTimerDecisionAttributes.new(
12
+ timerId: object.timer_id.to_s,
13
+ startToFireTimeoutSeconds: object.timeout.to_i
14
+ )
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,324 @@
1
+ require 'cadence/json'
2
+ require 'cadence/errors'
3
+ require 'cadence/workflow/decision'
4
+ require 'cadence/workflow/decision_state_machine'
5
+ require 'cadence/workflow/history/event_target'
6
+ require 'cadence/metadata'
7
+
8
+ module Cadence
9
+ class Workflow
10
+ class StateManager
11
+ SIDE_EFFECT_MARKER = 'SIDE_EFFECT'.freeze
12
+ RELEASE_MARKER = 'RELEASE'.freeze
13
+
14
+ class UnsupportedEvent < Cadence::InternalError; end
15
+ class UnsupportedMarkerType < Cadence::InternalError; end
16
+
17
+ attr_reader :decisions, :local_time
18
+
19
+ def initialize(dispatcher)
20
+ @dispatcher = dispatcher
21
+ @decisions = []
22
+ @marker_ids = Set.new
23
+ @releases = {}
24
+ @side_effects = []
25
+ @decision_tracker = Hash.new { |hash, key| hash[key] = DecisionStateMachine.new }
26
+ @last_event_id = 0
27
+ @local_time = nil
28
+ @replay = false
29
+ end
30
+
31
+ def replay?
32
+ @replay
33
+ end
34
+
35
+ def schedule(decision)
36
+ # Fast-forward event IDs to skip all the markers (version markers can
37
+ # be removed, so we can't rely on them being scheduled during a replay)
38
+ decision_id = next_event_id
39
+ while marker_ids.include?(decision_id) do
40
+ decision_id = next_event_id
41
+ end
42
+
43
+ cancelation_id =
44
+ case decision
45
+ when Decision::ScheduleActivity
46
+ decision.activity_id ||= decision_id
47
+ when Decision::StartChildWorkflow
48
+ decision.workflow_id ||= decision_id
49
+ when Decision::StartTimer
50
+ decision.timer_id ||= decision_id
51
+ end
52
+
53
+ state_machine = decision_tracker[decision_id]
54
+ state_machine.requested if state_machine.state == DecisionStateMachine::NEW_STATE
55
+
56
+ decisions << [decision_id, decision]
57
+
58
+ return [event_target_from(decision_id, decision), cancelation_id]
59
+ end
60
+
61
+ def release?(release_name)
62
+ track_release(release_name) unless releases.key?(release_name)
63
+
64
+ releases[release_name]
65
+ end
66
+
67
+ def next_side_effect
68
+ side_effects.shift
69
+ end
70
+
71
+ def apply(history_window)
72
+ @replay = history_window.replay?
73
+ @local_time = history_window.local_time
74
+ @last_event_id = history_window.last_event_id
75
+
76
+ # handle markers first since their data is needed for processing events
77
+ history_window.markers.each do |event|
78
+ apply_event(event)
79
+ end
80
+
81
+ history_window.events.each do |event|
82
+ apply_event(event)
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ attr_reader :dispatcher, :decision_tracker, :marker_ids, :side_effects, :releases
89
+
90
+ def next_event_id
91
+ @last_event_id += 1
92
+ end
93
+
94
+ def apply_event(event)
95
+ state_machine = decision_tracker[event.decision_id]
96
+ target = History::EventTarget.from_event(event)
97
+
98
+ case event.type
99
+ when 'WorkflowExecutionStarted'
100
+ state_machine.start
101
+ dispatch(
102
+ History::EventTarget.workflow,
103
+ 'started',
104
+ safe_parse(event.attributes.input),
105
+ Metadata.generate(Metadata::WORKFLOW_TYPE, event.attributes)
106
+ )
107
+
108
+ when 'WorkflowExecutionCompleted'
109
+ # todo
110
+
111
+ when 'WorkflowExecutionFailed'
112
+ # todo
113
+
114
+ when 'WorkflowExecutionTimedOut'
115
+ # todo
116
+
117
+ when 'DecisionTaskScheduled'
118
+ # todo
119
+
120
+ when 'DecisionTaskStarted'
121
+ # todo
122
+
123
+ when 'DecisionTaskCompleted'
124
+ # todo
125
+
126
+ when 'DecisionTaskTimedOut'
127
+ # todo
128
+
129
+ when 'DecisionTaskFailed'
130
+ # todo
131
+
132
+ when 'ActivityTaskScheduled'
133
+ state_machine.schedule
134
+ discard_decision(target)
135
+
136
+ when 'ActivityTaskStarted'
137
+ state_machine.start
138
+
139
+ when 'ActivityTaskCompleted'
140
+ state_machine.complete
141
+ dispatch(target, 'completed', safe_parse(event.attributes.result))
142
+
143
+ when 'ActivityTaskFailed'
144
+ state_machine.fail
145
+ dispatch(target, 'failed', event.attributes.reason, safe_parse(event.attributes.details))
146
+
147
+ when 'ActivityTaskTimedOut'
148
+ state_machine.time_out
149
+ type = CadenceThrift::TimeoutType::VALUE_MAP[event.attributes.timeoutType]
150
+ dispatch(target, 'failed', 'Cadence::TimeoutError', "Timeout type: #{type}")
151
+
152
+ when 'ActivityTaskCancelRequested'
153
+ state_machine.requested
154
+ discard_decision(target)
155
+
156
+ when 'RequestCancelActivityTaskFailed'
157
+ state_machine.fail
158
+ dispatch(target, 'failed', event.attributes.cause, nil)
159
+
160
+ when 'ActivityTaskCanceled'
161
+ state_machine.cancel
162
+ dispatch(target, 'failed', 'CANCELLED', safe_parse(event.attributes.details))
163
+
164
+ when 'TimerStarted'
165
+ state_machine.start
166
+ discard_decision(target)
167
+
168
+ when 'TimerFired'
169
+ state_machine.complete
170
+ dispatch(target, 'fired')
171
+
172
+ when 'CancelTimerFailed'
173
+ state_machine.failed
174
+ dispatch(target, 'failed', event.attributes.cause, nil)
175
+
176
+ when 'TimerCanceled'
177
+ state_machine.cancel
178
+ dispatch(target, 'canceled')
179
+
180
+ when 'WorkflowExecutionCancelRequested'
181
+ # todo
182
+
183
+ when 'WorkflowExecutionCanceled'
184
+ # todo
185
+
186
+ when 'RequestCancelExternalWorkflowExecutionInitiated'
187
+ # todo
188
+
189
+ when 'RequestCancelExternalWorkflowExecutionFailed'
190
+ # todo
191
+
192
+ when 'ExternalWorkflowExecutionCancelRequested'
193
+ # todo
194
+
195
+ when 'MarkerRecorded'
196
+ state_machine.complete
197
+ handle_marker(event.id, event.attributes.markerName, safe_parse(event.attributes.details))
198
+
199
+ when 'WorkflowExecutionSignaled'
200
+ dispatch(target, 'signaled', event.attributes.signalName, safe_parse(event.attributes.input))
201
+
202
+ when 'WorkflowExecutionTerminated'
203
+ # todo
204
+
205
+ when 'WorkflowExecutionContinuedAsNew'
206
+ # todo
207
+
208
+ when 'StartChildWorkflowExecutionInitiated'
209
+ state_machine.schedule
210
+ discard_decision(target)
211
+
212
+ when 'StartChildWorkflowExecutionFailed'
213
+ state_machine.fail
214
+ dispatch(target, 'failed', 'StandardError', safe_parse(event.attributes.cause))
215
+
216
+ when 'ChildWorkflowExecutionStarted'
217
+ state_machine.start
218
+
219
+ when 'ChildWorkflowExecutionCompleted'
220
+ state_machine.complete
221
+ dispatch(target, 'completed', safe_parse(event.attributes.result))
222
+
223
+ when 'ChildWorkflowExecutionFailed'
224
+ state_machine.fail
225
+ dispatch(target, 'failed', event.attributes.reason, safe_parse(event.attributes.details))
226
+
227
+ when 'ChildWorkflowExecutionCanceled'
228
+ state_machine.cancel
229
+ dispatch(target, 'failed', 'CANCELLED', safe_parse(event.attributes.details))
230
+
231
+ when 'ChildWorkflowExecutionTimedOut'
232
+ state_machine.time_out
233
+ type = CadenceThrift::TimeoutType::VALUE_MAP[event.attributes.timeoutType]
234
+ dispatch(target, 'failed', 'Cadence::TimeoutError', "Timeout type: #{type}")
235
+
236
+ when 'ChildWorkflowExecutionTerminated'
237
+ # todo
238
+
239
+ when 'SignalExternalWorkflowExecutionInitiated'
240
+ # todo
241
+
242
+ when 'SignalExternalWorkflowExecutionFailed'
243
+ # todo
244
+
245
+ when 'ExternalWorkflowExecutionSignaled'
246
+ # todo
247
+
248
+ when 'UpsertWorkflowSearchAttributes'
249
+ # todo
250
+
251
+ else
252
+ raise UnsupportedEvent, event.type
253
+ end
254
+ end
255
+
256
+ def event_target_from(decision_id, decision)
257
+ target_type =
258
+ case decision
259
+ when Decision::ScheduleActivity
260
+ History::EventTarget::ACTIVITY_TYPE
261
+ when Decision::RequestActivityCancellation
262
+ History::EventTarget::CANCEL_ACTIVITY_REQUEST_TYPE
263
+ when Decision::RecordMarker
264
+ History::EventTarget::MARKER_TYPE
265
+ when Decision::StartTimer
266
+ History::EventTarget::TIMER_TYPE
267
+ when Decision::CancelTimer
268
+ History::EventTarget::CANCEL_TIMER_REQUEST_TYPE
269
+ when Decision::CompleteWorkflow, Decision::FailWorkflow
270
+ History::EventTarget::WORKFLOW_TYPE
271
+ when Decision::StartChildWorkflow
272
+ History::EventTarget::CHILD_WORKFLOW_TYPE
273
+ end
274
+
275
+ History::EventTarget.new(decision_id, target_type)
276
+ end
277
+
278
+ def dispatch(target, name, *attributes)
279
+ dispatcher.dispatch(target, name, attributes)
280
+ end
281
+
282
+ def discard_decision(target)
283
+ # Pop the first decision from the list, it is expected to match
284
+ existing_decision_id, existing_decision = decisions.shift
285
+
286
+ if !existing_decision_id
287
+ raise NonDeterministicWorkflowError, "A decision #{target} was not scheduled upon replay"
288
+ end
289
+
290
+ existing_target = event_target_from(existing_decision_id, existing_decision)
291
+ if target != existing_target
292
+ raise NonDeterministicWorkflowError, "Unexpected decision #{existing_target} (expected #{target})"
293
+ end
294
+ end
295
+
296
+ def handle_marker(id, type, details)
297
+ marker_ids << id
298
+
299
+ case type
300
+ when SIDE_EFFECT_MARKER
301
+ side_effects << [id, details]
302
+ when RELEASE_MARKER
303
+ releases[details] = true
304
+ else
305
+ raise UnsupportedMarkerType, event.type
306
+ end
307
+ end
308
+
309
+ def track_release(release_name)
310
+ # replay does not allow untracked (via marker) releases
311
+ if replay?
312
+ releases[release_name] = false
313
+ else
314
+ releases[release_name] = true
315
+ schedule(Decision::RecordMarker.new(name: RELEASE_MARKER, details: release_name))
316
+ end
317
+ end
318
+
319
+ def safe_parse(binary)
320
+ JSON.deserialize(binary)
321
+ end
322
+ end
323
+ end
324
+ end