temporal-ruby 0.0.0 → 0.0.1.pre.pre1

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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -42
  3. data/lib/gen/temporal/api/command/v1/message_pb.rb +146 -0
  4. data/lib/gen/temporal/api/common/v1/message_pb.rb +67 -0
  5. data/lib/gen/temporal/api/enums/v1/command_type_pb.rb +35 -0
  6. data/lib/gen/temporal/api/enums/v1/common_pb.rb +34 -0
  7. data/lib/gen/temporal/api/enums/v1/event_type_pb.rb +62 -0
  8. data/lib/gen/temporal/api/enums/v1/failed_cause_pb.rb +60 -0
  9. data/lib/gen/temporal/api/enums/v1/namespace_pb.rb +31 -0
  10. data/lib/gen/temporal/api/enums/v1/query_pb.rb +31 -0
  11. data/lib/gen/temporal/api/enums/v1/task_queue_pb.rb +30 -0
  12. data/lib/gen/temporal/api/enums/v1/workflow_pb.rb +82 -0
  13. data/lib/gen/temporal/api/errordetails/v1/message_pb.rb +55 -0
  14. data/lib/gen/temporal/api/failure/v1/message_pb.rb +81 -0
  15. data/lib/gen/temporal/api/filter/v1/message_pb.rb +38 -0
  16. data/lib/gen/temporal/api/history/v1/message_pb.rb +423 -0
  17. data/lib/gen/temporal/api/namespace/v1/message_pb.rb +55 -0
  18. data/lib/gen/temporal/api/query/v1/message_pb.rb +36 -0
  19. data/lib/gen/temporal/api/replication/v1/message_pb.rb +27 -0
  20. data/lib/gen/temporal/api/taskqueue/v1/message_pb.rb +60 -0
  21. data/lib/gen/temporal/api/version/v1/message_pb.rb +28 -0
  22. data/lib/gen/temporal/api/workflow/v1/message_pb.rb +83 -0
  23. data/lib/gen/temporal/api/workflowservice/v1/request_response_pb.rb +538 -0
  24. data/lib/gen/temporal/api/workflowservice/v1/service_pb.rb +19 -0
  25. data/lib/gen/temporal/api/workflowservice/v1/service_services_pb.rb +223 -0
  26. data/lib/temporal-ruby.rb +1 -0
  27. data/lib/temporal.rb +137 -0
  28. data/lib/temporal/activity.rb +33 -0
  29. data/lib/temporal/activity/async_token.rb +34 -0
  30. data/lib/temporal/activity/context.rb +64 -0
  31. data/lib/temporal/activity/poller.rb +79 -0
  32. data/lib/temporal/activity/task_processor.rb +78 -0
  33. data/lib/temporal/activity/workflow_convenience_methods.rb +41 -0
  34. data/lib/temporal/client.rb +21 -0
  35. data/lib/temporal/client/errors.rb +8 -0
  36. data/lib/temporal/client/grpc_client.rb +345 -0
  37. data/lib/temporal/client/serializer.rb +31 -0
  38. data/lib/temporal/client/serializer/base.rb +23 -0
  39. data/lib/temporal/client/serializer/cancel_timer.rb +19 -0
  40. data/lib/temporal/client/serializer/complete_workflow.rb +20 -0
  41. data/lib/temporal/client/serializer/fail_workflow.rb +20 -0
  42. data/lib/temporal/client/serializer/failure.rb +29 -0
  43. data/lib/temporal/client/serializer/payload.rb +25 -0
  44. data/lib/temporal/client/serializer/record_marker.rb +23 -0
  45. data/lib/temporal/client/serializer/request_activity_cancellation.rb +19 -0
  46. data/lib/temporal/client/serializer/schedule_activity.rb +53 -0
  47. data/lib/temporal/client/serializer/start_child_workflow.rb +51 -0
  48. data/lib/temporal/client/serializer/start_timer.rb +20 -0
  49. data/lib/temporal/concerns/executable.rb +37 -0
  50. data/lib/temporal/concerns/typed.rb +40 -0
  51. data/lib/temporal/configuration.rb +44 -0
  52. data/lib/temporal/errors.rb +38 -0
  53. data/lib/temporal/executable_lookup.rb +25 -0
  54. data/lib/temporal/execution_options.rb +35 -0
  55. data/lib/temporal/json.rb +18 -0
  56. data/lib/temporal/metadata.rb +68 -0
  57. data/lib/temporal/metadata/activity.rb +27 -0
  58. data/lib/temporal/metadata/base.rb +17 -0
  59. data/lib/temporal/metadata/workflow.rb +22 -0
  60. data/lib/temporal/metadata/workflow_task.rb +25 -0
  61. data/lib/temporal/metrics.rb +37 -0
  62. data/lib/temporal/metrics_adapters/log.rb +33 -0
  63. data/lib/temporal/metrics_adapters/null.rb +9 -0
  64. data/lib/temporal/middleware/chain.rb +30 -0
  65. data/lib/temporal/middleware/entry.rb +9 -0
  66. data/lib/temporal/retry_policy.rb +27 -0
  67. data/lib/temporal/saga/concern.rb +23 -0
  68. data/lib/temporal/saga/result.rb +22 -0
  69. data/lib/temporal/saga/saga.rb +24 -0
  70. data/lib/temporal/testing.rb +50 -0
  71. data/lib/temporal/testing/future_registry.rb +27 -0
  72. data/lib/temporal/testing/local_activity_context.rb +17 -0
  73. data/lib/temporal/testing/local_workflow_context.rb +178 -0
  74. data/lib/temporal/testing/temporal_override.rb +121 -0
  75. data/lib/temporal/testing/workflow_execution.rb +44 -0
  76. data/lib/temporal/testing/workflow_override.rb +36 -0
  77. data/lib/temporal/thread_local_context.rb +14 -0
  78. data/lib/temporal/thread_pool.rb +63 -0
  79. data/lib/temporal/types.rb +7 -0
  80. data/lib/temporal/uuid.rb +19 -0
  81. data/lib/temporal/version.rb +1 -1
  82. data/lib/temporal/worker.rb +88 -0
  83. data/lib/temporal/workflow.rb +42 -0
  84. data/lib/temporal/workflow/command.rb +39 -0
  85. data/lib/temporal/workflow/command_state_machine.rb +48 -0
  86. data/lib/temporal/workflow/context.rb +243 -0
  87. data/lib/temporal/workflow/convenience_methods.rb +34 -0
  88. data/lib/temporal/workflow/dispatcher.rb +31 -0
  89. data/lib/temporal/workflow/execution_info.rb +51 -0
  90. data/lib/temporal/workflow/executor.rb +45 -0
  91. data/lib/temporal/workflow/future.rb +77 -0
  92. data/lib/temporal/workflow/history.rb +76 -0
  93. data/lib/temporal/workflow/history/event.rb +69 -0
  94. data/lib/temporal/workflow/history/event_target.rb +75 -0
  95. data/lib/temporal/workflow/history/window.rb +40 -0
  96. data/lib/temporal/workflow/poller.rb +67 -0
  97. data/lib/temporal/workflow/replay_aware_logger.rb +36 -0
  98. data/lib/temporal/workflow/state_manager.rb +342 -0
  99. data/lib/temporal/workflow/task_processor.rb +78 -0
  100. data/rbi/temporal-ruby.rbi +43 -0
  101. data/temporal.gemspec +10 -2
  102. metadata +186 -6
@@ -0,0 +1,40 @@
1
+ module Temporal
2
+ class Workflow
3
+ class History
4
+ class Window
5
+ attr_reader :local_time, :last_event_id, :events, :markers
6
+
7
+ def initialize
8
+ @local_time = nil
9
+ @last_event_id = nil
10
+ @events = []
11
+ @markers = []
12
+ @replay = false
13
+ end
14
+
15
+ def replay?
16
+ @replay
17
+ end
18
+
19
+ def add(event)
20
+ case event.type
21
+ when 'MARKER_RECORDED'
22
+ markers << event
23
+ when 'WORKFLOW_TASK_STARTED'
24
+ @last_event_id = event.id + 1 # one for completed
25
+ @local_time = event.timestamp
26
+ when 'WORKFLOW_TASK_FAILED', 'WORKFLOW_TASK_TIMED_OUT'
27
+ @last_event_id = nil
28
+ @local_time = nil
29
+ when 'WORKFLOW_TASK_COMPLETED'
30
+ @replay = true
31
+ when 'WORKFLOW_TASK_SCHEDULED', 'WORKFLOW_TASK_FAILED'
32
+ # no-op
33
+ else
34
+ events << event
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,67 @@
1
+ require 'temporal/client'
2
+ require 'temporal/middleware/chain'
3
+ require 'temporal/workflow/task_processor'
4
+
5
+ module Temporal
6
+ class Workflow
7
+ class Poller
8
+ def initialize(namespace, task_queue, workflow_lookup, middleware = [])
9
+ @namespace = namespace
10
+ @task_queue = task_queue
11
+ @workflow_lookup = workflow_lookup
12
+ @middleware = middleware
13
+ @shutting_down = false
14
+ end
15
+
16
+ def start
17
+ @shutting_down = false
18
+ @thread = Thread.new(&method(:poll_loop))
19
+ end
20
+
21
+ def stop
22
+ @shutting_down = true
23
+ Thread.new { Temporal.logger.info('Shutting down a workflow poller') }.join
24
+ end
25
+
26
+ def wait
27
+ @thread.join
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :namespace, :task_queue, :client, :workflow_lookup, :middleware
33
+
34
+ def client
35
+ @client ||= Temporal::Client.generate
36
+ end
37
+
38
+ def middleware_chain
39
+ @middleware_chain ||= Middleware::Chain.new(middleware)
40
+ end
41
+
42
+ def shutting_down?
43
+ @shutting_down
44
+ end
45
+
46
+ def poll_loop
47
+ while !shutting_down? do
48
+ Temporal.logger.debug("Polling worklow task queue (#{namespace} / #{task_queue})")
49
+
50
+ task = poll_for_task
51
+ process(task) if task&.workflow_type
52
+ end
53
+ end
54
+
55
+ def poll_for_task
56
+ client.poll_workflow_task_queue(namespace: namespace, task_queue: task_queue)
57
+ rescue StandardError => error
58
+ Temporal.logger.error("Unable to poll workflow task queue: #{error.inspect}")
59
+ nil
60
+ end
61
+
62
+ def process(task)
63
+ TaskProcessor.new(task, namespace, workflow_lookup, client, middleware_chain).process
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,36 @@
1
+ module Temporal
2
+ class Workflow
3
+ class ReplayAwareLogger
4
+ SEVERITIES = %i[debug info warn error fatal unknown].freeze
5
+
6
+ attr_writer :replay
7
+
8
+ def initialize(main_logger, replay = true)
9
+ @main_logger = main_logger
10
+ @replay = replay
11
+ end
12
+
13
+ SEVERITIES.each do |severity|
14
+ define_method severity do |message|
15
+ return if replay?
16
+
17
+ main_logger.public_send(severity, message)
18
+ end
19
+ end
20
+
21
+ def log(severity, message)
22
+ return if replay?
23
+
24
+ main_logger.log(severity, message)
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :main_logger
30
+
31
+ def replay?
32
+ @replay
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,342 @@
1
+ require 'temporal/json'
2
+ require 'temporal/errors'
3
+ require 'temporal/workflow/command'
4
+ require 'temporal/workflow/command_state_machine'
5
+ require 'temporal/workflow/history/event_target'
6
+ require 'temporal/metadata'
7
+
8
+ module Temporal
9
+ class Workflow
10
+ class StateManager
11
+ SIDE_EFFECT_MARKER = 'SIDE_EFFECT'.freeze
12
+ RELEASE_MARKER = 'RELEASE'.freeze
13
+
14
+ class UnsupportedEvent < Temporal::InternalError; end
15
+ class UnsupportedMarkerType < Temporal::InternalError; end
16
+
17
+ attr_reader :commands, :local_time
18
+
19
+ def initialize(dispatcher)
20
+ @dispatcher = dispatcher
21
+ @commands = []
22
+ @marker_ids = Set.new
23
+ @releases = {}
24
+ @side_effects = []
25
+ @command_tracker = Hash.new { |hash, key| hash[key] = CommandStateMachine.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(command)
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
+ command_id = next_event_id
39
+ while marker_ids.include?(command_id) do
40
+ command_id = next_event_id
41
+ end
42
+
43
+ cancelation_id =
44
+ case command
45
+ when Command::ScheduleActivity
46
+ command.activity_id ||= command_id
47
+ when Command::StartChildWorkflow
48
+ command.workflow_id ||= command_id
49
+ when Command::StartTimer
50
+ command.timer_id ||= command_id
51
+ end
52
+
53
+ state_machine = command_tracker[command_id]
54
+ state_machine.requested if state_machine.state == CommandStateMachine::NEW_STATE
55
+
56
+ commands << [command_id, command]
57
+
58
+ return [event_target_from(command_id, command), 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, :command_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 = command_tracker[event.originating_event_id]
96
+ target = History::EventTarget.from_event(event)
97
+
98
+ case event.type
99
+ when 'WORKFLOW_EXECUTION_STARTED'
100
+ state_machine.start
101
+ dispatch(
102
+ History::EventTarget.workflow,
103
+ 'started',
104
+ parse_payload(event.attributes.input),
105
+ Metadata.generate(Metadata::WORKFLOW_TYPE, event.attributes)
106
+ )
107
+
108
+ when 'WORKFLOW_EXECUTION_COMPLETED'
109
+ # todo
110
+
111
+ when 'WORKFLOW_EXECUTION_FAILED'
112
+ # todo
113
+
114
+ when 'WORKFLOW_EXECUTION_TIMED_OUT'
115
+ # todo
116
+
117
+ when 'WORKFLOW_TASK_SCHEDULED'
118
+ # todo
119
+
120
+ when 'WORKFLOW_TASK_STARTED'
121
+ # todo
122
+
123
+ when 'WORKFLOW_TASK_COMPLETED'
124
+ # todo
125
+
126
+ when 'WORKFLOW_TASK_TIMED_OUT'
127
+ # todo
128
+
129
+ when 'WORKFLOW_TASK_FAILED'
130
+ # todo
131
+
132
+ when 'ACTIVITY_TASK_SCHEDULED'
133
+ state_machine.schedule
134
+ discard_command(event.originating_event_id)
135
+
136
+ when 'ACTIVITY_TASK_STARTED'
137
+ state_machine.start
138
+
139
+ when 'ACTIVITY_TASK_COMPLETED'
140
+ state_machine.complete
141
+ dispatch(target, 'completed', parse_payload(event.attributes.result))
142
+
143
+ when 'ACTIVITY_TASK_FAILED'
144
+ state_machine.fail
145
+ dispatch(target, 'failed', parse_failure(event.attributes.failure, ActivityException))
146
+
147
+ when 'ACTIVITY_TASK_TIMED_OUT'
148
+ state_machine.time_out
149
+ dispatch(target, 'failed', parse_failure(event.attributes.failure))
150
+
151
+ when 'ACTIVITY_TASK_CANCEL_REQUESTED'
152
+ state_machine.requested
153
+ discard_command(event.originating_event_id)
154
+
155
+ when 'REQUEST_CANCEL_ACTIVITY_TASK_FAILED'
156
+ state_machine.fail
157
+ dispatch(target, 'failed', event.attributes.cause, nil)
158
+
159
+ when 'ACTIVITY_TASK_CANCELED'
160
+ state_machine.cancel
161
+ dispatch(target, 'failed', parse_failure(event.attributes.failure))
162
+
163
+ when 'TIMER_STARTED'
164
+ state_machine.start
165
+ discard_command(event.originating_event_id)
166
+
167
+ when 'TIMER_FIRED'
168
+ state_machine.complete
169
+ dispatch(target, 'fired')
170
+
171
+ when 'CANCEL_TIMER_FAILED'
172
+ state_machine.failed
173
+ dispatch(target, 'failed', event.attributes.cause, nil)
174
+
175
+ when 'TIMER_CANCELED'
176
+ state_machine.cancel
177
+ dispatch(target, 'canceled')
178
+
179
+ when 'WORKFLOW_EXECUTION_CANCEL_REQUESTED'
180
+ # todo
181
+
182
+ when 'WORKFLOW_EXECUTION_CANCELED'
183
+ # todo
184
+
185
+ when 'REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED'
186
+ # todo
187
+
188
+ when 'REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED'
189
+ # todo
190
+
191
+ when 'EXTERNAL_WORKFLOW_EXECUTION_CANCEL_REQUESTED'
192
+ # todo
193
+
194
+ when 'MARKER_RECORDED'
195
+ state_machine.complete
196
+ handle_marker(event.id, event.attributes.marker_name, parse_payload(event.attributes.details['data']))
197
+
198
+ when 'WORKFLOW_EXECUTION_SIGNALED'
199
+ dispatch(target, 'signaled', event.attributes.signal_name, parse_payload(event.attributes.input))
200
+
201
+ when 'WORKFLOW_EXECUTION_TERMINATED'
202
+ # todo
203
+
204
+ when 'WORKFLOW_EXECUTION_CONTINUED_AS_NEW'
205
+ # todo
206
+
207
+ when 'START_CHILD_WORKFLOW_EXECUTION_INITIATED'
208
+ state_machine.schedule
209
+ discard_command(event.originating_event_id)
210
+
211
+ when 'START_CHILD_WORKFLOW_EXECUTION_FAILED'
212
+ state_machine.fail
213
+ dispatch(target, 'failed', 'StandardError', parse_payload(event.attributes.cause))
214
+
215
+ when 'CHILD_WORKFLOW_EXECUTION_STARTED'
216
+ state_machine.start
217
+
218
+ when 'CHILD_WORKFLOW_EXECUTION_COMPLETED'
219
+ state_machine.complete
220
+ dispatch(target, 'completed', parse_payload(event.attributes.result))
221
+
222
+ when 'CHILD_WORKFLOW_EXECUTION_FAILED'
223
+ state_machine.fail
224
+ dispatch(target, 'failed', parse_failure(event.attributes.failure))
225
+
226
+ when 'CHILD_WORKFLOW_EXECUTION_CANCELED'
227
+ state_machine.cancel
228
+ dispatch(target, 'failed', parse_failure(event.attributes.failure))
229
+
230
+ when 'CHILD_WORKFLOW_EXECUTION_TIMED_OUT'
231
+ state_machine.time_out
232
+ dispatch(target, 'failed', parse_failure(event.attributes.failure))
233
+
234
+ when 'CHILD_WORKFLOW_EXECUTION_TERMINATED'
235
+ # todo
236
+
237
+ when 'SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED'
238
+ # todo
239
+
240
+ when 'SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED'
241
+ # todo
242
+
243
+ when 'EXTERNAL_WORKFLOW_EXECUTION_SIGNALED'
244
+ # todo
245
+
246
+ when 'UPSERT_WORKFLOW_SEARCH_ATTRIBUTES'
247
+ # todo
248
+
249
+ else
250
+ raise UnsupportedEvent, event.type
251
+ end
252
+ end
253
+
254
+ def event_target_from(command_id, command)
255
+ target_type =
256
+ case command
257
+ when Command::ScheduleActivity
258
+ History::EventTarget::ACTIVITY_TYPE
259
+ when Command::RequestActivityCancellation
260
+ History::EventTarget::CANCEL_ACTIVITY_REQUEST_TYPE
261
+ when Command::RecordMarker
262
+ History::EventTarget::MARKER_TYPE
263
+ when Command::StartTimer
264
+ History::EventTarget::TIMER_TYPE
265
+ when Command::CancelTimer
266
+ History::EventTarget::CANCEL_TIMER_REQUEST_TYPE
267
+ when Command::CompleteWorkflow, Command::FailWorkflow
268
+ History::EventTarget::WORKFLOW_TYPE
269
+ when Command::StartChildWorkflow
270
+ History::EventTarget::CHILD_WORKFLOW_TYPE
271
+ end
272
+
273
+ History::EventTarget.new(command_id, target_type)
274
+ end
275
+
276
+ def dispatch(target, name, *attributes)
277
+ dispatcher.dispatch(target, name, attributes)
278
+ end
279
+
280
+ def discard_command(command_id)
281
+ commands.delete_if { |(id, _)| id == command_id }
282
+ end
283
+
284
+ def handle_marker(id, type, details)
285
+ marker_ids << id
286
+
287
+ case type
288
+ when SIDE_EFFECT_MARKER
289
+ side_effects << [id, details]
290
+ when RELEASE_MARKER
291
+ releases[details] = true
292
+ else
293
+ raise UnsupportedMarkerType, event.type
294
+ end
295
+ end
296
+
297
+ def track_release(release_name)
298
+ # replay does not allow untracked (via marker) releases
299
+ if replay?
300
+ releases[release_name] = false
301
+ else
302
+ releases[release_name] = true
303
+ schedule(Command::RecordMarker.new(name: RELEASE_MARKER, details: release_name))
304
+ end
305
+ end
306
+
307
+ def parse_payload(payload)
308
+ return if payload.nil? || payload.payloads.empty?
309
+
310
+ binary = payload.payloads.first.data
311
+ JSON.deserialize(binary)
312
+ end
313
+
314
+ def parse_failure(failure, default_exception_class = StandardError)
315
+ case failure.failure_info
316
+ when :application_failure_info
317
+ exception_class = safe_constantize(failure.application_failure_info.type)
318
+ exception_class ||= default_exception_class
319
+ details = parse_payload(failure.application_failure_info.details)
320
+ backtrace = failure.stack_trace.split("\n")
321
+
322
+ exception_class.new(details).tap do |exception|
323
+ exception.set_backtrace(backtrace) if !backtrace.empty?
324
+ end
325
+ when :timeout_failure_info
326
+ TimeoutError.new("Timeout type: #{failure.timeout_failure_info.timeout_type.to_s}")
327
+ when :canceled_failure_info
328
+ # TODO: Distinguish between different entity cancellations
329
+ StandardError.new(parse_payload(failure.canceled_failure_info.details))
330
+ else
331
+ StandardError.new(failure.message)
332
+ end
333
+ end
334
+
335
+ def safe_constantize(const)
336
+ Object.const_get(const) if Object.const_defined?(const)
337
+ rescue NameError
338
+ nil
339
+ end
340
+ end
341
+ end
342
+ end