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,36 @@
1
+ require 'securerandom'
2
+ require 'temporal/testing/local_workflow_context'
3
+ require 'temporal/testing/workflow_execution'
4
+
5
+ module Temporal
6
+ module Testing
7
+ module WorkflowOverride
8
+ def disabled_releases
9
+ @disabled_releases ||= Set.new
10
+ end
11
+
12
+ def allow_all_releases
13
+ disabled_releases.clear
14
+ end
15
+
16
+ def allow_release(release_name)
17
+ disabled_releases.delete(release_name.to_s)
18
+ end
19
+
20
+ def disable_release(release_name)
21
+ disabled_releases << release_name.to_s
22
+ end
23
+
24
+ def execute_locally(*input)
25
+ workflow_id = SecureRandom.uuid
26
+ run_id = SecureRandom.uuid
27
+ execution = WorkflowExecution.new
28
+ context = Temporal::Testing::LocalWorkflowContext.new(
29
+ execution, workflow_id, run_id, disabled_releases
30
+ )
31
+
32
+ execute_in_context(context, input)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ # Provides context for Temporal::Activity::WorkflowConvenienceMethods
2
+ module Temporal
3
+ module ThreadLocalContext
4
+ WORKFLOW_CONTEXT_KEY = :temporal_workflow_context
5
+
6
+ def self.get
7
+ Thread.current[WORKFLOW_CONTEXT_KEY]
8
+ end
9
+
10
+ def self.set(context)
11
+ Thread.current[WORKFLOW_CONTEXT_KEY] = context
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,63 @@
1
+ # This class implements a very simple ThreadPool with the ability to
2
+ # block until at least one thread becomes available. This allows Pollers
3
+ # to only poll when there's an available thread in the pool.
4
+ #
5
+ # NOTE: There's a minor race condition that can occur between calling
6
+ # #wait_for_available_threads and #schedule, but should be rare
7
+ #
8
+ module Temporal
9
+ class ThreadPool
10
+ attr_reader :size
11
+
12
+ def initialize(size)
13
+ @size = size
14
+ @queue = Queue.new
15
+ @mutex = Mutex.new
16
+ @availability = ConditionVariable.new
17
+ @available_threads = size
18
+ @pool = Array.new(size) do |i|
19
+ Thread.new { poll }
20
+ end
21
+ end
22
+
23
+ def wait_for_available_threads
24
+ @mutex.synchronize do
25
+ while @available_threads <= 0
26
+ @availability.wait(@mutex)
27
+ end
28
+ end
29
+ end
30
+
31
+ def schedule(&block)
32
+ @mutex.synchronize do
33
+ @available_threads -= 1
34
+ @queue << block
35
+ end
36
+ end
37
+
38
+ def shutdown
39
+ size.times do
40
+ schedule { throw EXIT_SYMBOL }
41
+ end
42
+
43
+ @pool.each(&:join)
44
+ end
45
+
46
+ private
47
+
48
+ EXIT_SYMBOL = :exit
49
+
50
+ def poll
51
+ catch(EXIT_SYMBOL) do
52
+ loop do
53
+ job = @queue.pop
54
+ job.call
55
+ @mutex.synchronize do
56
+ @available_threads += 1
57
+ @availability.signal
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ require 'dry-types'
2
+
3
+ module Temporal
4
+ module Types
5
+ include Dry.Types
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ # This is a simple UUIDv5 (SHA1) implementation adopted from:
2
+ # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/digest/uuid.rb#L18
3
+ require 'digest'
4
+
5
+ module Temporal
6
+ module UUID
7
+ def self.v5(uuid_namespace, name)
8
+ hash = Digest::SHA1.new
9
+ hash.update(uuid_namespace)
10
+ hash.update(name)
11
+
12
+ ary = hash.digest.unpack("NnnnnN")
13
+ ary[2] = (ary[2] & 0x0FFF) | (5 << 12)
14
+ ary[3] = (ary[3] & 0x3FFF) | 0x8000
15
+
16
+ "%08x-%04x-%04x-%04x-%04x%08x" % ary
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,3 @@
1
1
  module Temporal
2
- VERSION = '0.0.0'.freeze
2
+ VERSION = '0.0.1-pre1'.freeze
3
3
  end
@@ -0,0 +1,88 @@
1
+ require 'temporal/client'
2
+ require 'temporal/workflow/poller'
3
+ require 'temporal/activity/poller'
4
+ require 'temporal/execution_options'
5
+ require 'temporal/executable_lookup'
6
+ require 'temporal/middleware/entry'
7
+
8
+ module Temporal
9
+ class Worker
10
+ def initialize
11
+ @workflows = Hash.new { |hash, key| hash[key] = ExecutableLookup.new }
12
+ @activities = Hash.new { |hash, key| hash[key] = ExecutableLookup.new }
13
+ @pollers = []
14
+ @workflow_task_middleware = []
15
+ @activity_middleware = []
16
+ @shutting_down = false
17
+ end
18
+
19
+ def register_workflow(workflow_class, options = {})
20
+ execution_options = ExecutionOptions.new(workflow_class, options)
21
+ key = [execution_options.namespace, execution_options.task_queue]
22
+
23
+ @workflows[key].add(execution_options.name, workflow_class)
24
+ end
25
+
26
+ def register_activity(activity_class, options = {})
27
+ execution_options = ExecutionOptions.new(activity_class, options)
28
+ key = [execution_options.namespace, execution_options.task_queue]
29
+
30
+ @activities[key].add(execution_options.name, activity_class)
31
+ end
32
+
33
+ def add_workflow_task_middleware(middleware_class, *args)
34
+ @workflow_task_middleware << Middleware::Entry.new(middleware_class, args)
35
+ end
36
+
37
+ def add_activity_middleware(middleware_class, *args)
38
+ @activity_middleware << Middleware::Entry.new(middleware_class, args)
39
+ end
40
+
41
+ def start
42
+ workflows.each_pair do |(namespace, task_queue), lookup|
43
+ pollers << workflow_poller_for(namespace, task_queue, lookup)
44
+ end
45
+
46
+ activities.each_pair do |(namespace, task_queue), lookup|
47
+ pollers << activity_poller_for(namespace, task_queue, lookup)
48
+ end
49
+
50
+ trap_signals
51
+
52
+ pollers.each(&:start)
53
+
54
+ # wait until instructed to shut down
55
+ while !shutting_down? do
56
+ sleep 1
57
+ end
58
+ end
59
+
60
+ def stop
61
+ @shutting_down = true
62
+ pollers.each(&:stop)
63
+ pollers.each(&:wait)
64
+ end
65
+
66
+ private
67
+
68
+ attr_reader :activities, :workflows, :pollers, :workflow_task_middleware, :activity_middleware
69
+
70
+ def shutting_down?
71
+ @shutting_down
72
+ end
73
+
74
+ def workflow_poller_for(namespace, task_queue, lookup)
75
+ Workflow::Poller.new(namespace, task_queue, lookup.freeze, workflow_task_middleware)
76
+ end
77
+
78
+ def activity_poller_for(namespace, task_queue, lookup)
79
+ Activity::Poller.new(namespace, task_queue, lookup.freeze, activity_middleware)
80
+ end
81
+
82
+ def trap_signals
83
+ %w[TERM INT].each do |signal|
84
+ Signal.trap(signal) { stop }
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,42 @@
1
+ require 'temporal/concerns/executable'
2
+ require 'temporal/workflow/convenience_methods'
3
+ require 'temporal/thread_local_context'
4
+
5
+ module Temporal
6
+ class Workflow
7
+ extend Concerns::Executable
8
+ extend ConvenienceMethods
9
+
10
+ def self.execute_in_context(context, input)
11
+ Temporal::ThreadLocalContext.set(context)
12
+
13
+ workflow = new(context)
14
+ result = workflow.execute(*input)
15
+
16
+ context.complete(result)
17
+ rescue StandardError, ScriptError => error
18
+ Temporal.logger.error("Workflow execution failed with: #{error.inspect}")
19
+ Temporal.logger.debug(error.backtrace.join("\n"))
20
+
21
+ context.fail(error)
22
+ end
23
+
24
+ def initialize(context)
25
+ @context = context
26
+ end
27
+
28
+ def execute
29
+ raise NotImplementedError, '#execute method must be implemented by a subclass'
30
+ end
31
+
32
+ private
33
+
34
+ def workflow
35
+ @context
36
+ end
37
+
38
+ def logger
39
+ workflow.logger
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ module Temporal
2
+ class Workflow
3
+ module Command
4
+ # TODO: Move these classes into their own directories under workflow/command/*
5
+ ScheduleActivity = Struct.new(:activity_type, :activity_id, :input, :namespace, :task_queue, :retry_policy, :timeouts, :headers, keyword_init: true)
6
+ StartChildWorkflow = Struct.new(:workflow_type, :workflow_id, :input, :namespace, :task_queue, :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(:exception, keyword_init: true)
13
+
14
+ # only these commands 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
+ COMMAND_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
+ command_class = COMMAND_CLASS_MAP[type]
35
+ command_class.new(**args)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,48 @@
1
+ module Temporal
2
+ class Workflow
3
+ class CommandStateMachine
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,243 @@
1
+ require 'securerandom'
2
+
3
+ require 'temporal/execution_options'
4
+ require 'temporal/errors'
5
+ require 'temporal/thread_local_context'
6
+ require 'temporal/workflow/history/event_target'
7
+ require 'temporal/workflow/command'
8
+ require 'temporal/workflow/future'
9
+ require 'temporal/workflow/replay_aware_logger'
10
+ require 'temporal/workflow/state_manager'
11
+
12
+ # This context class is available in the workflow implementation
13
+ # and provides context and methods for interacting with Temporal
14
+ #
15
+ module Temporal
16
+ class Workflow
17
+ class Context
18
+ def initialize(state_manager, dispatcher, metadata)
19
+ @state_manager = state_manager
20
+ @dispatcher = dispatcher
21
+ @metadata = metadata
22
+ end
23
+
24
+ def logger
25
+ @logger ||= ReplayAwareLogger.new(Temporal.logger)
26
+ @logger.replay = state_manager.replay?
27
+ @logger
28
+ end
29
+
30
+ def headers
31
+ metadata.headers
32
+ end
33
+
34
+ def has_release?(release_name)
35
+ state_manager.release?(release_name.to_s)
36
+ end
37
+
38
+ def execute_activity(activity_class, *input, **args)
39
+ options = args.delete(:options) || {}
40
+ input << args unless args.empty?
41
+
42
+ execution_options = ExecutionOptions.new(activity_class, options)
43
+
44
+ command = Command::ScheduleActivity.new(
45
+ activity_id: options[:activity_id],
46
+ activity_type: execution_options.name,
47
+ input: input,
48
+ namespace: execution_options.namespace,
49
+ task_queue: execution_options.task_queue,
50
+ retry_policy: execution_options.retry_policy,
51
+ timeouts: execution_options.timeouts,
52
+ headers: execution_options.headers
53
+ )
54
+
55
+ target, cancelation_id = schedule_command(command)
56
+ future = Future.new(target, self, cancelation_id: cancelation_id)
57
+
58
+ dispatcher.register_handler(target, 'completed') do |result|
59
+ future.set(result)
60
+ future.callbacks.each { |callback| call_in_fiber(callback, result) }
61
+ end
62
+
63
+ dispatcher.register_handler(target, 'failed') do |exception|
64
+ future.fail(exception)
65
+ end
66
+
67
+ future
68
+ end
69
+
70
+ def execute_activity!(activity_class, *input, **args)
71
+ future = execute_activity(activity_class, *input, **args)
72
+ result = future.get
73
+
74
+ raise result if future.failed?
75
+
76
+ result
77
+ end
78
+
79
+ # TODO: how to handle failures?
80
+ def execute_local_activity(activity_class, *input, **args)
81
+ input << args unless args.empty?
82
+
83
+ side_effect do
84
+ # TODO: this probably requires a local context implementation
85
+ context = Activity::Context.new(nil, nil)
86
+ activity_class.execute_in_context(context, input)
87
+ end
88
+ end
89
+
90
+ def execute_workflow(workflow_class, *input, **args)
91
+ options = args.delete(:options) || {}
92
+ input << args unless args.empty?
93
+
94
+ execution_options = ExecutionOptions.new(workflow_class, options)
95
+
96
+ command = Command::StartChildWorkflow.new(
97
+ workflow_id: options[:workflow_id] || SecureRandom.uuid,
98
+ workflow_type: execution_options.name,
99
+ input: input,
100
+ namespace: execution_options.namespace,
101
+ task_queue: execution_options.task_queue,
102
+ retry_policy: execution_options.retry_policy,
103
+ timeouts: execution_options.timeouts,
104
+ headers: execution_options.headers
105
+ )
106
+
107
+ target, cancelation_id = schedule_command(command)
108
+ future = Future.new(target, self, cancelation_id: cancelation_id)
109
+
110
+ dispatcher.register_handler(target, 'completed') do |result|
111
+ future.set(result)
112
+ future.callbacks.each { |callback| call_in_fiber(callback, result) }
113
+ end
114
+
115
+ dispatcher.register_handler(target, 'failed') do |exception|
116
+ future.fail(exception)
117
+ end
118
+
119
+ future
120
+ end
121
+
122
+ def execute_workflow!(workflow_class, *input, **args)
123
+ future = execute_workflow(workflow_class, *input, **args)
124
+ result = future.get
125
+
126
+ raise result if future.failed?
127
+
128
+ result
129
+ end
130
+
131
+ def side_effect(&block)
132
+ marker = state_manager.next_side_effect
133
+ return marker.last if marker
134
+
135
+ result = block.call
136
+ command = Command::RecordMarker.new(name: StateManager::SIDE_EFFECT_MARKER, details: result)
137
+ schedule_command(command)
138
+
139
+ result
140
+ end
141
+
142
+ def sleep(timeout)
143
+ start_timer(timeout).wait
144
+ end
145
+
146
+ def start_timer(timeout, timer_id = nil)
147
+ command = Command::StartTimer.new(timeout: timeout, timer_id: timer_id)
148
+ target, cancelation_id = schedule_command(command)
149
+ future = Future.new(target, self, cancelation_id: cancelation_id)
150
+
151
+ dispatcher.register_handler(target, 'fired') do |result|
152
+ future.set(result)
153
+ future.callbacks.each { |callback| call_in_fiber(callback, result) }
154
+ end
155
+
156
+ dispatcher.register_handler(target, 'canceled') do |exception|
157
+ future.fail(exception)
158
+ end
159
+
160
+ future
161
+ end
162
+
163
+ def cancel_timer(timer_id)
164
+ command = Command::CancelTimer.new(timer_id: timer_id)
165
+ schedule_command(command)
166
+ end
167
+
168
+ # TODO: check if workflow can be completed
169
+ def complete(result = nil)
170
+ command = Command::CompleteWorkflow.new(result: result)
171
+ schedule_command(command)
172
+ end
173
+
174
+ # TODO: check if workflow can be failed
175
+ def fail(exception)
176
+ command = Command::FailWorkflow.new(exception: exception)
177
+ schedule_command(command)
178
+ end
179
+
180
+ def wait_for_all(*futures)
181
+ futures.each(&:wait)
182
+
183
+ return
184
+ end
185
+
186
+ def wait_for(future)
187
+ fiber = Fiber.current
188
+
189
+ dispatcher.register_handler(future.target, Dispatcher::WILDCARD) do
190
+ fiber.resume if future.finished?
191
+ end
192
+
193
+ Fiber.yield
194
+
195
+ return
196
+ end
197
+
198
+ def now
199
+ state_manager.local_time
200
+ end
201
+
202
+ def on_signal(&block)
203
+ target = History::EventTarget.workflow
204
+
205
+ dispatcher.register_handler(target, 'signaled') do |signal, input|
206
+ call_in_fiber(block, signal, input)
207
+ end
208
+ end
209
+
210
+ def cancel_activity(activity_id)
211
+ command = Command::RequestActivityCancellation.new(activity_id: activity_id)
212
+
213
+ schedule_command(command)
214
+ end
215
+
216
+ def cancel(target, cancelation_id)
217
+ case target.type
218
+ when History::EventTarget::ACTIVITY_TYPE
219
+ cancel_activity(cancelation_id)
220
+ when History::EventTarget::TIMER_TYPE
221
+ cancel_timer(cancelation_id)
222
+ else
223
+ raise "#{target} can not be canceled"
224
+ end
225
+ end
226
+
227
+ private
228
+
229
+ attr_reader :state_manager, :dispatcher, :metadata
230
+
231
+ def schedule_command(command)
232
+ state_manager.schedule(command)
233
+ end
234
+
235
+ def call_in_fiber(block, *args)
236
+ Fiber.new do
237
+ Temporal::ThreadLocalContext.set(self)
238
+ block.call(*args)
239
+ end.resume
240
+ end
241
+ end
242
+ end
243
+ end