temporal-ruby 0.0.0 → 0.0.1.pre.pre1

Sign up to get free protection for your applications and to get access to all the features.
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,34 @@
1
+ # This module provides a set of methods for imitating direct Child Workflow calls
2
+ # from within Workflows:
3
+ #
4
+ # class TestWorkflow < Temporal::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 'temporal/thread_local_context'
15
+
16
+ module Temporal
17
+ class Workflow
18
+ module ConvenienceMethods
19
+ def execute(*input, **args)
20
+ context = Temporal::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 = Temporal::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,31 @@
1
+ module Temporal
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,51 @@
1
+ module Temporal
2
+ class Workflow
3
+ class ExecutionInfo < Struct.new(:workflow, :workflow_id, :run_id, :start_time, :close_time, :status, :history_length, keyword_init: true)
4
+ RUNNING_STATUS = :RUNNING
5
+ COMPLETED_STATUS = :COMPLETED
6
+ FAILED_STATUS = :FAILED
7
+ CANCELED_STATUS = :CANCELED
8
+ TERMINATED_STATUS = :TERMINATED
9
+ CONTINUED_AS_NEW_STATUS = :CONTINUED_AS_NEW
10
+ TIMED_OUT_STATUS = :TIMED_OUT
11
+
12
+ API_STATUS_MAP = {
13
+ WORKFLOW_EXECUTION_STATUS_RUNNING: RUNNING_STATUS,
14
+ WORKFLOW_EXECUTION_STATUS_COMPLETED: COMPLETED_STATUS,
15
+ WORKFLOW_EXECUTION_STATUS_FAILED: FAILED_STATUS,
16
+ WORKFLOW_EXECUTION_STATUS_CANCELED: CANCELED_STATUS,
17
+ WORKFLOW_EXECUTION_STATUS_TERMINATED: TERMINATED_STATUS,
18
+ WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW: CONTINUED_AS_NEW_STATUS,
19
+ WORKFLOW_EXECUTION_STATUS_TIMED_OUT: TIMED_OUT_STATUS
20
+ }.freeze
21
+
22
+ VALID_STATUSES = [
23
+ RUNNING_STATUS,
24
+ COMPLETED_STATUS,
25
+ FAILED_STATUS,
26
+ CANCELED_STATUS,
27
+ TERMINATED_STATUS,
28
+ CONTINUED_AS_NEW_STATUS,
29
+ TIMED_OUT_STATUS
30
+ ].freeze
31
+
32
+ def self.generate_from(response)
33
+ new(
34
+ workflow: response.type.name,
35
+ workflow_id: response.execution.workflow_id,
36
+ run_id: response.execution.run_id,
37
+ start_time: response.start_time&.to_time,
38
+ close_time: response.close_time&.to_time,
39
+ status: API_STATUS_MAP.fetch(response.status),
40
+ history_length: response.history_length,
41
+ ).freeze
42
+ end
43
+
44
+ VALID_STATUSES.each do |status|
45
+ define_method("#{status.downcase}?") do
46
+ self.status == status
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,45 @@
1
+ require 'fiber'
2
+
3
+ require 'temporal/workflow/dispatcher'
4
+ require 'temporal/workflow/state_manager'
5
+ require 'temporal/workflow/context'
6
+ require 'temporal/workflow/history/event_target'
7
+
8
+ module Temporal
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.commands
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,77 @@
1
+ require 'fiber'
2
+
3
+ module Temporal
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
+ @failed = false
15
+ @result = nil
16
+ @exception = nil
17
+ end
18
+
19
+ def finished?
20
+ ready? || failed?
21
+ end
22
+
23
+ def ready?
24
+ @ready
25
+ end
26
+
27
+ def failed?
28
+ @failed
29
+ end
30
+
31
+ def wait
32
+ return if finished?
33
+ context.wait_for(self)
34
+ end
35
+
36
+ def get
37
+ wait
38
+ exception || result
39
+ end
40
+
41
+ def set(result)
42
+ raise 'can not fulfil a failed future' if failed?
43
+
44
+ @result = result
45
+ @ready = true
46
+ end
47
+
48
+ def fail(exception)
49
+ raise 'can not fail a fulfilled future' if ready?
50
+
51
+ @exception = exception
52
+ @failed = true
53
+ end
54
+
55
+ def done(&block)
56
+ # do nothing
57
+ return if failed?
58
+
59
+ if ready?
60
+ block.call(result)
61
+ else
62
+ callbacks << block
63
+ end
64
+ end
65
+
66
+ def cancel
67
+ return false if finished?
68
+
69
+ context.cancel(target, cancelation_id)
70
+ end
71
+
72
+ private
73
+
74
+ attr_reader :context, :cancelation_id, :result, :exception
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,76 @@
1
+ require 'temporal/workflow/history/event'
2
+ require 'temporal/workflow/history/window'
3
+
4
+ module Temporal
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_workflow_task
15
+ events.select { |event| event.type == 'WORKFLOW_TASK_COMPLETED' }.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
+ # workflow 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 workflow task related events (workflow task started, completed, failed, etc)
26
+ # 3. Commands issued by the last workflow 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 == 'WORKFLOW_TASK_COMPLETED'
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
+ ACTIVITY_TASK_SCHEDULED
49
+ ACTIVITY_TASK_CANCEL_REQUESTED
50
+ TIMER_STARTED
51
+ CANCEL_TIMER_FAILED
52
+ TIMER_CANCELED
53
+ WORKFLOW_EXECUTION_CANCEL_REQUESTED
54
+ START_CHILD_WORKFLOW_EXECUTION_INITIATED
55
+ SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED
56
+ REQUEST_CANCEL_ACTIVITY_TASK_FAILED
57
+ REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED
58
+ MARKER_RECORDED
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
@@ -0,0 +1,69 @@
1
+ module Temporal
2
+ class Workflow
3
+ class History
4
+ class Event
5
+ EVENT_TYPES = %w[
6
+ ACTIVITY_TASK_STARTED
7
+ ACTIVITY_TASK_COMPLETED
8
+ ACTIVITY_TASK_FAILED
9
+ ACTIVITY_TASK_TIMED_OUT
10
+ ACTIVITY_TASK_CANCELED
11
+ TIMER_FIRED
12
+ REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED
13
+ WORKFLOW_EXECUTION_SIGNALED
14
+ WORKFLOW_EXECUTION_TERMINATED
15
+ SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED
16
+ EXTERNAL_WORKFLOW_EXECUTION_CANCEL_REQUESTED
17
+ EXTERNAL_WORKFLOW_EXECUTION_SIGNALED
18
+ UPSERT_WORKFLOW_SEARCH_ATTRIBUTES
19
+ ].freeze
20
+
21
+ CHILD_WORKFLOW_EVENTS = %w[
22
+ START_CHILD_WORKFLOW_EXECUTION_FAILED
23
+ CHILD_WORKFLOW_EXECUTION_STARTED
24
+ CHILD_WORKFLOW_EXECUTION_COMPLETED
25
+ CHILD_WORKFLOW_EXECUTION_FAILED
26
+ CHILD_WORKFLOW_EXECUTION_CANCELED
27
+ CHILD_WORKFLOW_EXECUTION_TIMED_OUT
28
+ CHILD_WORKFLOW_EXECUTION_TERMINATED
29
+ ].freeze
30
+
31
+ PREFIX = 'EVENT_TYPE_'.freeze
32
+
33
+ attr_reader :id, :timestamp, :type, :attributes
34
+
35
+ def initialize(raw_event)
36
+ @id = raw_event.event_id
37
+ @timestamp = raw_event.event_time.to_time
38
+ @type = raw_event.event_type.to_s.gsub(PREFIX, '')
39
+ @attributes = extract_attributes(raw_event)
40
+
41
+ freeze
42
+ end
43
+
44
+ # Returns the ID of the first event associated with the current event.
45
+ def originating_event_id
46
+ case type
47
+ when 'TIMER_FIRED'
48
+ attributes.started_event_id
49
+ when 'WORKFLOW_EXECUTION_SIGNALED'
50
+ 1 # fixed id for everything related to current workflow
51
+ when *EVENT_TYPES
52
+ attributes.scheduled_event_id
53
+ when *CHILD_WORKFLOW_EVENTS
54
+ attributes.initiated_event_id
55
+ else
56
+ id
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def extract_attributes(raw_event)
63
+ attributes_name = raw_event.attributes
64
+ raw_event.public_send(attributes_name)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,75 @@
1
+ require 'temporal/errors'
2
+
3
+ module Temporal
4
+ class Workflow
5
+ class History
6
+ class EventTarget
7
+ class UnexpectedEventType < InternalError; end
8
+
9
+ ACTIVITY_TYPE = :activity
10
+ CANCEL_ACTIVITY_REQUEST_TYPE = :cancel_activity_request
11
+ TIMER_TYPE = :timer
12
+ CANCEL_TIMER_REQUEST_TYPE = :cancel_timer_request
13
+ CHILD_WORKFLOW_TYPE = :child_workflow
14
+ MARKER_TYPE = :marker
15
+ EXTERNAL_WORKFLOW_TYPE = :external_workflow
16
+ CANCEL_EXTERNAL_WORKFLOW_REQUEST_TYPE = :cancel_external_workflow_request
17
+ WORKFLOW_TYPE = :workflow
18
+ CANCEL_WORKFLOW_REQUEST_TYPE = :cancel_workflow_request
19
+
20
+ TARGET_TYPES = {
21
+ 'ACTIVITY_TASK' => ACTIVITY_TYPE,
22
+ 'ACTIVITY_TASK_CANCEL' => CANCEL_ACTIVITY_REQUEST_TYPE,
23
+ 'REQUEST_CANCEL_ACTIVITY_TASK' => CANCEL_ACTIVITY_REQUEST_TYPE,
24
+ 'TIMER' => TIMER_TYPE,
25
+ 'CANCEL_TIMER' => CANCEL_TIMER_REQUEST_TYPE,
26
+ 'CHILD_WORKFLOW_EXECUTION' => CHILD_WORKFLOW_TYPE,
27
+ 'START_CHILD_WORKFLOW_EXECUTION' => CHILD_WORKFLOW_TYPE,
28
+ 'MARKER' => MARKER_TYPE,
29
+ 'EXTERNAL_WORKFLOW_EXECUTION' => EXTERNAL_WORKFLOW_TYPE,
30
+ 'SIGNAL_EXTERNAL_WORKFLOW_EXECUTION' => EXTERNAL_WORKFLOW_TYPE,
31
+ 'EXTERNAL_WORKFLOW_EXECUTION_CANCEL' => CANCEL_EXTERNAL_WORKFLOW_REQUEST_TYPE,
32
+ 'REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION' => CANCEL_EXTERNAL_WORKFLOW_REQUEST_TYPE,
33
+ 'UPSERT_WORKFLOW_SEARCH_ATTRIBUTES' => WORKFLOW_TYPE,
34
+ 'WORKFLOW_EXECUTION' => WORKFLOW_TYPE,
35
+ 'WORKFLOW_EXECUTION_CANCEL' => CANCEL_WORKFLOW_REQUEST_TYPE,
36
+ }.freeze
37
+
38
+ attr_reader :id, :type
39
+
40
+ def self.workflow
41
+ @workflow ||= new(1, WORKFLOW_TYPE)
42
+ end
43
+
44
+ def self.from_event(event)
45
+ _, target_type = TARGET_TYPES.find { |type, _| event.type.start_with?(type) }
46
+
47
+ unless target_type
48
+ raise UnexpectedEventType, "Unexpected event #{event.type}"
49
+ end
50
+
51
+ new(event.originating_event_id, target_type)
52
+ end
53
+
54
+ def initialize(id, type)
55
+ @id = id
56
+ @type = type
57
+
58
+ freeze
59
+ end
60
+
61
+ def ==(other)
62
+ id == other.id && type == other.type
63
+ end
64
+
65
+ def eql?(other)
66
+ self == other
67
+ end
68
+
69
+ def hash
70
+ [id, type].hash
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end