cadence-ruby 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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