ductwork 0.7.2 → 0.8.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 +4 -4
- data/CHANGELOG.md +15 -0
- data/LICENSE.txt +1 -1
- data/lib/ductwork/cli.rb +43 -31
- data/lib/ductwork/models/pipeline.rb +9 -8
- data/lib/ductwork/models/step.rb +2 -2
- data/lib/ductwork/processes/job_worker_runner.rb +3 -5
- data/lib/ductwork/processes/pipeline_advancer.rb +33 -18
- data/lib/ductwork/processes/pipeline_advancer_runner.rb +3 -8
- data/lib/ductwork/version.rb +1 -1
- data/lib/generators/ductwork/install/templates/db/create_ductwork_steps.rb +3 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6fde482283602dcf363baed46b5d8468757ddb8f6cc6fabb9b9d5e37497e10f4
|
|
4
|
+
data.tar.gz: 3aa0f1a9aa6f77b47997b64b246ed3dc7371e63554943110de8e06f98135f9d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bc475be1df97dada6099f8210b5694cbc4d90e5466d565a50674bac796fc252923f19a18e6beae3cf673db8d25fccf4bfb8178622d8574fd0e2b911e27dc8a0f
|
|
7
|
+
data.tar.gz: e1caa07d54fe23a2f2ea1b90809371a53bfd791eba6751bab9b65dac254c7f7935cf54f4b217adb68e084abd478192000090c91005d644188f64e2d7930264fd
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Ductwork Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.1]
|
|
4
|
+
|
|
5
|
+
- fix: properly wrap "units of work" in rails application executor in pipeline advancer
|
|
6
|
+
- fix: remove wrapping thread creation with the rails application executor - these threads are long-running so they should not be wrapped; later commits will wrap each individual "unit of work" with the executor as recommended
|
|
7
|
+
- fix: move pipeline advancer creation into thread initialization block - this effectively doesn't change anything but is useful in case we need to do something on the advancer thread in the initializer
|
|
8
|
+
- fix: move job worker creation into thread initialization block - this effectively doesn't change anything but is useful in case we need to do something on the worker thread in the initializer
|
|
9
|
+
|
|
10
|
+
## [0.8.0]
|
|
11
|
+
|
|
12
|
+
- chore: re-organize `Ductwork::CLI` class
|
|
13
|
+
- feat!: better name `ductwork_steps.step_type` to `to_transition` - the column rename is a breaking change but not bumping major version since the gem is still pre-1.0
|
|
14
|
+
- feat: add `delay_seconds` and `timeout_seconds` columns to `ductwork_steps` table
|
|
15
|
+
- feat: set pipeline status to `advancing` once pipeline is claimed and advancing starts
|
|
16
|
+
- chore: add `advancing` status enum value to `Pipeline`
|
|
17
|
+
|
|
3
18
|
## [0.7.2]
|
|
4
19
|
|
|
5
20
|
- chore: small DRY refactor in pipeline advancement
|
data/LICENSE.txt
CHANGED
|
@@ -4,7 +4,7 @@ the LGPLv3.0 license. Please see below for license text:
|
|
|
4
4
|
GNU LESSER GENERAL PUBLIC LICENSE
|
|
5
5
|
Version 3, 29 June 2007
|
|
6
6
|
|
|
7
|
-
Copyright (c) 2024-2025
|
|
7
|
+
Copyright (c) 2024-2025 Pen and Paper Solutions LLC
|
|
8
8
|
Everyone is permitted to copy and distribute verbatim copies
|
|
9
9
|
of this license document, but changing it is not allowed.
|
|
10
10
|
|
data/lib/ductwork/cli.rb
CHANGED
|
@@ -4,45 +4,57 @@ require "optparse"
|
|
|
4
4
|
|
|
5
5
|
module Ductwork
|
|
6
6
|
class CLI
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Ductwork.configuration = Configuration.new(**options)
|
|
11
|
-
Ductwork.logger = if Ductwork.configuration.logger_source == "rails"
|
|
12
|
-
Rails.logger
|
|
13
|
-
else
|
|
14
|
-
Ductwork::Configuration::DEFAULT_LOGGER
|
|
15
|
-
end
|
|
16
|
-
Ductwork.logger.level = Ductwork.configuration.logger_level
|
|
17
|
-
|
|
18
|
-
Ductwork::Processes::SupervisorRunner.start!
|
|
19
|
-
end
|
|
7
|
+
def self.start!(args)
|
|
8
|
+
new(args).start!
|
|
9
|
+
end
|
|
20
10
|
|
|
21
|
-
|
|
11
|
+
def initialize(args)
|
|
12
|
+
@args = args
|
|
13
|
+
@options = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def start!
|
|
17
|
+
option_parser.parse!(args)
|
|
18
|
+
auto_configure
|
|
19
|
+
supervisor_runner.start!
|
|
20
|
+
end
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
options = {}
|
|
22
|
+
private
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
op.banner = "ductwork [options]"
|
|
24
|
+
attr_reader :args, :options
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
def option_parser
|
|
27
|
+
OptionParser.new do |op|
|
|
28
|
+
op.banner = "ductwork [options]"
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
end
|
|
30
|
+
op.on("-c", "--config PATH", "path to YAML config file") do |arg|
|
|
31
|
+
options[:path] = arg
|
|
32
|
+
end
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
end.parse!(args)
|
|
34
|
+
op.on("-h", "--help", "Prints this help") do
|
|
35
|
+
puts op
|
|
36
|
+
exit
|
|
37
|
+
end
|
|
43
38
|
|
|
44
|
-
|
|
39
|
+
op.on("-v", "--version", "Prints the version") do
|
|
40
|
+
puts "Ductwork #{Ductwork::VERSION}"
|
|
41
|
+
exit
|
|
42
|
+
end
|
|
45
43
|
end
|
|
46
44
|
end
|
|
45
|
+
|
|
46
|
+
def auto_configure
|
|
47
|
+
Ductwork.configuration = Configuration.new(**options)
|
|
48
|
+
Ductwork.logger = if Ductwork.configuration.logger_source == "rails"
|
|
49
|
+
Rails.logger
|
|
50
|
+
else
|
|
51
|
+
Ductwork::Configuration::DEFAULT_LOGGER
|
|
52
|
+
end
|
|
53
|
+
Ductwork.logger.level = Ductwork.configuration.logger_level
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def supervisor_runner
|
|
57
|
+
Ductwork::Processes::SupervisorRunner
|
|
58
|
+
end
|
|
47
59
|
end
|
|
48
60
|
end
|
|
@@ -16,6 +16,7 @@ module Ductwork
|
|
|
16
16
|
pending: "pending",
|
|
17
17
|
in_progress: "in_progress",
|
|
18
18
|
waiting: "waiting",
|
|
19
|
+
advancing: "advancing",
|
|
19
20
|
halted: "halted",
|
|
20
21
|
completed: "completed"
|
|
21
22
|
|
|
@@ -71,7 +72,7 @@ module Ductwork
|
|
|
71
72
|
step = p.steps.create!(
|
|
72
73
|
klass: step_klass,
|
|
73
74
|
status: :in_progress,
|
|
74
|
-
|
|
75
|
+
to_transition: :start,
|
|
75
76
|
started_at: Time.current
|
|
76
77
|
)
|
|
77
78
|
Ductwork::Job.enqueue(step, args)
|
|
@@ -118,10 +119,10 @@ module Ductwork
|
|
|
118
119
|
started_at = Time.current
|
|
119
120
|
# NOTE: "chain" is used by ActiveRecord so we have to call
|
|
120
121
|
# this enum value "default" :sad:
|
|
121
|
-
|
|
122
|
+
to_transition = edge[:type] == "chain" ? "default" : edge[:type]
|
|
122
123
|
klass ||= edge.fetch(:to).sole
|
|
123
124
|
|
|
124
|
-
next_step = steps.create!(klass:, status:,
|
|
125
|
+
next_step = steps.create!(klass:, status:, to_transition:, started_at:)
|
|
125
126
|
Ductwork::Job.enqueue(next_step, input_arg)
|
|
126
127
|
end
|
|
127
128
|
|
|
@@ -174,17 +175,17 @@ module Ductwork
|
|
|
174
175
|
end
|
|
175
176
|
|
|
176
177
|
def advance_non_merging_steps(step_klass, edge, advancing_ids)
|
|
177
|
-
|
|
178
|
+
to_transition = edge[:type]
|
|
178
179
|
|
|
179
180
|
steps.where(id: advancing_ids, klass: step_klass).find_each do |step|
|
|
180
|
-
if
|
|
181
|
+
if to_transition.in?(%w[chain divide])
|
|
181
182
|
advance_to_next_steps(step.id, edge)
|
|
182
|
-
elsif
|
|
183
|
+
elsif to_transition == "expand"
|
|
183
184
|
expand_to_next_steps(step.id, edge)
|
|
184
185
|
else
|
|
185
186
|
Ductwork.logger.error(
|
|
186
|
-
msg: "Invalid
|
|
187
|
-
|
|
187
|
+
msg: "Invalid To Transition",
|
|
188
|
+
to_transition: to_transition,
|
|
188
189
|
pipeline_id: id,
|
|
189
190
|
role: :pipeline_advancer
|
|
190
191
|
)
|
data/lib/ductwork/models/step.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Ductwork
|
|
|
7
7
|
|
|
8
8
|
validates :klass, presence: true
|
|
9
9
|
validates :status, presence: true
|
|
10
|
-
validates :
|
|
10
|
+
validates :to_transition, presence: true
|
|
11
11
|
|
|
12
12
|
enum :status,
|
|
13
13
|
pending: "pending",
|
|
@@ -17,7 +17,7 @@ module Ductwork
|
|
|
17
17
|
failed: "failed",
|
|
18
18
|
completed: "completed"
|
|
19
19
|
|
|
20
|
-
enum :
|
|
20
|
+
enum :to_transition,
|
|
21
21
|
start: "start",
|
|
22
22
|
default: "default", # `chain` is used by AR
|
|
23
23
|
divide: "divide",
|
|
@@ -51,17 +51,15 @@ module Ductwork
|
|
|
51
51
|
|
|
52
52
|
def create_threads
|
|
53
53
|
worker_count.times.map do |i|
|
|
54
|
-
job_worker = Ductwork::Processes::JobWorker.new(
|
|
55
|
-
pipeline,
|
|
56
|
-
running_context
|
|
57
|
-
)
|
|
58
54
|
Ductwork.logger.debug(
|
|
59
55
|
msg: "Creating new thread",
|
|
60
56
|
role: :job_worker_runner,
|
|
61
57
|
pipeline: pipeline
|
|
62
58
|
)
|
|
63
59
|
thread = Thread.new do
|
|
64
|
-
|
|
60
|
+
Ductwork::Processes::JobWorker
|
|
61
|
+
.new(pipeline, running_context)
|
|
62
|
+
.run
|
|
65
63
|
end
|
|
66
64
|
thread.name = "ductwork.job_worker.#{i}"
|
|
67
65
|
|
|
@@ -10,8 +10,9 @@ module Ductwork
|
|
|
10
10
|
|
|
11
11
|
def run # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
|
|
12
12
|
run_hooks_for(:start)
|
|
13
|
+
|
|
13
14
|
while running_context.running?
|
|
14
|
-
id = Ductwork
|
|
15
|
+
id = Ductwork.wrap_with_app_executor do
|
|
15
16
|
Ductwork::Pipeline
|
|
16
17
|
.in_progress
|
|
17
18
|
.where(klass: klass, claimed_for_advancing_at: nil)
|
|
@@ -24,9 +25,14 @@ module Ductwork
|
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
if id.present?
|
|
27
|
-
rows_updated = Ductwork
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
rows_updated = Ductwork.wrap_with_app_executor do
|
|
29
|
+
Ductwork::Pipeline
|
|
30
|
+
.where(id: id, claimed_for_advancing_at: nil)
|
|
31
|
+
.update_all(
|
|
32
|
+
claimed_for_advancing_at: Time.current,
|
|
33
|
+
status: "advancing"
|
|
34
|
+
)
|
|
35
|
+
end
|
|
30
36
|
|
|
31
37
|
if rows_updated == 1
|
|
32
38
|
Ductwork.logger.debug(
|
|
@@ -36,22 +42,31 @@ module Ductwork
|
|
|
36
42
|
role: :pipeline_advancer
|
|
37
43
|
)
|
|
38
44
|
|
|
39
|
-
pipeline = Ductwork
|
|
40
|
-
|
|
45
|
+
pipeline = Ductwork.wrap_with_app_executor do
|
|
46
|
+
pipeline = Ductwork::Pipeline.find(id)
|
|
47
|
+
pipeline.advance!
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
Ductwork.logger.debug(
|
|
50
|
+
msg: "Pipeline advanced",
|
|
51
|
+
pipeline_id: id,
|
|
52
|
+
pipeline: klass,
|
|
53
|
+
role: :pipeline_advancer
|
|
54
|
+
)
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
# rubocop:todo Metrics/BlockNesting
|
|
57
|
+
status = pipeline.completed? ? "completed" : "in_progress"
|
|
58
|
+
# rubocop:enable Metrics/BlockNesting
|
|
59
|
+
|
|
60
|
+
# release the pipeline and set last advanced at so it doesn't
|
|
61
|
+
# block. we're not using a queue so we have to use a db
|
|
62
|
+
# timestamp
|
|
63
|
+
ensure
|
|
64
|
+
pipeline.update!(
|
|
65
|
+
claimed_for_advancing_at: nil,
|
|
66
|
+
last_advanced_at: Time.current,
|
|
67
|
+
status: status
|
|
68
|
+
)
|
|
69
|
+
end
|
|
55
70
|
else
|
|
56
71
|
Ductwork.logger.debug(
|
|
57
72
|
msg: "Did not claim pipeline, avoided race condition",
|
|
@@ -46,20 +46,15 @@ module Ductwork
|
|
|
46
46
|
|
|
47
47
|
def create_threads
|
|
48
48
|
klasses.map do |klass|
|
|
49
|
-
pipeline_advancer = Ductwork::Processes::PipelineAdvancer.new(
|
|
50
|
-
running_context,
|
|
51
|
-
klass
|
|
52
|
-
)
|
|
53
|
-
|
|
54
49
|
Ductwork.logger.debug(
|
|
55
50
|
msg: "Creating new thread",
|
|
56
51
|
role: :pipeline_advancer_runner,
|
|
57
52
|
pipeline: klass
|
|
58
53
|
)
|
|
59
54
|
thread = Thread.new do
|
|
60
|
-
Ductwork
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
Ductwork::Processes::PipelineAdvancer
|
|
56
|
+
.new(running_context, klass)
|
|
57
|
+
.run
|
|
63
58
|
end
|
|
64
59
|
thread.name = "ductwork.pipeline_advancer.#{klass}"
|
|
65
60
|
|
data/lib/ductwork/version.rb
CHANGED
|
@@ -5,10 +5,12 @@ class CreateDuctworkSteps < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>
|
|
|
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
7
|
table.string :klass, null: false
|
|
8
|
-
table.string :
|
|
8
|
+
table.string :to_transition, null: false
|
|
9
9
|
table.timestamp :started_at
|
|
10
10
|
table.timestamp :completed_at
|
|
11
11
|
table.string :status, null: false
|
|
12
|
+
table.integer :delay_seconds
|
|
13
|
+
table.integer :timeout_seconds
|
|
12
14
|
table.timestamps null: false
|
|
13
15
|
end
|
|
14
16
|
|