temporal-ruby 0.0.1.pre.pre1 → 0.0.1

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/README.md +23 -3
  4. data/lib/gen/temporal/api/command/v1/message_pb.rb +1 -1
  5. data/lib/gen/temporal/api/enums/v1/common_pb.rb +7 -0
  6. data/lib/gen/temporal/api/errordetails/v1/message_pb.rb +5 -6
  7. data/lib/gen/temporal/api/version/v1/message_pb.rb +19 -8
  8. data/lib/gen/temporal/api/workflowservice/v1/request_response_pb.rb +19 -8
  9. data/lib/gen/temporal/api/workflowservice/v1/service_services_pb.rb +0 -3
  10. data/lib/temporal.rb +104 -0
  11. data/lib/temporal/activity/context.rb +5 -1
  12. data/lib/temporal/activity/poller.rb +26 -9
  13. data/lib/temporal/activity/task_processor.rb +33 -20
  14. data/lib/temporal/client/converter/base.rb +35 -0
  15. data/lib/temporal/client/converter/composite.rb +49 -0
  16. data/lib/temporal/client/converter/payload/bytes.rb +30 -0
  17. data/lib/temporal/client/converter/payload/json.rb +28 -0
  18. data/lib/temporal/client/converter/payload/nil.rb +27 -0
  19. data/lib/temporal/client/grpc_client.rb +102 -27
  20. data/lib/temporal/client/retryer.rb +49 -0
  21. data/lib/temporal/client/serializer.rb +2 -0
  22. data/lib/temporal/client/serializer/cancel_timer.rb +2 -2
  23. data/lib/temporal/client/serializer/complete_workflow.rb +6 -4
  24. data/lib/temporal/client/serializer/continue_as_new.rb +37 -0
  25. data/lib/temporal/client/serializer/fail_workflow.rb +2 -2
  26. data/lib/temporal/client/serializer/failure.rb +4 -2
  27. data/lib/temporal/client/serializer/record_marker.rb +6 -4
  28. data/lib/temporal/client/serializer/request_activity_cancellation.rb +2 -2
  29. data/lib/temporal/client/serializer/retry_policy.rb +24 -0
  30. data/lib/temporal/client/serializer/schedule_activity.rb +8 -20
  31. data/lib/temporal/client/serializer/start_child_workflow.rb +9 -20
  32. data/lib/temporal/client/serializer/start_timer.rb +2 -2
  33. data/lib/temporal/concerns/payloads.rb +51 -0
  34. data/lib/temporal/configuration.rb +31 -4
  35. data/lib/temporal/error_handler.rb +11 -0
  36. data/lib/temporal/errors.rb +24 -0
  37. data/lib/temporal/execution_options.rb +9 -1
  38. data/lib/temporal/json.rb +3 -1
  39. data/lib/temporal/logger.rb +17 -0
  40. data/lib/temporal/metadata.rb +11 -3
  41. data/lib/temporal/metadata/activity.rb +15 -2
  42. data/lib/temporal/metadata/workflow.rb +8 -0
  43. data/lib/temporal/metadata/workflow_task.rb +11 -0
  44. data/lib/temporal/retry_policy.rb +6 -9
  45. data/lib/temporal/saga/concern.rb +1 -1
  46. data/lib/temporal/testing.rb +1 -0
  47. data/lib/temporal/testing/future_registry.rb +1 -1
  48. data/lib/temporal/testing/local_activity_context.rb +1 -1
  49. data/lib/temporal/testing/local_workflow_context.rb +38 -14
  50. data/lib/temporal/testing/scheduled_workflows.rb +75 -0
  51. data/lib/temporal/testing/temporal_override.rb +35 -7
  52. data/lib/temporal/testing/workflow_override.rb +6 -1
  53. data/lib/temporal/version.rb +1 -1
  54. data/lib/temporal/worker.rb +28 -10
  55. data/lib/temporal/workflow.rb +8 -2
  56. data/lib/temporal/workflow/command.rb +3 -0
  57. data/lib/temporal/workflow/context.rb +40 -5
  58. data/lib/temporal/workflow/errors.rb +39 -0
  59. data/lib/temporal/workflow/executor.rb +1 -1
  60. data/lib/temporal/workflow/future.rb +18 -6
  61. data/lib/temporal/workflow/history/event.rb +1 -3
  62. data/lib/temporal/workflow/history/event_target.rb +4 -0
  63. data/lib/temporal/workflow/history/window.rb +1 -1
  64. data/lib/temporal/workflow/poller.rb +41 -13
  65. data/lib/temporal/workflow/replay_aware_logger.rb +4 -4
  66. data/lib/temporal/workflow/state_manager.rb +33 -52
  67. data/lib/temporal/workflow/task_processor.rb +41 -11
  68. metadata +21 -9
  69. data/lib/temporal/client/serializer/payload.rb +0 -25
@@ -2,18 +2,22 @@ require 'temporal/client'
2
2
  require 'temporal/thread_pool'
3
3
  require 'temporal/middleware/chain'
4
4
  require 'temporal/activity/task_processor'
5
+ require 'temporal/error_handler'
5
6
 
6
7
  module Temporal
7
8
  class Activity
8
9
  class Poller
9
- THREAD_POOL_SIZE = 20
10
+ DEFAULT_OPTIONS = {
11
+ thread_pool_size: 20
12
+ }.freeze
10
13
 
11
- def initialize(namespace, task_queue, activity_lookup, middleware = [])
14
+ def initialize(namespace, task_queue, activity_lookup, middleware = [], options = {})
12
15
  @namespace = namespace
13
16
  @task_queue = task_queue
14
17
  @activity_lookup = activity_lookup
15
18
  @middleware = middleware
16
19
  @shutting_down = false
20
+ @options = DEFAULT_OPTIONS.merge(options)
17
21
  end
18
22
 
19
23
  def start
@@ -21,18 +25,23 @@ module Temporal
21
25
  @thread = Thread.new(&method(:poll_loop))
22
26
  end
23
27
 
24
- def stop
28
+ def stop_polling
25
29
  @shutting_down = true
26
- Thread.new { Temporal.logger.info('Shutting down activity poller') }.join
30
+ Temporal.logger.info('Shutting down activity poller', { namespace: namespace, task_queue: task_queue })
31
+ end
32
+
33
+ def cancel_pending_requests
34
+ client.cancel_polling_request
27
35
  end
28
36
 
29
37
  def wait
30
38
  thread.join
39
+ thread_pool.shutdown
31
40
  end
32
41
 
33
42
  private
34
43
 
35
- attr_reader :namespace, :task_queue, :activity_lookup, :middleware, :thread
44
+ attr_reader :namespace, :task_queue, :activity_lookup, :middleware, :options, :thread
36
45
 
37
46
  def client
38
47
  @client ||= Temporal::Client.generate
@@ -43,14 +52,20 @@ module Temporal
43
52
  end
44
53
 
45
54
  def poll_loop
55
+ last_poll_time = Time.now
56
+ metrics_tags = { namespace: namespace, task_queue: task_queue }.freeze
57
+
46
58
  loop do
47
59
  thread_pool.wait_for_available_threads
48
60
 
49
61
  return if shutting_down?
50
62
 
51
- Temporal.logger.debug("Polling activity task queue (#{namespace} / #{task_queue})")
63
+ time_diff_ms = ((Time.now - last_poll_time) * 1000).round
64
+ Temporal.metrics.timing('activity_poller.time_since_last_poll', time_diff_ms, metrics_tags)
65
+ Temporal.logger.debug("Polling activity task queue", { namespace: namespace, task_queue: task_queue })
52
66
 
53
67
  task = poll_for_task
68
+ last_poll_time = Time.now
54
69
  next unless task&.activity_type
55
70
 
56
71
  thread_pool.schedule { process(task) }
@@ -60,19 +75,21 @@ module Temporal
60
75
  def poll_for_task
61
76
  client.poll_activity_task_queue(namespace: namespace, task_queue: task_queue)
62
77
  rescue StandardError => error
63
- Temporal.logger.error("Unable to poll activity task queue: #{error.inspect}")
78
+ Temporal.logger.error("Unable to poll activity task queue", { namespace: namespace, task_queue: task_queue, error: error.inspect })
79
+
80
+ Temporal::ErrorHandler.handle(error)
81
+
64
82
  nil
65
83
  end
66
84
 
67
85
  def process(task)
68
- client = Temporal::Client.generate
69
86
  middleware_chain = Middleware::Chain.new(middleware)
70
87
 
71
88
  TaskProcessor.new(task, namespace, activity_lookup, client, middleware_chain).process
72
89
  end
73
90
 
74
91
  def thread_pool
75
- @thread_pool ||= ThreadPool.new(THREAD_POOL_SIZE)
92
+ @thread_pool ||= ThreadPool.new(options[:thread_pool_size])
76
93
  end
77
94
  end
78
95
  end
@@ -1,14 +1,19 @@
1
1
  require 'temporal/metadata'
2
+ require 'temporal/error_handler'
2
3
  require 'temporal/errors'
3
4
  require 'temporal/activity/context'
4
- require 'temporal/json'
5
+ require 'temporal/concerns/payloads'
6
+ require 'temporal/client/retryer'
5
7
 
6
8
  module Temporal
7
9
  class Activity
8
10
  class TaskProcessor
11
+ include Concerns::Payloads
12
+
9
13
  def initialize(task, namespace, activity_lookup, client, middleware_chain)
10
14
  @task = task
11
15
  @namespace = namespace
16
+ @metadata = Metadata.generate(Metadata::ACTIVITY_TYPE, task, namespace)
12
17
  @task_token = task.task_token
13
18
  @activity_name = task.activity_type.name
14
19
  @activity_class = activity_lookup.find(activity_name)
@@ -19,33 +24,34 @@ module Temporal
19
24
  def process
20
25
  start_time = Time.now
21
26
 
22
- Temporal.logger.info("Processing activity task for #{activity_name}")
27
+ Temporal.logger.debug("Processing Activity task", metadata.to_h)
23
28
  Temporal.metrics.timing('activity_task.queue_time', queue_time_ms, activity: activity_name)
24
29
 
30
+ context = Activity::Context.new(client, metadata)
31
+
25
32
  if !activity_class
26
33
  raise ActivityNotRegistered, 'Activity is not registered with this worker'
27
34
  end
28
35
 
29
- metadata = Metadata.generate(Metadata::ACTIVITY_TYPE, task, namespace)
30
- context = Activity::Context.new(client, metadata)
31
-
32
36
  result = middleware_chain.invoke(metadata) do
33
- activity_class.execute_in_context(context, parse_payload(task.input))
37
+ activity_class.execute_in_context(context, from_payloads(task.input))
34
38
  end
35
39
 
36
40
  # Do not complete asynchronous activities, these should be completed manually
37
41
  respond_completed(result) unless context.async?
38
42
  rescue StandardError, ScriptError => error
43
+ Temporal::ErrorHandler.handle(error, metadata: metadata)
44
+
39
45
  respond_failed(error)
40
46
  ensure
41
47
  time_diff_ms = ((Time.now - start_time) * 1000).round
42
48
  Temporal.metrics.timing('activity_task.latency', time_diff_ms, activity: activity_name)
43
- Temporal.logger.debug("Activity task processed in #{time_diff_ms}ms")
49
+ Temporal.logger.debug("Activity task processed", metadata.to_h.merge(execution_time: time_diff_ms))
44
50
  end
45
51
 
46
52
  private
47
53
 
48
- attr_reader :task, :namespace, :task_token, :activity_name, :activity_class, :client, :middleware_chain
54
+ attr_reader :task, :namespace, :task_token, :activity_name, :activity_class, :client, :middleware_chain, :metadata
49
55
 
50
56
  def queue_time_ms
51
57
  scheduled = task.current_attempt_scheduled_time.to_f
@@ -54,24 +60,31 @@ module Temporal
54
60
  end
55
61
 
56
62
  def respond_completed(result)
57
- Temporal.logger.info("Activity #{activity_name} completed")
58
- client.respond_activity_task_completed(task_token: task_token, result: result)
63
+ Temporal.logger.info("Activity task completed", metadata.to_h)
64
+ log_retry = proc do
65
+ Temporal.logger.debug("Failed to report activity task completion, retrying", metadata.to_h)
66
+ end
67
+ Temporal::Client::Retryer.with_retries(on_retry: log_retry) do
68
+ client.respond_activity_task_completed(task_token: task_token, result: result)
69
+ end
59
70
  rescue StandardError => error
60
- Temporal.logger.error("Unable to complete Activity #{activity_name}: #{error.inspect}")
71
+ Temporal.logger.error("Unable to complete Activity", metadata.to_h.merge(error: error.inspect))
72
+
73
+ Temporal::ErrorHandler.handle(error, metadata: metadata)
61
74
  end
62
75
 
63
76
  def respond_failed(error)
64
- Temporal.logger.error("Activity #{activity_name} failed with: #{error.inspect}")
65
- client.respond_activity_task_failed(task_token: task_token, exception: error)
77
+ Temporal.logger.error("Activity task failed", metadata.to_h.merge(error: error.inspect))
78
+ log_retry = proc do
79
+ Temporal.logger.debug("Failed to report activity task failure, retrying", metadata.to_h)
80
+ end
81
+ Temporal::Client::Retryer.with_retries(on_retry: log_retry) do
82
+ client.respond_activity_task_failed(task_token: task_token, exception: error)
83
+ end
66
84
  rescue StandardError => error
67
- Temporal.logger.error("Unable to fail Activity #{activity_name}: #{error.inspect}")
68
- end
69
-
70
- def parse_payload(payload)
71
- return if payload.nil? || payload.payloads.empty?
85
+ Temporal.logger.error("Unable to fail Activity task", metadata.to_h.merge(error: error.inspect))
72
86
 
73
- binary = payload.payloads.first.data
74
- JSON.deserialize(binary)
87
+ Temporal::ErrorHandler.handle(error, metadata: metadata)
75
88
  end
76
89
  end
77
90
  end
@@ -0,0 +1,35 @@
1
+ module Temporal
2
+ module Client
3
+ module Converter
4
+ class Base
5
+ def initialize(payload_converter:)
6
+ @payload_converter = payload_converter
7
+ end
8
+
9
+ def from_payloads(payloads)
10
+ return nil if payloads.nil?
11
+ payloads.payloads.map(&method(:from_payload))
12
+ end
13
+
14
+ def from_payload(payload)
15
+ payload_converter.from_payload(payload)
16
+ end
17
+
18
+ def to_payloads(data)
19
+ return nil if data.nil?
20
+ Temporal::Api::Common::V1::Payloads.new(
21
+ payloads: data.map(&method(:to_payload))
22
+ )
23
+ end
24
+
25
+ def to_payload(data)
26
+ payload_converter.to_payload(data)
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :payload_converter
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ require 'temporal/client/converter/base'
2
+
3
+ module Temporal
4
+ module Client
5
+ module Converter
6
+ class Composite < Base
7
+ class ConverterNotFound < RuntimeError; end
8
+ class MetadataNotSet < RuntimeError; end
9
+
10
+ def initialize(payload_converters:)
11
+ @payload_converters = payload_converters
12
+ @payload_converters_by_encoding = {}
13
+
14
+ @payload_converters.each do |converter|
15
+ @payload_converters_by_encoding[converter.encoding] = converter
16
+ end
17
+ end
18
+
19
+ def from_payload(payload)
20
+ encoding = payload.metadata['encoding']
21
+ if encoding.nil?
22
+ raise MetadataNotSet
23
+ end
24
+
25
+ converter = payload_converters_by_encoding[encoding]
26
+
27
+ if converter.nil?
28
+ raise ConverterNotFound
29
+ end
30
+
31
+ converter.from_payload(payload)
32
+ end
33
+
34
+ def to_payload(data)
35
+ payload_converters.each do |converter|
36
+ payload = converter.to_payload(data)
37
+ return payload unless payload.nil?
38
+ end
39
+
40
+ raise ConverterNotFound
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :payload_converters, :payload_converters_by_encoding
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,30 @@
1
+ require 'temporal/json'
2
+
3
+ module Temporal
4
+ module Client
5
+ module Converter
6
+ module Payload
7
+ class Bytes
8
+ ENCODING = 'binary/plain'.freeze
9
+
10
+ def encoding
11
+ ENCODING
12
+ end
13
+
14
+ def from_payload(payload)
15
+ payload.data
16
+ end
17
+
18
+ def to_payload(data)
19
+ return nil unless data.is_a?(String) && data.encoding == Encoding::ASCII_8BIT
20
+
21
+ Temporal::Api::Common::V1::Payload.new(
22
+ metadata: { 'encoding' => ENCODING },
23
+ data: data
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ require 'temporal/json'
2
+
3
+ module Temporal
4
+ module Client
5
+ module Converter
6
+ module Payload
7
+ class JSON
8
+ ENCODING = 'json/plain'.freeze
9
+
10
+ def encoding
11
+ ENCODING
12
+ end
13
+
14
+ def from_payload(payload)
15
+ Temporal::JSON.deserialize(payload.data)
16
+ end
17
+
18
+ def to_payload(data)
19
+ Temporal::Api::Common::V1::Payload.new(
20
+ metadata: { 'encoding' => ENCODING },
21
+ data: Temporal::JSON.serialize(data).b
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ module Temporal
2
+ module Client
3
+ module Converter
4
+ module Payload
5
+ class Nil
6
+ ENCODING = 'binary/null'.freeze
7
+
8
+ def encoding
9
+ ENCODING
10
+ end
11
+
12
+ def from_payload(payload)
13
+ nil
14
+ end
15
+
16
+ def to_payload(data)
17
+ return nil unless data.nil?
18
+
19
+ Temporal::Api::Common::V1::Payload.new(
20
+ metadata: { 'encoding' => ENCODING }
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,30 +1,39 @@
1
1
  require 'grpc'
2
2
  require 'google/protobuf/well_known_types'
3
3
  require 'securerandom'
4
- require 'temporal/json'
5
4
  require 'temporal/client/errors'
6
5
  require 'temporal/client/serializer'
7
- require 'temporal/client/serializer/payload'
8
6
  require 'temporal/client/serializer/failure'
9
7
  require 'gen/temporal/api/workflowservice/v1/service_services_pb'
8
+ require 'temporal/concerns/payloads'
10
9
 
11
10
  module Temporal
12
11
  module Client
13
12
  class GRPCClient
13
+ include Concerns::Payloads
14
+
14
15
  WORKFLOW_ID_REUSE_POLICY = {
15
16
  allow_failed: Temporal::Api::Enums::V1::WorkflowIdReusePolicy::WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY,
16
17
  allow: Temporal::Api::Enums::V1::WorkflowIdReusePolicy::WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE,
17
18
  reject: Temporal::Api::Enums::V1::WorkflowIdReusePolicy::WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE
18
19
  }.freeze
19
20
 
21
+ HISTORY_EVENT_FILTER = {
22
+ all: Temporal::Api::Enums::V1::HistoryEventFilterType::HISTORY_EVENT_FILTER_TYPE_ALL_EVENT,
23
+ close: Temporal::Api::Enums::V1::HistoryEventFilterType::HISTORY_EVENT_FILTER_TYPE_CLOSE_EVENT,
24
+ }.freeze
25
+
20
26
  def initialize(host, port, identity)
21
27
  @url = "#{host}:#{port}"
22
28
  @identity = identity
29
+ @poll = true
30
+ @poll_mutex = Mutex.new
31
+ @poll_request = nil
23
32
  end
24
33
 
25
34
  def register_namespace(name:, description: nil, global: false, retention_period: 10)
26
35
  request = Temporal::Api::WorkflowService::V1::RegisterNamespaceRequest.new(
27
- name: name,
36
+ namespace: name,
28
37
  description: description,
29
38
  is_global_namespace: global,
30
39
  workflow_execution_retention_period: Google::Protobuf::Duration.new(
@@ -37,7 +46,7 @@ module Temporal
37
46
  end
38
47
 
39
48
  def describe_namespace(name:)
40
- request = Temporal::Api::WorkflowService::V1::DescribeNamespaceRequest.new(name: name)
49
+ request = Temporal::Api::WorkflowService::V1::DescribeNamespaceRequest.new(namespace: name)
41
50
  client.describe_namespace(request)
42
51
  end
43
52
 
@@ -48,7 +57,7 @@ module Temporal
48
57
 
49
58
  def update_namespace(name:, description:)
50
59
  request = Temporal::Api::WorkflowService::V1::UpdateNamespaceRequest.new(
51
- name: name,
60
+ namespace: name,
52
61
  update_info: Temporal::Api::WorkflowService::V1::UpdateNamespaceInfo.new(
53
62
  description: description
54
63
  )
@@ -57,7 +66,7 @@ module Temporal
57
66
  end
58
67
 
59
68
  def deprecate_namespace(name:)
60
- request = Temporal::Api::WorkflowService::V1::DeprecateNamespaceRequest.new(name: name)
69
+ request = Temporal::Api::WorkflowService::V1::DeprecateNamespaceRequest.new(namespace: name)
61
70
  client.deprecate_namespace(request)
62
71
  end
63
72
 
@@ -68,9 +77,11 @@ module Temporal
68
77
  task_queue:,
69
78
  input: nil,
70
79
  execution_timeout:,
80
+ run_timeout:,
71
81
  task_timeout:,
72
82
  workflow_id_reuse_policy: nil,
73
- headers: nil
83
+ headers: nil,
84
+ cron_schedule: nil
74
85
  )
75
86
  request = Temporal::Api::WorkflowService::V1::StartWorkflowExecutionRequest.new(
76
87
  identity: identity,
@@ -82,14 +93,15 @@ module Temporal
82
93
  task_queue: Temporal::Api::TaskQueue::V1::TaskQueue.new(
83
94
  name: task_queue
84
95
  ),
85
- input: Serializer::Payload.new(input).to_proto,
96
+ input: to_payloads(input),
86
97
  workflow_execution_timeout: execution_timeout,
87
- workflow_run_timeout: execution_timeout,
98
+ workflow_run_timeout: run_timeout,
88
99
  workflow_task_timeout: task_timeout,
89
100
  request_id: SecureRandom.uuid,
90
101
  header: Temporal::Api::Common::V1::Header.new(
91
102
  fields: headers
92
- )
103
+ ),
104
+ cron_schedule: cron_schedule
93
105
  )
94
106
 
95
107
  if workflow_id_reuse_policy
@@ -102,20 +114,43 @@ module Temporal
102
114
  client.start_workflow_execution(request)
103
115
  rescue GRPC::AlreadyExists => e
104
116
  # Feel like there should be cleaner way to do this...
105
- run_id = e.details[/RunId: (.*)\.$/, 1]
117
+ run_id = e.details[/RunId: ([a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+)/, 1]
106
118
  raise Temporal::WorkflowExecutionAlreadyStartedFailure.new(e.details, run_id)
107
119
  end
108
120
 
109
- def get_workflow_execution_history(namespace:, workflow_id:, run_id:)
121
+ SERVER_MAX_GET_WORKFLOW_EXECUTION_HISTORY_POLL = 30
122
+
123
+ def get_workflow_execution_history(
124
+ namespace:,
125
+ workflow_id:,
126
+ run_id:,
127
+ next_page_token: nil,
128
+ wait_for_new_event: false,
129
+ event_type: :all,
130
+ timeout: nil
131
+ )
132
+ if wait_for_new_event
133
+ if timeout.nil?
134
+ # This is an internal error. Wrappers should enforce this.
135
+ raise "You must specify a timeout when wait_for_new_event = true."
136
+ elsif timeout > SERVER_MAX_GET_WORKFLOW_EXECUTION_HISTORY_POLL
137
+ raise ClientError.new(
138
+ "You may not specify a timeout of more than #{SERVER_MAX_GET_WORKFLOW_EXECUTION_HISTORY_POLL} seconds, got: #{timeout}."
139
+ )
140
+ end
141
+ end
110
142
  request = Temporal::Api::WorkflowService::V1::GetWorkflowExecutionHistoryRequest.new(
111
143
  namespace: namespace,
112
144
  execution: Temporal::Api::Common::V1::WorkflowExecution.new(
113
145
  workflow_id: workflow_id,
114
146
  run_id: run_id
115
- )
147
+ ),
148
+ next_page_token: next_page_token,
149
+ wait_new_event: wait_for_new_event,
150
+ history_event_filter_type: HISTORY_EVENT_FILTER[event_type]
116
151
  )
117
-
118
- client.get_workflow_execution_history(request)
152
+ deadline = timeout ? Time.now + timeout : nil
153
+ client.get_workflow_execution_history(request, deadline: deadline)
119
154
  end
120
155
 
121
156
  def poll_workflow_task_queue(namespace:, task_queue:)
@@ -126,7 +161,13 @@ module Temporal
126
161
  name: task_queue
127
162
  )
128
163
  )
129
- client.poll_workflow_task_queue(request)
164
+
165
+ poll_mutex.synchronize do
166
+ return unless can_poll?
167
+ @poll_request = client.poll_workflow_task_queue(request, return_op: true)
168
+ end
169
+
170
+ poll_request.execute
130
171
  end
131
172
 
132
173
  def respond_workflow_task_completed(task_token:, commands:)
@@ -156,13 +197,19 @@ module Temporal
156
197
  name: task_queue
157
198
  )
158
199
  )
159
- client.poll_activity_task_queue(request)
200
+
201
+ poll_mutex.synchronize do
202
+ return unless can_poll?
203
+ @poll_request = client.poll_activity_task_queue(request, return_op: true)
204
+ end
205
+
206
+ poll_request.execute
160
207
  end
161
208
 
162
209
  def record_activity_task_heartbeat(task_token:, details: nil)
163
210
  request = Temporal::Api::WorkflowService::V1::RecordActivityTaskHeartbeatRequest.new(
164
211
  task_token: task_token,
165
- details: Serializer::Payload.new(details).to_proto,
212
+ details: to_details_payloads(details),
166
213
  identity: identity
167
214
  )
168
215
  client.record_activity_task_heartbeat(request)
@@ -176,7 +223,7 @@ module Temporal
176
223
  request = Temporal::Api::WorkflowService::V1::RespondActivityTaskCompletedRequest.new(
177
224
  identity: identity,
178
225
  task_token: task_token,
179
- result: Serializer::Payload.new(result).to_proto,
226
+ result: to_result_payloads(result),
180
227
  )
181
228
  client.respond_activity_task_completed(request)
182
229
  end
@@ -188,7 +235,7 @@ module Temporal
188
235
  workflow_id: workflow_id,
189
236
  run_id: run_id,
190
237
  activity_id: activity_id,
191
- result: Serializer::Payload.new(result).to_proto
238
+ result: to_result_payloads(result)
192
239
  )
193
240
  client.respond_activity_task_completed_by_id(request)
194
241
  end
@@ -217,7 +264,7 @@ module Temporal
217
264
  def respond_activity_task_canceled(task_token:, details: nil)
218
265
  request = Temporal::Api::WorkflowService::V1::RespondActivityTaskCanceledRequest.new(
219
266
  task_token: task_token,
220
- details: Serializer::Payload.new(details).to_proto,
267
+ details: to_details_payloads(details),
221
268
  identity: identity
222
269
  )
223
270
  client.respond_activity_task_canceled(request)
@@ -239,7 +286,7 @@ module Temporal
239
286
  run_id: run_id
240
287
  ),
241
288
  signal_name: signal,
242
- input: Serializer::Payload.new(input).to_proto,
289
+ input: to_signal_payloads(input),
243
290
  identity: identity
244
291
  )
245
292
  client.signal_workflow_execution(request)
@@ -254,7 +301,7 @@ module Temporal
254
301
  namespace: namespace,
255
302
  workflow_execution: Temporal::Api::Common::V1::WorkflowExecution.new(
256
303
  workflow_id: workflow_id,
257
- run_id: run_id
304
+ run_id: run_id,
258
305
  ),
259
306
  reason: reason,
260
307
  workflow_task_finish_event_id: workflow_task_event_id
@@ -262,8 +309,25 @@ module Temporal
262
309
  client.reset_workflow_execution(request)
263
310
  end
264
311
 
265
- def terminate_workflow_execution
266
- raise NotImplementedError
312
+ def terminate_workflow_execution(
313
+ namespace:,
314
+ workflow_id:,
315
+ run_id:,
316
+ reason: nil,
317
+ details: nil
318
+ )
319
+ request = Temporal::Api::WorkflowService::V1::TerminateWorkflowExecutionRequest.new(
320
+ identity: identity,
321
+ namespace: namespace,
322
+ workflow_execution: Temporal::Api::Common::V1::WorkflowExecution.new(
323
+ workflow_id: workflow_id,
324
+ run_id: run_id,
325
+ ),
326
+ reason: reason,
327
+ details: to_details_payloads(details)
328
+ )
329
+
330
+ client.terminate_workflow_execution(request)
267
331
  end
268
332
 
269
333
  def list_open_workflow_executions
@@ -329,17 +393,28 @@ module Temporal
329
393
  client.describe_task_queue(request)
330
394
  end
331
395
 
396
+ def cancel_polling_request
397
+ poll_mutex.synchronize do
398
+ @poll = false
399
+ poll_request&.cancel
400
+ end
401
+ end
402
+
332
403
  private
333
404
 
334
- attr_reader :url, :identity
405
+ attr_reader :url, :identity, :poll_mutex, :poll_request
335
406
 
336
407
  def client
337
408
  @client ||= Temporal::Api::WorkflowService::V1::WorkflowService::Stub.new(
338
409
  url,
339
410
  :this_channel_is_insecure,
340
- timeout: 5
411
+ timeout: 60
341
412
  )
342
413
  end
414
+
415
+ def can_poll?
416
+ @poll
417
+ end
343
418
  end
344
419
  end
345
420
  end