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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3a59fb73965f325d3e23243f59e7e973419f3683d2de741cf5bbf115bccad8d
4
- data.tar.gz: d66a5c291d197b7a8e679d23e9c68b6575d2e35100b06f7c412c97e5bc515ec3
3
+ metadata.gz: 6fde482283602dcf363baed46b5d8468757ddb8f6cc6fabb9b9d5e37497e10f4
4
+ data.tar.gz: 3aa0f1a9aa6f77b47997b64b246ed3dc7371e63554943110de8e06f98135f9d3
5
5
  SHA512:
6
- metadata.gz: de5ca4b1c9dff4478992234183ae84d810cec930eede8d97dc7511959efad9521acb201ec5f39a2d96d430383ee3f240d3a3395bc55fc19ce272603000eb92a2
7
- data.tar.gz: b86ea8a91f912ae55e509f2c059fc8db8d4ccff0055fe96db77593a98ce188255b2bbcb395709a0b672ded6b8df1a1537001fc56cfb3de7244a5dda9a4e48192
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 Tyler Ewing
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
- class << self
8
- def start!(args)
9
- options = parse_options(args)
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
- private
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
- def parse_options(args)
24
- options = {}
22
+ private
25
23
 
26
- OptionParser.new do |op|
27
- op.banner = "ductwork [options]"
24
+ attr_reader :args, :options
28
25
 
29
- op.on("-c", "--config PATH", "path to YAML config file") do |arg|
30
- options[:path] = arg
31
- end
26
+ def option_parser
27
+ OptionParser.new do |op|
28
+ op.banner = "ductwork [options]"
32
29
 
33
- op.on("-h", "--help", "Prints this help") do
34
- puts op
35
- exit
36
- end
30
+ op.on("-c", "--config PATH", "path to YAML config file") do |arg|
31
+ options[:path] = arg
32
+ end
37
33
 
38
- op.on("-v", "--version", "Prints the version") do
39
- puts "Ductwork #{Ductwork::VERSION}"
40
- exit
41
- end
42
- end.parse!(args)
34
+ op.on("-h", "--help", "Prints this help") do
35
+ puts op
36
+ exit
37
+ end
43
38
 
44
- options
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
- step_type: :start,
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
- step_type = edge[:type] == "chain" ? "default" : edge[:type]
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:, step_type:, started_at:)
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
- step_type = edge[:type]
178
+ to_transition = edge[:type]
178
179
 
179
180
  steps.where(id: advancing_ids, klass: step_klass).find_each do |step|
180
- if step_type.in?(%w[chain divide])
181
+ if to_transition.in?(%w[chain divide])
181
182
  advance_to_next_steps(step.id, edge)
182
- elsif step_type == "expand"
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 step type",
187
- step_type: step_type,
187
+ msg: "Invalid To Transition",
188
+ to_transition: to_transition,
188
189
  pipeline_id: id,
189
190
  role: :pipeline_advancer
190
191
  )
@@ -7,7 +7,7 @@ module Ductwork
7
7
 
8
8
  validates :klass, presence: true
9
9
  validates :status, presence: true
10
- validates :step_type, presence: true
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 :step_type,
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
- job_worker.run
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::Record.uncached do
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::Pipeline
28
- .where(id: id, claimed_for_advancing_at: nil)
29
- .update_all(claimed_for_advancing_at: Time.current)
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::Pipeline.find(id)
40
- pipeline.advance!
45
+ pipeline = Ductwork.wrap_with_app_executor do
46
+ pipeline = Ductwork::Pipeline.find(id)
47
+ pipeline.advance!
41
48
 
42
- Ductwork.logger.debug(
43
- msg: "Pipeline advanced",
44
- pipeline_id: id,
45
- pipeline: klass,
46
- role: :pipeline_advancer
47
- )
49
+ Ductwork.logger.debug(
50
+ msg: "Pipeline advanced",
51
+ pipeline_id: id,
52
+ pipeline: klass,
53
+ role: :pipeline_advancer
54
+ )
48
55
 
49
- # release the pipeline and set last advanced at so it doesnt block.
50
- # we're not using a queue so we have to use a db timestamp
51
- pipeline.update!(
52
- claimed_for_advancing_at: nil,
53
- last_advanced_at: Time.current
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.wrap_with_app_executor do
61
- pipeline_advancer.run
62
- end
55
+ Ductwork::Processes::PipelineAdvancer
56
+ .new(running_context, klass)
57
+ .run
63
58
  end
64
59
  thread.name = "ductwork.pipeline_advancer.#{klass}"
65
60
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ductwork
4
- VERSION = "0.7.2"
4
+ VERSION = "0.8.1"
5
5
  end
@@ -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 :step_type, null: false
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
 
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.7.2
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Ewing