ductwork 0.8.1 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6fde482283602dcf363baed46b5d8468757ddb8f6cc6fabb9b9d5e37497e10f4
4
- data.tar.gz: 3aa0f1a9aa6f77b47997b64b246ed3dc7371e63554943110de8e06f98135f9d3
3
+ metadata.gz: 816c6bd3f6f983b777f6826e0bd9365be8b5fae5dc3512c2249d1f728208f5d2
4
+ data.tar.gz: 9deca63fadc27613ba93ef45d68ac1e8629b6f2b06bfb931842d59116f8d002c
5
5
  SHA512:
6
- metadata.gz: bc475be1df97dada6099f8210b5694cbc4d90e5466d565a50674bac796fc252923f19a18e6beae3cf673db8d25fccf4bfb8178622d8574fd0e2b911e27dc8a0f
7
- data.tar.gz: e1caa07d54fe23a2f2ea1b90809371a53bfd791eba6751bab9b65dac254c7f7935cf54f4b217adb68e084abd478192000090c91005d644188f64e2d7930264fd
6
+ metadata.gz: 746d2583354152bd51dc2acdd1958ec23234d27abd2b4ded448b69e82c164ae8f7c24ae1c91c536d9f8b60a72af826aa8dad0ff5c9b9301b34e6eeae14bf758c
7
+ data.tar.gz: a1114da01143e85ac0b58e5127f1c60cda5a625067e2c36389eee3ee61f30364aad7bf6dd4995ce86c38c05fe0a44a176a1bdd23f1943f5503ea9c6c80d3b0bb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Ductwork Changelog
2
2
 
3
+ ## [0.9.1]
4
+
5
+ - fix!: assign unique node names (klass name and stage) in pipeline definition DAG - this is a BREAKING CHANGE because the pipeline definition structure has changed
6
+
7
+ ## [0.9.0]
8
+
9
+ - feat: add health check to job worker runner process - this is a basic check if a thread is healthy via `Thread#alive?` and restarts the thread if it is dead
10
+
3
11
  ## [0.8.1]
4
12
 
5
13
  - fix: properly wrap "units of work" in rails application executor in pipeline advancer
@@ -7,36 +7,37 @@ module Ductwork
7
7
 
8
8
  attr_reader :last_node
9
9
 
10
- def initialize(klass:, definition:)
11
- @last_node = klass.name
10
+ def initialize(klass:, definition:, stages:)
11
+ @last_node = "#{klass.name}.#{stages.length - 1}"
12
12
  @definition = definition
13
+ @stages = stages
13
14
  @expansions = 0
14
15
  end
15
16
 
16
17
  def chain(next_klass)
17
- definition[:edges][last_node] << {
18
- to: [next_klass.name],
19
- type: :chain,
20
- }
21
-
22
- definition[:nodes].push(next_klass.name)
23
- definition[:edges][next_klass.name] ||= []
24
- @last_node = next_klass.name
18
+ next_klass_name = "#{next_klass.name}.#{stages.length}"
19
+ definition[:edges][last_node][:to] = [next_klass_name]
20
+ definition[:edges][last_node][:type] = :chain
21
+ definition[:nodes].push(next_klass_name)
22
+ definition[:edges][next_klass_name] ||= { klass: next_klass.name }
23
+ stages.push(1)
24
+ @last_node = next_klass_name
25
25
 
26
26
  self
27
27
  end
28
28
 
29
- def divide(to:)
30
- definition[:edges][last_node] << {
31
- to: to.map(&:name),
32
- type: :divide,
33
- }
29
+ def divide(to:) # rubocop:todo Metrics/AbcSize
30
+ next_klass_names = to.map { |klass| "#{klass.name}.#{stages.length}" }
31
+ definition[:edges][last_node][:to] = next_klass_names
32
+ definition[:edges][last_node][:type] = :divide
33
+ definition[:nodes].push(*next_klass_names)
34
+ stages.push(1)
34
35
 
35
- definition[:nodes].push(*to.map(&:name))
36
36
  sub_branches = to.map do |klass|
37
- definition[:edges][klass.name] ||= []
37
+ next_klass_name = "#{klass.name}.#{stages.length - 1}"
38
+ definition[:edges][next_klass_name] ||= { klass: klass.name }
38
39
 
39
- Ductwork::DSL::BranchBuilder.new(klass:, definition:)
40
+ Ductwork::DSL::BranchBuilder.new(klass:, definition:, stages:)
40
41
  end
41
42
 
42
43
  yield sub_branches
@@ -44,32 +45,30 @@ module Ductwork
44
45
  self
45
46
  end
46
47
 
47
- def combine(*branch_builders, into:)
48
- definition[:edges][last_node] << {
49
- to: [into.name],
50
- type: :combine,
51
- }
48
+ def combine(*branch_builders, into:) # rubocop:todo Metrics/AbcSize
49
+ next_klass_name = "#{into.name}.#{stages.length}"
50
+ definition[:edges][last_node][:to] = [next_klass_name]
51
+ definition[:edges][last_node][:type] = :combine
52
+
52
53
  branch_builders.each do |branch|
53
- definition[:edges][branch.last_node] << {
54
- to: [into.name],
55
- type: :combine,
56
- }
54
+ definition[:edges][branch.last_node][:to] = [next_klass_name]
55
+ definition[:edges][branch.last_node][:type] = :combine
57
56
  end
58
- definition[:nodes].push(into.name)
59
- definition[:edges][into.name] ||= []
57
+ definition[:nodes].push(next_klass_name)
58
+ definition[:edges][next_klass_name] ||= { klass: into.name }
59
+ stages.push(1)
60
60
 
61
61
  self
62
62
  end
63
63
 
64
64
  def expand(to:)
65
- definition[:edges][last_node] << {
66
- to: [to.name],
67
- type: :expand,
68
- }
69
-
70
- definition[:nodes].push(to.name)
71
- definition[:edges][to.name] ||= []
72
- @last_node = to.name
65
+ next_klass_name = "#{to.name}.#{stages.length}"
66
+ definition[:edges][last_node][:to] = [next_klass_name]
67
+ definition[:edges][last_node][:type] = :expand
68
+ definition[:nodes].push(next_klass_name)
69
+ definition[:edges][next_klass_name] ||= { klass: to.name }
70
+ stages.push(1)
71
+ @last_node = next_klass_name
73
72
  @expansions += 1
74
73
 
75
74
  self
@@ -81,13 +80,13 @@ module Ductwork
81
80
  "Must expand pipeline definition before collapsing steps"
82
81
  end
83
82
 
84
- definition[:edges][last_node] << {
85
- to: [into.name],
86
- type: :collapse,
87
- }
83
+ next_klass_name = "#{into.name}.#{stages.length}"
84
+ definition[:edges][last_node][:to] = [next_klass_name]
85
+ definition[:edges][last_node][:type] = :collapse
88
86
 
89
- definition[:nodes].push(into.name)
90
- definition[:edges][into.name] ||= []
87
+ definition[:nodes].push(next_klass_name)
88
+ definition[:edges][next_klass_name] ||= { klass: into.name }
89
+ stages.push(1)
91
90
  @last_node = into.name
92
91
  @expansions -= 1
93
92
 
@@ -96,7 +95,7 @@ module Ductwork
96
95
 
97
96
  private
98
97
 
99
- attr_reader :definition, :expansions
98
+ attr_reader :definition, :expansions, :stages
100
99
  end
101
100
  end
102
101
  end
@@ -15,23 +15,24 @@ module Ductwork
15
15
  }
16
16
  @divergences = []
17
17
  @last_nodes = []
18
+ @stages = []
18
19
  end
19
20
 
20
21
  def start(klass)
21
22
  validate_classes!(klass)
22
23
  validate_start_once!
23
24
  add_new_nodes(klass)
25
+ increment_position
24
26
 
25
27
  self
26
28
  end
27
29
 
28
- # NOTE: there is a bug here that does not allow the user to reuse step
29
- # classes in the same pipeline. i'll fix this later
30
30
  def chain(klass)
31
31
  validate_classes!(klass)
32
32
  validate_definition_started!(action: "chaining")
33
33
  add_edge_to_last_nodes(klass, type: :chain)
34
34
  add_new_nodes(klass)
35
+ increment_position
35
36
 
36
37
  self
37
38
  end
@@ -41,13 +42,13 @@ module Ductwork
41
42
  validate_definition_started!(action: "dividing chain")
42
43
  add_edge_to_last_nodes(*to, type: :divide)
43
44
  add_new_nodes(*to)
44
-
45
+ increment_position
45
46
  divergences.push(:divide)
46
47
 
47
48
  if block_given?
48
49
  branches = to.map do |klass|
49
50
  Ductwork::DSL::BranchBuilder
50
- .new(klass:, definition:)
51
+ .new(klass:, definition:, stages:)
51
52
  end
52
53
 
53
54
  yield branches
@@ -64,15 +65,14 @@ module Ductwork
64
65
  divergences.pop
65
66
 
66
67
  last_nodes = definition[:nodes].reverse.select do |node|
67
- definition[:edges][node].empty?
68
+ definition.dig(:edges, node, :to).blank?
68
69
  end
69
70
  last_nodes.each do |node|
70
- definition[:edges][node] << {
71
- to: [into.name],
72
- type: :combine,
73
- }
71
+ definition[:edges][node][:to] = ["#{into.name}.#{stages.length}"]
72
+ definition[:edges][node][:type] = :combine
74
73
  end
75
74
  add_new_nodes(into)
75
+ increment_position
76
76
 
77
77
  self
78
78
  end
@@ -82,7 +82,7 @@ module Ductwork
82
82
  validate_definition_started!(action: "expanding chain")
83
83
  add_edge_to_last_nodes(to, type: :expand)
84
84
  add_new_nodes(to)
85
-
85
+ increment_position
86
86
  divergences.push(:expand)
87
87
 
88
88
  self
@@ -94,7 +94,7 @@ module Ductwork
94
94
  validate_can_collapse!
95
95
  add_edge_to_last_nodes(into, type: :collapse)
96
96
  add_new_nodes(into)
97
-
97
+ increment_position
98
98
  divergences.pop
99
99
 
100
100
  self
@@ -116,7 +116,7 @@ module Ductwork
116
116
 
117
117
  private
118
118
 
119
- attr_reader :definition, :last_nodes, :divergences
119
+ attr_reader :definition, :last_nodes, :divergences, :stages
120
120
 
121
121
  def validate_classes!(klasses)
122
122
  valid = Array(klasses).all? do |klass|
@@ -165,23 +165,28 @@ module Ductwork
165
165
  end
166
166
 
167
167
  def add_new_nodes(*klasses)
168
- nodes = klasses.map(&:name)
168
+ nodes = klasses.map { |klass| "#{klass.name}.#{stages.length}" }
169
169
  @last_nodes = Array(nodes)
170
170
 
171
171
  definition[:nodes].push(*nodes)
172
172
  klasses.each do |klass|
173
- definition[:edges][klass.name] ||= []
173
+ node = "#{klass.name}.#{stages.length}"
174
+ definition[:edges][node] ||= { klass: klass.name }
174
175
  end
175
176
  end
176
177
 
177
178
  def add_edge_to_last_nodes(*klasses, type:)
178
179
  last_nodes.each do |last_node|
179
- definition[:edges][last_node] << {
180
- to: klasses.map(&:name),
181
- type: type,
182
- }
180
+ to = klasses.map { |klass| "#{klass.name}.#{stages.length}" }
181
+ definition[:edges][last_node][:to] = to
182
+ definition[:edges][last_node][:type] = type
183
+ definition[:edges][last_node][:klass] ||= klass
183
184
  end
184
185
  end
186
+
187
+ def increment_position
188
+ stages.push(1)
189
+ end
185
190
  end
186
191
  end
187
192
  end
@@ -56,7 +56,8 @@ module Ductwork
56
56
  raise DefinitionError, "Pipeline must be defined before triggering"
57
57
  end
58
58
 
59
- step_klass = pipeline_definition.dig(:nodes, 0)
59
+ node = pipeline_definition.dig(:nodes, 0)
60
+ klass = pipeline_definition.dig(:edges, node, :klass)
60
61
  definition = JSON.dump(pipeline_definition)
61
62
 
62
63
  pipeline = Record.transaction do
@@ -70,7 +71,8 @@ module Ductwork
70
71
  last_advanced_at: Time.current
71
72
  )
72
73
  step = p.steps.create!(
73
- klass: step_klass,
74
+ node: node,
75
+ klass: klass,
74
76
  status: :in_progress,
75
77
  to_transition: :start,
76
78
  started_at: Time.current
@@ -95,12 +97,12 @@ module Ductwork
95
97
  # advancing records which may cause memory issues. something to
96
98
  # watch out for here and maybe add in config to use AR relation
97
99
  # at certain counts or even memory limits.
98
- advancing_steps = steps.advancing.pluck(:id, :klass)
100
+ advancing_steps = steps.advancing.pluck(:id, :node, :klass)
99
101
  advancing_ids = advancing_steps.map(&:first)
100
102
  edges = find_edges(advancing_steps)
101
103
 
102
104
  Ductwork::Record.transaction do
103
- if edges.nil? || edges.values.all?(&:empty?)
105
+ if edges.nil? || edges.all? { |_, attrs| attrs[:to].blank? }
104
106
  conditionally_complete_pipeline(advancing_ids)
105
107
  else
106
108
  advance_to_next_steps_by_type(edges, advancing_ids)
@@ -114,23 +116,24 @@ module Ductwork
114
116
 
115
117
  private
116
118
 
117
- def create_step_and_enqueue_job(edge:, input_arg:, klass: nil)
119
+ def create_step_and_enqueue_job(edge:, input_arg:, node: nil)
118
120
  status = :in_progress
119
121
  started_at = Time.current
120
122
  # NOTE: "chain" is used by ActiveRecord so we have to call
121
123
  # this enum value "default" :sad:
122
124
  to_transition = edge[:type] == "chain" ? "default" : edge[:type]
123
- klass ||= edge.fetch(:to).sole
125
+ node ||= edge[:to].sole
126
+ klass = parsed_definition.dig(:edges, node, :klass)
124
127
 
125
- next_step = steps.create!(klass:, status:, to_transition:, started_at:)
128
+ next_step = steps.create!(node:, klass:, status:, to_transition:, started_at:)
126
129
  Ductwork::Job.enqueue(next_step, input_arg)
127
130
  end
128
131
 
129
132
  def find_edges(advancing_steps)
130
133
  if advancing_steps.any?
131
- klasses = advancing_steps.map(&:last)
134
+ nodes = advancing_steps.map(&:second)
132
135
 
133
- parsed_definition.fetch(:edges, {}).select { |k| k.in?(klasses) }
136
+ parsed_definition.fetch(:edges, {}).select { |k| k.in?(nodes) }
134
137
  end
135
138
  end
136
139
 
@@ -158,26 +161,25 @@ module Ductwork
158
161
  def advance_to_next_steps_by_type(edges, advancing_ids)
159
162
  steps.where(id: advancing_ids).update_all(status: :completed, completed_at: Time.current)
160
163
 
161
- if edges.all? { |_, v| v.dig(-1, :type) == "combine" }
164
+ if edges.all? { |_, attrs| attrs[:type] == "combine" }
162
165
  conditionally_combine_next_steps(edges, advancing_ids)
163
166
  else
164
- edges.each do |step_klass, step_edges|
165
- edge = step_edges[-1]
166
-
167
- if edge[:type] == "collapse"
168
- conditionally_collapse_next_steps(step_klass, edge, advancing_ids)
167
+ edges.each do |node, attrs|
168
+ if attrs[:type] == "collapse"
169
+ conditionally_collapse_next_steps(node, attrs, advancing_ids)
169
170
  else
170
- advance_non_merging_steps(step_klass, edge, advancing_ids)
171
+ advance_non_merging_steps(attrs, advancing_ids)
171
172
  end
172
173
  end
173
174
  end
174
175
  log_pipeline_advanced(edges)
175
176
  end
176
177
 
177
- def advance_non_merging_steps(step_klass, edge, advancing_ids)
178
+ def advance_non_merging_steps(edge, advancing_ids)
178
179
  to_transition = edge[:type]
180
+ klass = edge[:klass]
179
181
 
180
- steps.where(id: advancing_ids, klass: step_klass).find_each do |step|
182
+ steps.where(id: advancing_ids, klass: klass).find_each do |step|
181
183
  if to_transition.in?(%w[chain divide])
182
184
  advance_to_next_steps(step.id, edge)
183
185
  elsif to_transition == "expand"
@@ -205,15 +207,15 @@ module Ductwork
205
207
  if too_many
206
208
  halted!
207
209
  else
208
- edge[:to].each do |klass|
210
+ edge[:to].each do |node|
209
211
  input_arg = Ductwork::Job.find_by(step_id:).return_value
210
- create_step_and_enqueue_job(edge:, input_arg:, klass:)
212
+ create_step_and_enqueue_job(edge:, input_arg:, node:)
211
213
  end
212
214
  end
213
215
  end
214
216
 
215
217
  def conditionally_combine_next_steps(edges, advancing_ids)
216
- if steps.where(status: %w[pending in_progress], klass: edges.keys).none?
218
+ if steps.where(status: %w[pending in_progress], node: edges.keys).none?
217
219
  combine_next_steps(edges, advancing_ids)
218
220
  else
219
221
  Ductwork.logger.debug(
@@ -225,13 +227,13 @@ module Ductwork
225
227
  end
226
228
 
227
229
  def combine_next_steps(edges, advancing_ids)
228
- edge = edges.values.sample[-1]
230
+ edge = edges.values.sample
229
231
  groups = steps
230
232
  .where(id: advancing_ids)
231
- .group(:klass)
233
+ .group(:node)
232
234
  .count
233
235
  .keys
234
- .map { |k| steps.where(id: advancing_ids).where(klass: k) }
236
+ .map { |node| steps.where(id: advancing_ids).where(node:) }
235
237
 
236
238
  groups.first.zip(*groups[1..]).each do |group|
237
239
  input_arg = Ductwork::Job
@@ -257,8 +259,8 @@ module Ductwork
257
259
  end
258
260
  end
259
261
 
260
- def conditionally_collapse_next_steps(step_klass, edge, advancing_ids)
261
- if steps.where(status: %w[pending in_progress], klass: step_klass).none?
262
+ def conditionally_collapse_next_steps(node, edge, advancing_ids)
263
+ if steps.where(status: %w[pending in_progress], node: node).none?
262
264
  collapse_next_steps(edge, advancing_ids)
263
265
  else
264
266
  Ductwork.logger.debug(
@@ -5,6 +5,7 @@ module Ductwork
5
5
  belongs_to :pipeline, class_name: "Ductwork::Pipeline"
6
6
  has_one :job, class_name: "Ductwork::Job", foreign_key: "step_id", dependent: :destroy
7
7
 
8
+ validates :node, presence: true
8
9
  validates :klass, presence: true
9
10
  validates :status, presence: true
10
11
  validates :to_transition, presence: true
@@ -3,18 +3,44 @@
3
3
  module Ductwork
4
4
  module Processes
5
5
  class JobWorker
6
- def initialize(pipeline, running_context)
6
+ attr_reader :thread, :last_hearthbeat_at
7
+
8
+ def initialize(pipeline, id)
7
9
  @pipeline = pipeline
8
- @running_context = running_context
10
+ @id = id
11
+ @running_context = Ductwork::RunningContext.new
12
+ @thread = nil
13
+ @last_hearthbeat_at = Time.current
14
+ end
15
+
16
+ def start
17
+ @thread = Thread.new { work_loop }
18
+ @thread.name = "ductwork.job_worker.#{id}"
19
+ end
20
+
21
+ alias restart start
22
+
23
+ def alive?
24
+ thread&.alive? || false
25
+ end
26
+
27
+ def stop
28
+ running_context.shutdown!
9
29
  end
10
30
 
11
- def run
31
+ private
32
+
33
+ attr_reader :pipeline, :id, :running_context
34
+
35
+ def work_loop
12
36
  run_hooks_for(:start)
37
+
13
38
  Ductwork.logger.debug(
14
39
  msg: "Entering main work loop",
15
40
  role: :job_worker,
16
41
  pipeline: pipeline
17
42
  )
43
+
18
44
  while running_context.running?
19
45
  Ductwork.logger.debug(
20
46
  msg: "Attempting to claim job",
@@ -37,21 +63,16 @@ module Ductwork
37
63
  )
38
64
  sleep(polling_timeout)
39
65
  end
40
- end
41
-
42
- shutdown
43
- end
44
-
45
- private
46
66
 
47
- attr_reader :pipeline, :running_context
67
+ @last_hearthbeat_at = Time.current
68
+ end
48
69
 
49
- def shutdown
50
70
  Ductwork.logger.debug(
51
71
  msg: "Shutting down",
52
72
  role: :job_worker,
53
73
  pipeline: pipeline
54
74
  )
75
+
55
76
  run_hooks_for(:stop)
56
77
  end
57
78
 
@@ -6,7 +6,7 @@ module Ductwork
6
6
  def initialize(pipeline)
7
7
  @pipeline = pipeline
8
8
  @running_context = Ductwork::RunningContext.new
9
- @threads = create_threads
9
+ @job_workers = []
10
10
 
11
11
  Signal.trap(:INT) { running_context.shutdown! }
12
12
  Signal.trap(:TERM) { running_context.shutdown! }
@@ -24,7 +24,9 @@ module Ductwork
24
24
  end
25
25
 
26
26
  def run
27
- create_process!
27
+ create_process_record!
28
+ start_job_workers
29
+
28
30
  Ductwork.logger.debug(
29
31
  msg: "Entering main work loop",
30
32
  role: :job_worker_runner,
@@ -34,7 +36,7 @@ module Ductwork
34
36
  while running?
35
37
  # TODO: Increase or make configurable
36
38
  sleep(5)
37
- attempt_synchronize_threads
39
+ check_thread_health
38
40
  report_heartbeat!
39
41
  end
40
42
 
@@ -43,43 +45,29 @@ module Ductwork
43
45
 
44
46
  private
45
47
 
46
- attr_reader :pipeline, :running_context, :threads
48
+ attr_reader :pipeline, :running_context, :job_workers
47
49
 
48
- def worker_count
49
- Ductwork.configuration.job_worker_count(pipeline)
50
+ def create_process_record!
51
+ Ductwork.wrap_with_app_executor do
52
+ Ductwork::Process.create!(
53
+ pid: ::Process.pid,
54
+ machine_identifier: Ductwork::MachineIdentifier.fetch,
55
+ last_heartbeat_at: Time.current
56
+ )
57
+ end
50
58
  end
51
59
 
52
- def create_threads
53
- worker_count.times.map do |i|
54
- Ductwork.logger.debug(
55
- msg: "Creating new thread",
56
- role: :job_worker_runner,
57
- pipeline: pipeline
58
- )
59
- thread = Thread.new do
60
- Ductwork::Processes::JobWorker
61
- .new(pipeline, running_context)
62
- .run
63
- end
64
- thread.name = "ductwork.job_worker.#{i}"
60
+ def start_job_workers
61
+ Ductwork.configuration.job_worker_count(pipeline).times do |i|
62
+ job_worker = Ductwork::Processes::JobWorker.new(pipeline, i)
63
+ job_workers.push(job_worker)
64
+ job_worker.start
65
65
 
66
66
  Ductwork.logger.debug(
67
- msg: "Created new thread",
67
+ msg: "Created new job worker",
68
68
  role: :job_worker_runner,
69
69
  pipeline: pipeline
70
70
  )
71
-
72
- thread
73
- end
74
- end
75
-
76
- def create_process!
77
- Ductwork.wrap_with_app_executor do
78
- Ductwork::Process.create!(
79
- pid: ::Process.pid,
80
- machine_identifier: Ductwork::MachineIdentifier.fetch,
81
- last_heartbeat_at: Time.current
82
- )
83
71
  end
84
72
  end
85
73
 
@@ -87,13 +75,17 @@ module Ductwork
87
75
  running_context.running?
88
76
  end
89
77
 
90
- def attempt_synchronize_threads
78
+ def check_thread_health
91
79
  Ductwork.logger.debug(
92
80
  msg: "Attempting to synchronize threads",
93
81
  role: :job_worker_runner,
94
82
  pipeline: pipeline
95
83
  )
96
- threads.each { |thread| thread.join(0.1) }
84
+ job_workers.each do |job_worker|
85
+ if !job_worker.alive?
86
+ job_worker.restart
87
+ end
88
+ end
97
89
  Ductwork.logger.debug(
98
90
  msg: "Synchronizing threads timed out",
99
91
  role: :job_worker_runner,
@@ -111,31 +103,36 @@ module Ductwork
111
103
 
112
104
  def shutdown!
113
105
  running_context.shutdown!
106
+ job_workers.each(&:stop)
114
107
  await_threads_graceful_shutdown
115
- kill_remaining_threads
116
- delete_process
108
+ kill_remaining_job_workers
109
+ delete_process_record!
117
110
  end
118
111
 
119
112
  def await_threads_graceful_shutdown
120
113
  timeout = Ductwork.configuration.job_worker_shutdown_timeout
121
114
  deadline = Time.current + timeout
122
115
 
123
- Ductwork.logger.debug(msg: "Attempting graceful shutdown", role: :job_worker_runner)
124
- while Time.current < deadline && threads.any?(&:alive?)
125
- threads.each do |thread|
116
+ Ductwork.logger.debug(
117
+ msg: "Attempting graceful shutdown",
118
+ role: :job_worker_runner
119
+ )
120
+
121
+ while Time.current < deadline && job_workers.any?(&:alive?)
122
+ job_workers.each do |job_worker|
126
123
  break if Time.current < deadline
127
124
 
128
125
  # TODO: Maybe make this configurable. If there's a ton of workers
129
126
  # it may not even get to the "later" ones depending on the timeout
130
- thread.join(1)
127
+ job_worker.thread.join(1)
131
128
  end
132
129
  end
133
130
  end
134
131
 
135
- def kill_remaining_threads
136
- threads.each do |thread|
137
- if thread.alive?
138
- thread.kill
132
+ def kill_remaining_job_workers
133
+ job_workers.each do |job_worker|
134
+ if job_worker.alive?
135
+ job_worker.thread.kill
139
136
  Ductwork.logger.debug(
140
137
  msg: "Killed thread",
141
138
  role: :job_worker_runner,
@@ -145,7 +142,7 @@ module Ductwork
145
142
  end
146
143
  end
147
144
 
148
- def delete_process
145
+ def delete_process_record!
149
146
  Ductwork.wrap_with_app_executor do
150
147
  Ductwork::Process.find_by!(
151
148
  pid: ::Process.pid,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ductwork
4
- VERSION = "0.8.1"
4
+ VERSION = "0.9.1"
5
5
  end
@@ -4,6 +4,7 @@ class CreateDuctworkSteps < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>
4
4
  def change
5
5
  create_table :ductwork_steps do |table|
6
6
  table.belongs_to :pipeline, index: true, null: false, foreign_key: { to_table: :ductwork_pipelines }
7
+ table.string :node, null: false
7
8
  table.string :klass, null: false
8
9
  table.string :to_transition, null: false
9
10
  table.timestamp :started_at
@@ -14,9 +15,9 @@ class CreateDuctworkSteps < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>
14
15
  table.timestamps null: false
15
16
  end
16
17
 
17
- add_index :ductwork_steps, %i[pipeline_id status klass]
18
- add_index :ductwork_steps, %i[pipeline_id klass status]
19
- add_index :ductwork_steps, %i[status klass]
18
+ add_index :ductwork_steps, %i[pipeline_id status node]
19
+ add_index :ductwork_steps, %i[pipeline_id node status]
20
+ add_index :ductwork_steps, %i[status node]
20
21
  add_index :ductwork_steps, %i[pipeline_id status]
21
22
  end
22
23
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ductwork
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Ewing