distribot 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +10 -0
- data/Dockerfile +9 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +153 -0
- data/LICENSE +201 -0
- data/README.md +107 -0
- data/Rakefile +16 -0
- data/bin/distribot.flow-created +6 -0
- data/bin/distribot.flow-finished +6 -0
- data/bin/distribot.handler-finished +6 -0
- data/bin/distribot.phase-finished +6 -0
- data/bin/distribot.phase-started +6 -0
- data/bin/distribot.task-finished +6 -0
- data/distribot.gemspec +35 -0
- data/docker-compose.yml +29 -0
- data/examples/controller +168 -0
- data/examples/distribot.eye +49 -0
- data/examples/status +38 -0
- data/examples/worker +135 -0
- data/lib/distribot/connector.rb +162 -0
- data/lib/distribot/flow.rb +200 -0
- data/lib/distribot/flow_created_handler.rb +12 -0
- data/lib/distribot/flow_finished_handler.rb +12 -0
- data/lib/distribot/handler.rb +40 -0
- data/lib/distribot/handler_finished_handler.rb +29 -0
- data/lib/distribot/phase.rb +46 -0
- data/lib/distribot/phase_finished_handler.rb +19 -0
- data/lib/distribot/phase_handler.rb +15 -0
- data/lib/distribot/phase_started_handler.rb +69 -0
- data/lib/distribot/task_finished_handler.rb +37 -0
- data/lib/distribot/worker.rb +148 -0
- data/lib/distribot.rb +108 -0
- data/provision/nodes.sh +80 -0
- data/provision/templates/fluentd.conf +27 -0
- data/spec/distribot/bunny_connector_spec.rb +196 -0
- data/spec/distribot/connection_sharer_spec.rb +34 -0
- data/spec/distribot/connector_spec.rb +63 -0
- data/spec/distribot/flow_created_handler_spec.rb +32 -0
- data/spec/distribot/flow_finished_handler_spec.rb +32 -0
- data/spec/distribot/flow_spec.rb +661 -0
- data/spec/distribot/handler_finished_handler_spec.rb +112 -0
- data/spec/distribot/handler_spec.rb +32 -0
- data/spec/distribot/module_spec.rb +163 -0
- data/spec/distribot/multi_subscription_spec.rb +37 -0
- data/spec/distribot/phase_finished_handler_spec.rb +61 -0
- data/spec/distribot/phase_started_handler_spec.rb +150 -0
- data/spec/distribot/subscription_spec.rb +40 -0
- data/spec/distribot/task_finished_handler_spec.rb +71 -0
- data/spec/distribot/worker_spec.rb +281 -0
- data/spec/fixtures/simple_flow.json +49 -0
- data/spec/spec_helper.rb +74 -0
- metadata +371 -0
@@ -0,0 +1,200 @@
|
|
1
|
+
|
2
|
+
module Distribot
|
3
|
+
class NotRunningError < StandardError; end
|
4
|
+
class NotPausedError < StandardError; end
|
5
|
+
|
6
|
+
# rubocop:disable ClassLength
|
7
|
+
class Flow
|
8
|
+
attr_accessor :id, :phases, :consumer, :finished_callback_queue, :created_at, :data
|
9
|
+
|
10
|
+
def initialize(attrs = {})
|
11
|
+
self.id = attrs[:id]
|
12
|
+
self.created_at = attrs[:created_at] unless attrs[:created_at].nil?
|
13
|
+
self.phases = []
|
14
|
+
(attrs[:phases] || []).each do |options|
|
15
|
+
add_phase(options)
|
16
|
+
end
|
17
|
+
self.data = attrs[:data]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.active
|
21
|
+
redis.smembers('distribot.flows.active').map do |id|
|
22
|
+
self.find(id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.create!(attrs = {})
|
27
|
+
new(attrs).save!
|
28
|
+
end
|
29
|
+
|
30
|
+
# rubocop:disable Metrics/AbcSize
|
31
|
+
def save!(&block)
|
32
|
+
fail StandardError, 'Cannot re-save a flow' if id
|
33
|
+
self.id = SecureRandom.uuid
|
34
|
+
record_id = redis_id + ':definition'
|
35
|
+
self.created_at = Time.now.to_f
|
36
|
+
|
37
|
+
# Actually save the record:
|
38
|
+
redis.set record_id, serialize
|
39
|
+
|
40
|
+
# Transition into the first phase:
|
41
|
+
add_transition to: current_phase, timestamp: Time.now.utc.to_f
|
42
|
+
|
43
|
+
# Add our id to the list of active flows:
|
44
|
+
redis.sadd 'distribot.flows.active', id
|
45
|
+
redis.incr('distribot.flows.running')
|
46
|
+
|
47
|
+
# Announce our arrival to the rest of the system:
|
48
|
+
Distribot.publish! 'distribot.flow.created', flow_id: id
|
49
|
+
|
50
|
+
wait_for_flow_to_finish(block) if block_given?
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.find(id)
|
55
|
+
redis_id = Distribot.redis_id('flow', id)
|
56
|
+
raw_json = redis.get("#{redis_id}:definition") || return
|
57
|
+
new(
|
58
|
+
JSON.parse(raw_json, symbolize_names: true)
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_phase(options = {})
|
63
|
+
phases << Phase.new(options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def phase(name)
|
67
|
+
phases.find { |x| x.name == name }
|
68
|
+
end
|
69
|
+
|
70
|
+
def pause!
|
71
|
+
fail NotRunningError, 'Cannot pause unless running' unless running?
|
72
|
+
add_transition(
|
73
|
+
from: current_phase,
|
74
|
+
to: 'paused',
|
75
|
+
timestamp: Time.now.utc.to_f
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def resume!
|
80
|
+
fail NotPausedError, 'Cannot resume unless paused' unless paused?
|
81
|
+
|
82
|
+
# Find the last transition before we were paused:
|
83
|
+
prev_phase = transitions.reverse.find { |x| x.to != 'paused' }
|
84
|
+
# Back to where we once belonged
|
85
|
+
add_transition(
|
86
|
+
from: 'paused', to: prev_phase.to, timestamp: Time.now.utc.to_f
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def paused?
|
91
|
+
current_phase == 'paused'
|
92
|
+
end
|
93
|
+
|
94
|
+
def cancel!
|
95
|
+
fail NotRunningError, 'Cannot cancel unless running' unless running?
|
96
|
+
add_transition(
|
97
|
+
from: current_phase, to: 'canceled', timestamp: Time.now.utc.to_f
|
98
|
+
)
|
99
|
+
redis.decr 'distribot.flows.running'
|
100
|
+
redis.srem 'distribot.flows.active', id
|
101
|
+
end
|
102
|
+
|
103
|
+
def canceled?
|
104
|
+
current_phase == 'canceled'
|
105
|
+
end
|
106
|
+
|
107
|
+
def running?
|
108
|
+
! (paused? || canceled? || finished?)
|
109
|
+
end
|
110
|
+
|
111
|
+
def redis_id
|
112
|
+
@redis_id ||= Distribot.redis_id('flow', id)
|
113
|
+
end
|
114
|
+
|
115
|
+
def transition_to!(phase)
|
116
|
+
previous_transition = transitions.last
|
117
|
+
prev = previous_transition ? previous_transition[:to] : nil
|
118
|
+
add_transition(from: prev, to: phase, timestamp: Time.now.utc.to_f)
|
119
|
+
Distribot.publish!(
|
120
|
+
'distribot.flow.phase.started',
|
121
|
+
flow_id: id,
|
122
|
+
phase: phase
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_transition(item)
|
127
|
+
redis.sadd(redis_id + ':transitions', item.to_json)
|
128
|
+
end
|
129
|
+
|
130
|
+
def transitions
|
131
|
+
redis.smembers(redis_id + ':transitions').map do |item|
|
132
|
+
OpenStruct.new JSON.parse(item, symbolize_names: true)
|
133
|
+
end.sort_by(&:timestamp)
|
134
|
+
end
|
135
|
+
|
136
|
+
def current_phase
|
137
|
+
latest_transition = transitions.last
|
138
|
+
if latest_transition
|
139
|
+
latest_transition.to
|
140
|
+
else
|
141
|
+
phases.find(&:is_initial).name
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def next_phase
|
146
|
+
current = current_phase
|
147
|
+
phases.find { |x| x.name == current }.transitions_to
|
148
|
+
end
|
149
|
+
|
150
|
+
def finished?
|
151
|
+
phase(transitions.last.to).is_final
|
152
|
+
end
|
153
|
+
|
154
|
+
def stubbornly(task, &block)
|
155
|
+
loop do
|
156
|
+
begin
|
157
|
+
return block.call
|
158
|
+
rescue NoMethodError => e
|
159
|
+
warn "Error during #{task}: #{e} --- #{e.backtrace.join("\n")}"
|
160
|
+
sleep 1
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def wait_for_flow_to_finish(block)
|
168
|
+
Thread.new do
|
169
|
+
loop do
|
170
|
+
sleep 1
|
171
|
+
if finished? || canceled?
|
172
|
+
block.call(flow_id: id)
|
173
|
+
break
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.redis
|
180
|
+
Distribot.redis
|
181
|
+
end
|
182
|
+
|
183
|
+
def redis
|
184
|
+
self.class.redis
|
185
|
+
end
|
186
|
+
|
187
|
+
def serialize
|
188
|
+
to_hash.to_json
|
189
|
+
end
|
190
|
+
|
191
|
+
def to_hash
|
192
|
+
{
|
193
|
+
id: id,
|
194
|
+
created_at: created_at,
|
195
|
+
phases: phases.map(&:to_hash),
|
196
|
+
data: self.data
|
197
|
+
}
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
module Distribot
|
3
|
+
class FlowCreatedHandler
|
4
|
+
include Distribot::Handler
|
5
|
+
subscribe_to 'distribot.flow.created', handler: :callback
|
6
|
+
|
7
|
+
def callback(message)
|
8
|
+
flow = Distribot::Flow.find(message[:flow_id])
|
9
|
+
flow.transition_to! flow.next_phase
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
module Distribot
|
3
|
+
class FlowFinishedHandler
|
4
|
+
include Distribot::Handler
|
5
|
+
subscribe_to 'distribot.flow.finished', handler: :callback
|
6
|
+
|
7
|
+
def callback(message)
|
8
|
+
Distribot.redis.decr('distribot.flows.running')
|
9
|
+
Distribot.redis.srem 'distribot.flows.active', message[:flow_id]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
module Distribot
|
3
|
+
module Handler
|
4
|
+
attr_accessor :queue_name, :consumers
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
self.consumers = []
|
12
|
+
self.queue_name = self.class.queue
|
13
|
+
handler = self.class.handler
|
14
|
+
Distribot.subscribe(queue_name, self.class.subscribe_args) do |message|
|
15
|
+
send(handler, message)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.handler_for(klass)
|
20
|
+
klass.handler
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.queue_for(klass)
|
24
|
+
klass.queue
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
class << self
|
29
|
+
attr_accessor :queue, :handler, :subscribe_args
|
30
|
+
end
|
31
|
+
attr_reader :handler, :queue, :subscribe_args
|
32
|
+
|
33
|
+
def subscribe_to(queue_name, handler_args)
|
34
|
+
@queue = queue_name
|
35
|
+
@handler = handler_args.delete :handler
|
36
|
+
@subscribe_args = handler_args
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module Distribot
|
3
|
+
class HandlerFinishedHandler
|
4
|
+
include Distribot::Handler
|
5
|
+
subscribe_to 'distribot.flow.handler.finished', handler: :callback
|
6
|
+
|
7
|
+
def callback(message)
|
8
|
+
flow = Distribot::Flow.find(message[:flow_id])
|
9
|
+
phase = flow.phase(message[:phase])
|
10
|
+
|
11
|
+
Distribot.publish!(
|
12
|
+
'distribot.flow.phase.finished',
|
13
|
+
flow_id: flow.id,
|
14
|
+
phase: phase.name
|
15
|
+
) if self.all_phase_handler_tasks_are_complete?(flow, phase)
|
16
|
+
end
|
17
|
+
|
18
|
+
def all_phase_handler_tasks_are_complete?(flow, phase)
|
19
|
+
redis = Distribot.redis
|
20
|
+
name = phase.name
|
21
|
+
phase.handlers
|
22
|
+
.map { |h| "distribot.flow.#{flow.id}.#{name}.#{h}.tasks" }
|
23
|
+
.map { |task_counter_key| redis.get(task_counter_key) }
|
24
|
+
.reject(&:nil?)
|
25
|
+
.select { |val| val.to_i > 0 }
|
26
|
+
.empty?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
module Distribot
|
3
|
+
class Phase
|
4
|
+
attr_accessor :id,
|
5
|
+
:name,
|
6
|
+
:is_initial,
|
7
|
+
:is_final,
|
8
|
+
:transitions_to,
|
9
|
+
:on_error_transition_to,
|
10
|
+
:handlers
|
11
|
+
|
12
|
+
def initialize(attrs = {})
|
13
|
+
attrs.each do |key, val|
|
14
|
+
next if key.to_s == 'handlers'
|
15
|
+
public_send("#{key}=", val)
|
16
|
+
end
|
17
|
+
self.name = name
|
18
|
+
self.handlers = []
|
19
|
+
initialize_handlers(attrs[:handlers] || [])
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_hash
|
23
|
+
{
|
24
|
+
id: id,
|
25
|
+
name: name,
|
26
|
+
is_initial: is_initial || false,
|
27
|
+
is_final: is_final || false,
|
28
|
+
transitions_to: transitions_to,
|
29
|
+
on_error_transition_to: on_error_transition_to,
|
30
|
+
handlers: handlers
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def initialize_handlers(handler_args)
|
37
|
+
handler_args.each do |handler|
|
38
|
+
if handler.is_a? Hash
|
39
|
+
handlers.push(PhaseHandler.new handler)
|
40
|
+
else
|
41
|
+
handlers.push(PhaseHandler.new name: handler)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
module Distribot
|
3
|
+
class PhaseFinishedHandler
|
4
|
+
include Distribot::Handler
|
5
|
+
subscribe_to 'distribot.flow.phase.finished', handler: :callback
|
6
|
+
|
7
|
+
def callback(message)
|
8
|
+
flow = Distribot::Flow.find(message[:flow_id])
|
9
|
+
return unless flow.current_phase == message[:phase]
|
10
|
+
if flow.next_phase
|
11
|
+
flow.transition_to! flow.next_phase
|
12
|
+
else
|
13
|
+
Distribot.publish!(
|
14
|
+
'distribot.flow.finished', flow_id: flow.id
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
module Distribot
|
3
|
+
require 'semantic'
|
4
|
+
class PhaseStartedHandler
|
5
|
+
include Distribot::Handler
|
6
|
+
subscribe_to 'distribot.flow.phase.started', handler: :callback
|
7
|
+
|
8
|
+
def callback(message)
|
9
|
+
flow = Distribot::Flow.find(message[:flow_id])
|
10
|
+
phase = flow.phase(message[:phase])
|
11
|
+
if phase.handlers.empty?
|
12
|
+
Distribot.publish!(
|
13
|
+
'distribot.flow.phase.finished',
|
14
|
+
flow_id: flow.id,
|
15
|
+
phase: phase.name
|
16
|
+
)
|
17
|
+
else
|
18
|
+
handler_versions = phase.handlers.map do |handler|
|
19
|
+
version = best_version(handler)
|
20
|
+
unless version && !version.blank?
|
21
|
+
fail "Cannot find a good #{handler} version #{handler.version}"
|
22
|
+
end
|
23
|
+
{
|
24
|
+
handler.to_s => version
|
25
|
+
}
|
26
|
+
end.reduce({}, :merge)
|
27
|
+
phase.handlers.each do |handler|
|
28
|
+
init_handler(flow, phase, handler, handler_versions[handler.to_s])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# rubocop:disable Metrics/LineLength
|
34
|
+
def init_handler(flow, phase, handler, version)
|
35
|
+
Distribot.publish!(
|
36
|
+
"distribot.flow.handler.#{handler}.#{version}.enumerate",
|
37
|
+
flow_id: flow.id,
|
38
|
+
phase: phase.name,
|
39
|
+
task_queue: "distribot.flow.handler.#{handler}.#{version}.tasks",
|
40
|
+
task_counter: "distribot.flow.#{flow.id}.#{phase.name}.#{handler}.finished",
|
41
|
+
finished_queue: 'distribot.flow.task.finished'
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def best_version(handler)
|
46
|
+
if handler.version
|
47
|
+
wanted_version = Gem::Dependency.new(handler.to_s, handler.version)
|
48
|
+
# Figure out the highest acceptable version of the handler we can assign work to:
|
49
|
+
handler_versions(handler.to_s)
|
50
|
+
.reverse
|
51
|
+
.find { |x| wanted_version.match?(handler.to_s, x.to_s) }
|
52
|
+
.to_s
|
53
|
+
else
|
54
|
+
# Find the highest version for this queue:
|
55
|
+
handler_versions(handler.to_s).last
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def handler_versions(handler)
|
60
|
+
queue_prefix = "distribot.flow.handler.#{handler}."
|
61
|
+
Distribot.connector.queues
|
62
|
+
.select { |x| x.start_with? queue_prefix }
|
63
|
+
.reject { |x| x.end_with? '.enumerate' }
|
64
|
+
.map { |x| x.gsub(/^#{queue_prefix}/, '').gsub(/\.tasks$/, '') }
|
65
|
+
.map { |x| Semantic::Version.new x }
|
66
|
+
.sort
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
module Distribot
|
3
|
+
class TaskFinishedHandler
|
4
|
+
include Distribot::Handler
|
5
|
+
subscribe_to 'distribot.flow.task.finished', handler: :callback
|
6
|
+
|
7
|
+
def callback(message)
|
8
|
+
task_counter_key = task_counter(message)
|
9
|
+
current_value = Distribot.redis.get(task_counter_key) || return
|
10
|
+
return unless current_value.to_i == 0
|
11
|
+
Distribot.redis.del(task_counter_key)
|
12
|
+
announce_handler_has_finished(message)
|
13
|
+
end
|
14
|
+
|
15
|
+
def announce_handler_has_finished(message)
|
16
|
+
Distribot.publish!(
|
17
|
+
'distribot.flow.handler.finished',
|
18
|
+
flow_id: message[:flow_id],
|
19
|
+
phase: message[:phase],
|
20
|
+
handler: message[:handler],
|
21
|
+
task_queue: message[:task_queue]
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def task_counter(message)
|
26
|
+
# i.e. - distribot.flow.flowId.phaseName.handlerName.finished
|
27
|
+
[
|
28
|
+
'distribot',
|
29
|
+
'flow',
|
30
|
+
message[:flow_id],
|
31
|
+
message[:phase],
|
32
|
+
message[:handler].to_s,
|
33
|
+
'finished'
|
34
|
+
].join('.')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
|
2
|
+
module Distribot
|
3
|
+
class FlowCanceledError < StandardError; end
|
4
|
+
class FlowPausedError < StandardError; end
|
5
|
+
module Worker
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
class << self
|
12
|
+
attr_accessor :version, :enumerator, :process_tasks_with, :processor
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :processor, :enumerator
|
16
|
+
|
17
|
+
def enumerate_with(callback)
|
18
|
+
@enumerator = callback
|
19
|
+
end
|
20
|
+
|
21
|
+
def process_tasks_with(callback)
|
22
|
+
@processor = callback
|
23
|
+
end
|
24
|
+
|
25
|
+
# Does both setting/getting:
|
26
|
+
def version(val = nil)
|
27
|
+
@version ||= '0.0.0'
|
28
|
+
@version = val unless val.nil?
|
29
|
+
@version
|
30
|
+
end
|
31
|
+
|
32
|
+
def enumeration_queue
|
33
|
+
self.version ||= '0.0.0'
|
34
|
+
"distribot.flow.handler.#{self}.#{self.version}.enumerate"
|
35
|
+
end
|
36
|
+
|
37
|
+
def task_queue
|
38
|
+
self.version ||= '0.0.0'
|
39
|
+
"distribot.flow.handler.#{self}.#{self.version}.tasks"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_accessor :task_consumers
|
44
|
+
|
45
|
+
def run
|
46
|
+
prepare_for_enumeration
|
47
|
+
subscribe_to_task_queue
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def logger
|
52
|
+
Distribot.logger
|
53
|
+
end
|
54
|
+
|
55
|
+
def prepare_for_enumeration
|
56
|
+
logger.tagged("handler:#{self.class}") do
|
57
|
+
pool = Concurrent::FixedThreadPool.new(5)
|
58
|
+
Distribot.subscribe(self.class.enumeration_queue, solo: true) do |message|
|
59
|
+
pool.post do
|
60
|
+
logger.tagged(message.map { |k, v| [k, v].join(':') }) do
|
61
|
+
context = OpenStruct.new(message)
|
62
|
+
trycatch do
|
63
|
+
tasks = enumerate_tasks(message)
|
64
|
+
announce_tasks(context, message, tasks)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def subscribe_to_task_queue
|
73
|
+
logger.tagged("handler:#{self.class}") do
|
74
|
+
subscribe_args = { reenqueue_on_failure: true, solo: true }
|
75
|
+
pool = Concurrent::FixedThreadPool.new(500)
|
76
|
+
Distribot.subscribe(self.class.task_queue, subscribe_args) do |task|
|
77
|
+
pool.post do
|
78
|
+
logger_tags = task.map { |k, v| [k, v].join(':') }
|
79
|
+
logger.tagged(logger_tags) do
|
80
|
+
handle_task_execution(task)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def handle_task_execution(task)
|
88
|
+
context = OpenStruct.new(
|
89
|
+
flow_id: task[:flow_id],
|
90
|
+
phase: task[:phase],
|
91
|
+
finished_queue: 'distribot.flow.task.finished'
|
92
|
+
)
|
93
|
+
trycatch do
|
94
|
+
process_single_task(context, task)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def process_single_task(context, task)
|
99
|
+
inspect_task!(context)
|
100
|
+
# Your code is called right here:
|
101
|
+
send(self.class.processor, context, task)
|
102
|
+
task_counter_key = "distribot.flow.#{context.flow_id}.#{context.phase}.#{self.class}.finished"
|
103
|
+
Distribot.redis.decr(task_counter_key)
|
104
|
+
publish_args = {
|
105
|
+
flow_id: context.flow_id,
|
106
|
+
phase: context.phase,
|
107
|
+
handler: self.class.to_s
|
108
|
+
}
|
109
|
+
Distribot.publish!(context.finished_queue, publish_args)
|
110
|
+
end
|
111
|
+
|
112
|
+
def enumerate_tasks(message)
|
113
|
+
trycatch do
|
114
|
+
context = OpenStruct.new(message)
|
115
|
+
flow = Distribot::Flow.find(context.flow_id)
|
116
|
+
fail FlowCanceledError if flow.canceled?
|
117
|
+
send(self.class.enumerator, context)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def announce_tasks(context, message, tasks)
|
124
|
+
task_counter = message[:task_counter]
|
125
|
+
Distribot.redis.incrby task_counter, tasks.count
|
126
|
+
Distribot.redis.incrby "#{task_counter}.total", tasks.count
|
127
|
+
tasks.each do |task|
|
128
|
+
task.merge!(flow_id: context.flow_id, phase: context.phase)
|
129
|
+
Distribot.publish! message[:task_queue], task
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def inspect_task!(context)
|
134
|
+
flow = Distribot::Flow.find(context.flow_id)
|
135
|
+
fail FlowCanceledError if flow.canceled?
|
136
|
+
fail FlowPausedError if flow.paused?
|
137
|
+
end
|
138
|
+
|
139
|
+
def trycatch(&block)
|
140
|
+
# Put the try/catch logic here to reduce boilerplate code:
|
141
|
+
block.call
|
142
|
+
rescue StandardError => e
|
143
|
+
logger.error "ERROR: #{e} --- #{e.backtrace.join("\n")}"
|
144
|
+
warn "ERROR: #{e} --- #{e.backtrace.join("\n")}"
|
145
|
+
raise e
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|