ductwork 0.2.1 → 0.3.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: 8ab7c6e79c26352d990680a3913a26ea8bd4bc9999136b4ee1a1846c06e74523
4
- data.tar.gz: 7adb0603119c80d5edc54e94de44828bba2c927edad418e5944d594ba21f734a
3
+ metadata.gz: ab38ab3c5ce61b7fd6d48eac93d93bcaa33b3476a7b8b6e126403da7b4c2ea04
4
+ data.tar.gz: 7f3c09913cf33f5f2d6b42f2f3b786b09b35d343d1277262f77b0f9ff666b653
5
5
  SHA512:
6
- metadata.gz: 83be2952cf26ebbee28521df07b68ee29e87ade164a70a87ede7c7bcfc1ef5525097b1346757d76e34d4fc25535c5489f56900fa763006d923bd8491dcba520f
7
- data.tar.gz: 53cabb6cdfa4f96f847ddc68818a3f0a87d59ca443282700eca5f690a8a01805e46dbd925990565630bb5c45dc728183de251629488d055f8062d136f06dff7e
6
+ metadata.gz: cd1bbacdd428ed77064e526a7a2bfe639aad474c19d002417b5d3893856cef916baaba805124e1f4042c90ce972328f3de8c9747c88a96d8b3a60d88a26b183a
7
+ data.tar.gz: cef8abdee38bea2530ba058fde0062699fc53f190e3e67deb8c369f7c1badae7a7e9538f11ce6fc8f1bce488e15a9ab97271bf6fbc0c59f38f8eea1187f376e8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Ductwork Changelog
2
2
 
3
+ ## [0.3.1]
4
+
5
+ - chore: bump dependencies and update necessary files
6
+ - chore: update email address in gemspec
7
+ - chore: move `logger` out of config to top-level `Ductwork` module
8
+ - chore: promote `Ductwork::Pipeline#parsed_definition` to a public method
9
+ - fix: raise when trying to collapse a most recently divided pipeline and vice versa
10
+
11
+ ## [0.3.0]
12
+
13
+ - fix: correctly create collapsing and combining steps and jobs for complex pipelines
14
+ - fix: add a new step and job for each active branch in a running pipeline
15
+ - fix: add a new node and edge for each active branch of the definition
16
+ - feat: add info-level logging for job events
17
+ - feat: add info-level logging for pipeline events
18
+
3
19
  ## [0.2.1]
4
20
 
5
21
  - fix: do not splat arguments when executing a job nor triggering a pipeline
data/README.md CHANGED
@@ -103,7 +103,7 @@ end
103
103
 
104
104
  **Important:** Return values must be JSON-serializable.
105
105
 
106
- See [Defining Pipelines](https://docs.getductwork.io/getting_started/defining-pipelines.html) for detailed documentation.
106
+ See [Defining Pipelines](https://docs.getductwork.io/getting-started/defining-pipelines.html) for detailed documentation.
107
107
 
108
108
  ### 4. Run Ductwork
109
109
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ductwork
4
- class Job < Ductwork::Record
4
+ class Job < Ductwork::Record # rubocop:todo Metrics/ClassLength
5
5
  belongs_to :step, class_name: "Ductwork::Step"
6
6
  has_many :executions, class_name: "Ductwork::Execution", foreign_key: "job_id", dependent: :destroy
7
7
 
@@ -39,7 +39,7 @@ module Ductwork
39
39
  end
40
40
 
41
41
  if rows_updated == 1
42
- Ductwork.configuration.logger.debug(
42
+ Ductwork.logger.debug(
43
43
  msg: "Job claimed",
44
44
  role: :job_worker,
45
45
  process_id: process_id,
@@ -49,7 +49,7 @@ module Ductwork
49
49
  .joins(executions: :availability)
50
50
  .find_by(ductwork_availabilities: { id:, process_id: })
51
51
  else
52
- Ductwork.configuration.logger.debug(
52
+ Ductwork.logger.debug(
53
53
  msg: "Did not claim job, avoided race condition",
54
54
  role: :job_worker,
55
55
  process_id: process_id,
@@ -61,13 +61,13 @@ module Ductwork
61
61
  end
62
62
 
63
63
  def self.enqueue(step, args)
64
- Ductwork::Record.transaction do
65
- job = step.create_job!(
64
+ job = Ductwork::Record.transaction do
65
+ j = step.create_job!(
66
66
  klass: step.klass,
67
67
  started_at: Time.current,
68
68
  input_args: JSON.dump({ args: })
69
69
  )
70
- execution = job.executions.create!(
70
+ execution = j.executions.create!(
71
71
  started_at: Time.current,
72
72
  retry_count: 0
73
73
  )
@@ -75,14 +75,22 @@ module Ductwork
75
75
  started_at: Time.current
76
76
  )
77
77
 
78
- job
78
+ j
79
79
  end
80
+
81
+ Ductwork.logger.info(
82
+ msg: "Job enqueued",
83
+ job_id: job.id,
84
+ job_klass: job.klass
85
+ )
86
+
87
+ job
80
88
  end
81
89
 
82
90
  def execute(pipeline)
83
91
  # i don't _really_ like this, but it should be fine for now...
84
92
  execution = executions.order(:created_at).last
85
- logger.debug(
93
+ Ductwork.logger.debug(
86
94
  msg: "Executing job",
87
95
  role: :job_worker,
88
96
  pipeline: pipeline,
@@ -103,12 +111,13 @@ module Ductwork
103
111
  execution_failed!(execution, run, e)
104
112
  result = "failure"
105
113
  ensure
106
- logger.debug(
107
- msg: "Executed job",
108
- role: :job_worker,
114
+ Ductwork.logger.info(
115
+ msg: "Job executed",
109
116
  pipeline: pipeline,
117
+ job_id: id,
110
118
  job_klass: klass,
111
- result: result
119
+ result: result,
120
+ role: :job_worker
112
121
  )
113
122
  end
114
123
  end
@@ -121,10 +130,6 @@ module Ductwork
121
130
 
122
131
  private
123
132
 
124
- def logger
125
- Ductwork.configuration.logger
126
- end
127
-
128
133
  def execution_succeeded!(execution, run, output_payload)
129
134
  payload = JSON.dump({ payload: output_payload })
130
135
 
@@ -167,6 +172,16 @@ module Ductwork
167
172
  end
168
173
  end
169
174
 
175
+ Ductwork.logger.warn(
176
+ msg: "Job errored",
177
+ error_klass: error.class.name,
178
+ error_message: error.message,
179
+ job_id: id,
180
+ job_klass: klass,
181
+ pipeline_id: pipeline.id,
182
+ role: :job_worker
183
+ )
184
+
170
185
  # NOTE: perform lifecycle hook execution outside of the transaction as
171
186
  # to not unnecessarily hold it open
172
187
  if halted
@@ -56,8 +56,8 @@ module Ductwork
56
56
  step_klass = pipeline_definition.dig(:nodes, 0)
57
57
  definition = JSON.dump(pipeline_definition)
58
58
 
59
- Record.transaction do
60
- pipeline = create!(
59
+ pipeline = Record.transaction do
60
+ p = create!(
61
61
  klass: name.to_s,
62
62
  status: :in_progress,
63
63
  definition: definition,
@@ -65,7 +65,7 @@ module Ductwork
65
65
  triggered_at: Time.current,
66
66
  last_advanced_at: Time.current
67
67
  )
68
- step = pipeline.steps.create!(
68
+ step = p.steps.create!(
69
69
  klass: step_klass,
70
70
  status: :in_progress,
71
71
  step_type: :start,
@@ -73,28 +73,44 @@ module Ductwork
73
73
  )
74
74
  Ductwork::Job.enqueue(step, args)
75
75
 
76
- pipeline
76
+ p
77
77
  end
78
+
79
+ Ductwork.logger.info(
80
+ msg: "Pipeline triggered",
81
+ pipeline_id: pipeline.id,
82
+ role: :application
83
+ )
84
+
85
+ pipeline
78
86
  end
79
87
  end
80
88
 
81
89
  def advance!
82
- step = steps.advancing.take
83
- edge = if step.present?
84
- parsed_definition.dig(:edges, step.klass, 0)
85
- end
90
+ # NOTE: there could be A LOT of steps advancing for a single pipeline
91
+ # so instead of loading everything into memory and using ruby collection
92
+ # methods we make multiple queries. may need to revisist this once
93
+ # we do extensive load testing
94
+ advancing = steps.advancing
95
+ edges = if advancing.exists?
96
+ parsed_definition
97
+ .fetch(:edges, {})
98
+ .select { |k| k.in?(advancing.pluck(:klass)) }
99
+ end
86
100
 
87
101
  Ductwork::Record.transaction do
88
- steps.advancing.update!(status: :completed, completed_at: Time.current)
89
-
90
- if edge.nil?
91
- conditionally_complete_pipeline
102
+ if edges.nil? || edges.values.all?(&:empty?)
103
+ conditionally_complete_pipeline(advancing)
92
104
  else
93
- advance_to_next_step_by_type(edge, step)
105
+ advance_to_next_steps_by_type(edges, advancing)
94
106
  end
95
107
  end
96
108
  end
97
109
 
110
+ def parsed_definition
111
+ @parsed_definition ||= JSON.parse(definition).with_indifferent_access
112
+ end
113
+
98
114
  private
99
115
 
100
116
  def create_step_and_enqueue_job(klass:, step_type:, input_arg:)
@@ -104,38 +120,64 @@ module Ductwork
104
120
  Ductwork::Job.enqueue(next_step, input_arg)
105
121
  end
106
122
 
107
- def parsed_definition
108
- @parsed_definition ||= JSON.parse(definition).with_indifferent_access
109
- end
123
+ def conditionally_complete_pipeline(advancing)
124
+ advancing.update!(status: :completed, completed_at: Time.current)
110
125
 
111
- def conditionally_complete_pipeline
112
126
  if steps.where(status: %w[in_progress pending]).none?
113
127
  update!(status: :completed, completed_at: Time.current)
128
+
129
+ Ductwork.logger.info(
130
+ msg: "Pipeline completed",
131
+ pipeline_id: id,
132
+ role: :pipeline_advancer
133
+ )
114
134
  end
115
135
  end
116
136
 
117
- def advance_to_next_step_by_type(edge, step)
118
- # NOTE: "chain" is used by ActiveRecord so we have to call
119
- # this enum value "default" :sad:
120
- step_type = edge[:type] == "chain" ? "default" : edge[:type]
121
-
122
- if step_type.in?(%w[default divide])
123
- advance_to_next_steps(step_type, step, edge)
124
- elsif step_type == "combine"
125
- combine_next_steps(step_type, edge)
126
- elsif step_type == "expand"
127
- expand_to_next_steps(step_type, step, edge)
128
- elsif step_type == "collapse"
129
- collapse_next_steps(step_type, step, edge)
137
+ def advance_to_next_steps_by_type(edges, advancing)
138
+ if edges.all? { |_, v| v.dig(-1, :type) == "combine" }
139
+ conditionally_combine_next_steps(edges, advancing)
130
140
  else
131
- Ductwork.configuration.logger.error(
132
- msg: "Invalid step type",
133
- role: :pipeline_advancer
134
- )
141
+ edges.each do |step_klass, step_edges|
142
+ edge = step_edges[-1]
143
+ # NOTE: "chain" is used by ActiveRecord so we have to call
144
+ # this enum value "default" :sad:
145
+ step_type = edge[:type] == "chain" ? "default" : edge[:type]
146
+
147
+ if step_type == "collapse"
148
+ conditionally_collapse_next_steps(step_klass, edge, advancing)
149
+ else
150
+ advance_non_merging_steps(step_klass, edges, advancing)
151
+ end
152
+ end
153
+ end
154
+ advancing.update!(status: :completed, completed_at: Time.current)
155
+ log_pipeline_advanced(edges)
156
+ end
157
+
158
+ def advance_non_merging_steps(step_klass, edges, advancing)
159
+ advancing.where(klass: step_klass).find_each do |step|
160
+ edge = edges.dig(step.klass, -1)
161
+ # NOTE: "chain" is used by ActiveRecord so we have to call
162
+ # this enum value "default" :sad:
163
+ step_type = edge[:type] == "chain" ? "default" : edge[:type]
164
+
165
+ if step_type.in?(%w[default divide])
166
+ advance_to_next_steps(step_type, advancing, edge)
167
+ elsif step_type == "expand"
168
+ expand_to_next_steps(step_type, advancing, edge)
169
+ else
170
+ Ductwork.logger.error(
171
+ msg: "Invalid step type",
172
+ step_type: step_type,
173
+ pipeline_id: id,
174
+ role: :pipeline_advancer
175
+ )
176
+ end
135
177
  end
136
178
  end
137
179
 
138
- def advance_to_next_steps(step_type, step, edge)
180
+ def advance_to_next_steps(step_type, advancing, edge)
139
181
  edge[:to].each do |to_klass|
140
182
  next_step = steps.create!(
141
183
  klass: to_klass,
@@ -143,53 +185,82 @@ module Ductwork
143
185
  step_type: step_type,
144
186
  started_at: Time.current
145
187
  )
146
- Ductwork::Job.enqueue(next_step, step.job.return_value)
188
+ Ductwork::Job.enqueue(next_step, advancing.take.job.return_value)
147
189
  end
148
190
  end
149
191
 
150
- def combine_next_steps(step_type, edge)
151
- previous_klasses = parsed_definition[:edges].select do |_, v|
152
- v.dig(0, :to, 0) == edge[:to].sole && v.dig(0, :type) == "combine"
153
- end.keys
154
-
155
- if steps.not_completed.where(klass: previous_klasses).none?
156
- input_arg = Job.where(
157
- step: steps.completed.where(klass: previous_klasses)
158
- ).map(&:return_value)
159
- create_step_and_enqueue_job(
160
- klass: edge[:to].sole,
161
- step_type: step_type,
162
- input_arg: input_arg
192
+ def conditionally_combine_next_steps(edges, advancing)
193
+ if steps.where(status: %w[pending in_progress], klass: edges.keys).none?
194
+ combine_next_steps(edges, advancing)
195
+ else
196
+ Ductwork.logger.debug(
197
+ msg: "Not all divided steps have completed; not combining",
198
+ pipeline_id: id,
199
+ role: :pipeline_advancer
163
200
  )
164
201
  end
165
202
  end
166
203
 
167
- def expand_to_next_steps(step_type, step, edge)
168
- step.job.return_value.each do |input_arg|
169
- create_step_and_enqueue_job(
170
- klass: edge[:to].sole,
171
- step_type: step_type,
172
- input_arg: input_arg
173
- )
204
+ def combine_next_steps(edges, advancing)
205
+ klass = edges.values.sample.dig(-1, :to).sole
206
+ step_type = "combine"
207
+ groups = advancing
208
+ .group(:klass)
209
+ .count
210
+ .keys
211
+ .map { |k| advancing.where(klass: k) }
212
+
213
+ groups.first.zip(*groups[1..]).each do |group|
214
+ input_arg = Ductwork::Job
215
+ .where(step_id: group.map(&:id))
216
+ .map(&:return_value)
217
+ create_step_and_enqueue_job(klass:, step_type:, input_arg:)
174
218
  end
175
219
  end
176
220
 
177
- def collapse_next_steps(step_type, step, edge)
178
- if steps.not_completed.where(klass: step.klass).none?
179
- input_arg = Job.where(
180
- step: steps.completed.where(klass: step.klass)
181
- ).map(&:return_value)
221
+ def expand_to_next_steps(step_type, advancing, edge)
222
+ Array(advancing.take.job.return_value).each do |input_arg|
182
223
  create_step_and_enqueue_job(
183
224
  klass: edge[:to].sole,
184
225
  step_type: step_type,
185
226
  input_arg: input_arg
186
227
  )
228
+ end
229
+ end
230
+
231
+ def conditionally_collapse_next_steps(step_klass, edge, advancing)
232
+ if steps.where(status: %w[pending in_progress], klass: step_klass).none?
233
+ collapse_next_steps(edge[:to].sole, advancing)
187
234
  else
188
- Ductwork.configuration.logger.debug(
189
- msg: "Not all expanded steps have completed",
235
+ Ductwork.logger.debug(
236
+ msg: "Not all expanded steps have completed; not collapsing",
237
+ pipeline_id: id,
190
238
  role: :pipeline_advancer
191
239
  )
192
240
  end
193
241
  end
242
+
243
+ def collapse_next_steps(klass, advancing)
244
+ step_type = "collapse"
245
+ input_arg = []
246
+
247
+ # NOTE: because of expanding based on return values, there
248
+ # could be A LOT of jobs so we want to use batch methods
249
+ # to avoid creating too many in-memory objects
250
+ Ductwork::Job.where(step_id: advancing.ids).find_each do |job|
251
+ input_arg << job.return_value
252
+ end
253
+
254
+ create_step_and_enqueue_job(klass:, step_type:, input_arg:)
255
+ end
256
+
257
+ def log_pipeline_advanced(edges)
258
+ Ductwork.logger.info(
259
+ msg: "Pipeline advanced",
260
+ pipeline_id: id,
261
+ transitions: edges.map { |_, v| v.dig(-1, :type) },
262
+ role: :pipeline_advancer
263
+ )
264
+ end
194
265
  end
195
266
  end
data/lib/ductwork/cli.rb CHANGED
@@ -8,12 +8,12 @@ module Ductwork
8
8
  def start!(args)
9
9
  options = parse_options(args)
10
10
  Ductwork.configuration = Configuration.new(**options)
11
- Ductwork.configuration.logger = if Ductwork.configuration.logger_source == "rails"
12
- Rails.logger
13
- else
14
- Ductwork::Configuration::DEFAULT_LOGGER
15
- end
16
- Ductwork.configuration.logger.level = Ductwork.configuration.logger_level
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
17
 
18
18
  Ductwork::Processes::SupervisorRunner.start!
19
19
  end
@@ -17,7 +17,6 @@ module Ductwork
17
17
  DEFAULT_LOGGER = ::Logger.new($stdout)
18
18
  PIPELINES_WILDCARD = "*"
19
19
 
20
- attr_accessor :logger
21
20
  attr_writer :job_worker_polling_timeout, :job_worker_shutdown_timeout,
22
21
  :job_worker_max_retry,
23
22
  :logger_level,
@@ -12,8 +12,8 @@ module Ductwork
12
12
  nodes: [],
13
13
  edges: {},
14
14
  }
15
- @divisions = 0
16
- @expansions = 0
15
+ @divergences = []
16
+ @last_nodes = []
17
17
  end
18
18
 
19
19
  def start(klass)
@@ -29,7 +29,7 @@ module Ductwork
29
29
  def chain(klass)
30
30
  validate_classes!(klass)
31
31
  validate_definition_started!(action: "chaining")
32
- add_edge_to_last_node(klass, type: :chain)
32
+ add_edge_to_last_nodes(klass, type: :chain)
33
33
  add_new_nodes(klass)
34
34
 
35
35
  self
@@ -38,10 +38,10 @@ module Ductwork
38
38
  def divide(to:)
39
39
  validate_classes!(to)
40
40
  validate_definition_started!(action: "dividing chain")
41
- add_edge_to_last_node(*to, type: :divide)
41
+ add_edge_to_last_nodes(*to, type: :divide)
42
42
  add_new_nodes(*to)
43
43
 
44
- @divisions += 1
44
+ divergences.push(:divide)
45
45
 
46
46
  if block_given?
47
47
  branches = to.map do |klass|
@@ -58,9 +58,9 @@ module Ductwork
58
58
  def combine(into:)
59
59
  validate_classes!(into)
60
60
  validate_definition_started!(action: "combining steps")
61
- validate_definition_divided!
61
+ validate_can_combine!
62
62
 
63
- @divisions -= 1
63
+ divergences.pop
64
64
 
65
65
  last_nodes = definition[:nodes].reverse.select do |node|
66
66
  definition[:edges][node].empty?
@@ -79,10 +79,10 @@ module Ductwork
79
79
  def expand(to:)
80
80
  validate_classes!(to)
81
81
  validate_definition_started!(action: "expanding chain")
82
- add_edge_to_last_node(to, type: :expand)
82
+ add_edge_to_last_nodes(to, type: :expand)
83
83
  add_new_nodes(to)
84
84
 
85
- @expansions += 1
85
+ divergences.push(:expand)
86
86
 
87
87
  self
88
88
  end
@@ -90,11 +90,11 @@ module Ductwork
90
90
  def collapse(into:)
91
91
  validate_classes!(into)
92
92
  validate_definition_started!(action: "collapsing steps")
93
- validate_definition_expanded!
94
- add_edge_to_last_node(into, type: :collapse)
93
+ validate_can_collapse!
94
+ add_edge_to_last_nodes(into, type: :collapse)
95
95
  add_new_nodes(into)
96
96
 
97
- @expansions -= 1
97
+ divergences.pop
98
98
 
99
99
  self
100
100
  end
@@ -117,7 +117,7 @@ module Ductwork
117
117
 
118
118
  private
119
119
 
120
- attr_reader :definition, :divisions, :expansions
120
+ attr_reader :definition, :last_nodes, :divergences
121
121
 
122
122
  def validate_classes!(klasses)
123
123
  valid = Array(klasses).all? do |klass|
@@ -149,32 +149,39 @@ module Ductwork
149
149
  end
150
150
  end
151
151
 
152
- def validate_definition_divided!
153
- if divisions.zero?
152
+ def validate_can_combine!
153
+ if divergences.empty?
154
154
  raise CombineError, "Must divide pipeline definition before combining steps"
155
+ elsif divergences[-1] != :divide
156
+ raise CombineError, "Ambiguous combine on most recently expanded definition"
155
157
  end
156
158
  end
157
159
 
158
- def validate_definition_expanded!
159
- if expansions.zero?
160
+ def validate_can_collapse!
161
+ if divergences.empty?
160
162
  raise CollapseError, "Must expand pipeline definition before collapsing steps"
163
+ elsif divergences[-1] != :expand
164
+ raise CollapseError, "Ambiguous collapse on most recently divided definition"
161
165
  end
162
166
  end
163
167
 
164
168
  def add_new_nodes(*klasses)
165
- definition[:nodes].push(*klasses.map(&:name))
169
+ nodes = klasses.map(&:name)
170
+ @last_nodes = Array(nodes)
171
+
172
+ definition[:nodes].push(*nodes)
166
173
  klasses.each do |klass|
167
174
  definition[:edges][klass.name] ||= []
168
175
  end
169
176
  end
170
177
 
171
- def add_edge_to_last_node(*klasses, type:)
172
- last_node = definition.dig(:nodes, -1)
173
-
174
- definition[:edges][last_node] << {
175
- to: klasses.map(&:name),
176
- type: type,
177
- }
178
+ def add_edge_to_last_nodes(*klasses, type:)
179
+ last_nodes.each do |last_node|
180
+ definition[:edges][last_node] << {
181
+ to: klasses.map(&:name),
182
+ type: type,
183
+ }
184
+ end
178
185
  end
179
186
  end
180
187
  end
@@ -8,7 +8,7 @@ module Ductwork
8
8
 
9
9
  initializer "ductwork.configure" do
10
10
  Ductwork.configuration ||= Ductwork::Configuration.new
11
- Ductwork.configuration.logger ||= Rails.logger
11
+ Ductwork.logger ||= Ductwork::Configuration::DEFAULT_LOGGER
12
12
  end
13
13
 
14
14
  initializer "ductwork.validate_definitions", after: :load_config_initializers do
@@ -10,13 +10,13 @@ module Ductwork
10
10
 
11
11
  def run
12
12
  run_hooks_for(:start)
13
- logger.debug(
13
+ Ductwork.logger.debug(
14
14
  msg: "Entering main work loop",
15
15
  role: :job_worker,
16
16
  pipeline: pipeline
17
17
  )
18
18
  while running_context.running?
19
- logger.debug(
19
+ Ductwork.logger.debug(
20
20
  msg: "Attempting to claim job",
21
21
  role: :job_worker,
22
22
  pipeline: pipeline
@@ -30,7 +30,7 @@ module Ductwork
30
30
  job.execute(pipeline)
31
31
  end
32
32
  else
33
- logger.debug(
33
+ Ductwork.logger.debug(
34
34
  msg: "No job to claim, looping",
35
35
  role: :job_worker,
36
36
  pipeline: pipeline
@@ -47,7 +47,7 @@ module Ductwork
47
47
  attr_reader :pipeline, :running_context
48
48
 
49
49
  def shutdown
50
- logger.debug(
50
+ Ductwork.logger.debug(
51
51
  msg: "Shutting down",
52
52
  role: :job_worker,
53
53
  pipeline: pipeline
@@ -62,10 +62,6 @@ module Ductwork
62
62
  end
63
63
  end
64
64
  end
65
-
66
- def logger
67
- Ductwork.configuration.logger
68
- end
69
65
  end
70
66
  end
71
67
  end
@@ -25,7 +25,7 @@ module Ductwork
25
25
 
26
26
  def run
27
27
  create_process!
28
- logger.debug(
28
+ Ductwork.logger.debug(
29
29
  msg: "Entering main work loop",
30
30
  role: :job_worker_runner,
31
31
  pipeline: pipeline
@@ -55,7 +55,7 @@ module Ductwork
55
55
  pipeline,
56
56
  running_context
57
57
  )
58
- logger.debug(
58
+ Ductwork.logger.debug(
59
59
  msg: "Creating new thread",
60
60
  role: :job_worker_runner,
61
61
  pipeline: pipeline
@@ -65,7 +65,7 @@ module Ductwork
65
65
  end
66
66
  thread.name = "ductwork.job_worker_#{i}"
67
67
 
68
- logger.debug(
68
+ Ductwork.logger.debug(
69
69
  msg: "Created new thread",
70
70
  role: :job_worker_runner,
71
71
  pipeline: pipeline
@@ -90,13 +90,13 @@ module Ductwork
90
90
  end
91
91
 
92
92
  def attempt_synchronize_threads
93
- logger.debug(
93
+ Ductwork.logger.debug(
94
94
  msg: "Attempting to synchronize threads",
95
95
  role: :job_worker_runner,
96
96
  pipeline: pipeline
97
97
  )
98
98
  threads.each { |thread| thread.join(0.1) }
99
- logger.debug(
99
+ Ductwork.logger.debug(
100
100
  msg: "Synchronizing threads timed out",
101
101
  role: :job_worker_runner,
102
102
  pipeline: pipeline
@@ -104,11 +104,11 @@ module Ductwork
104
104
  end
105
105
 
106
106
  def report_heartbeat!
107
- logger.debug(msg: "Reporting heartbeat", role: :job_worker_runner)
107
+ Ductwork.logger.debug(msg: "Reporting heartbeat", role: :job_worker_runner)
108
108
  Ductwork.wrap_with_app_executor do
109
109
  Ductwork::Process.report_heartbeat!
110
110
  end
111
- logger.debug(msg: "Reported heartbeat", role: :job_worker_runner)
111
+ Ductwork.logger.debug(msg: "Reported heartbeat", role: :job_worker_runner)
112
112
  end
113
113
 
114
114
  def shutdown!
@@ -122,7 +122,7 @@ module Ductwork
122
122
  timeout = Ductwork.configuration.job_worker_shutdown_timeout
123
123
  deadline = Time.current + timeout
124
124
 
125
- logger.debug(msg: "Attempting graceful shutdown", role: :job_worker_runner)
125
+ Ductwork.logger.debug(msg: "Attempting graceful shutdown", role: :job_worker_runner)
126
126
  while Time.current < deadline && threads.any?(&:alive?)
127
127
  threads.each do |thread|
128
128
  break if Time.current < deadline
@@ -138,7 +138,7 @@ module Ductwork
138
138
  threads.each do |thread|
139
139
  if thread.alive?
140
140
  thread.kill
141
- logger.debug(
141
+ Ductwork.logger.debug(
142
142
  msg: "Killed thread",
143
143
  role: :job_worker_runner,
144
144
  thread: thread.name
@@ -155,10 +155,6 @@ module Ductwork
155
155
  ).delete
156
156
  end
157
157
  end
158
-
159
- def logger
160
- Ductwork.configuration.logger
161
- end
162
158
  end
163
159
  end
164
160
  end
@@ -28,7 +28,7 @@ module Ductwork
28
28
  .update_all(claimed_for_advancing_at: Time.current)
29
29
 
30
30
  if rows_updated == 1
31
- logger.debug(
31
+ Ductwork.logger.debug(
32
32
  msg: "Pipeline claimed",
33
33
  pipeline: klass,
34
34
  role: :pipeline_advancer
@@ -37,13 +37,13 @@ module Ductwork
37
37
  pipeline = Ductwork::Pipeline.find(id)
38
38
  pipeline.advance!
39
39
 
40
- logger.debug(
40
+ Ductwork.logger.debug(
41
41
  msg: "Pipeline advanced",
42
42
  pipeline: klass,
43
43
  role: :pipeline_advancer
44
44
  )
45
45
  else
46
- logger.debug(
46
+ Ductwork.logger.debug(
47
47
  msg: "Did not claim pipeline, avoided race condition",
48
48
  pipeline: klass,
49
49
  role: :pipeline_advancer
@@ -57,7 +57,7 @@ module Ductwork
57
57
  last_advanced_at: Time.current
58
58
  )
59
59
  else
60
- logger.debug(
60
+ Ductwork.logger.debug(
61
61
  msg: "No pipeline needs advancing",
62
62
  pipeline: klass,
63
63
  id: id,
@@ -82,10 +82,6 @@ module Ductwork
82
82
  end
83
83
  end
84
84
  end
85
-
86
- def logger
87
- Ductwork.configuration.logger
88
- end
89
85
  end
90
86
  end
91
87
  end
@@ -25,7 +25,7 @@ module Ductwork
25
25
 
26
26
  def run
27
27
  create_process!
28
- logger.debug(
28
+ Ductwork.logger.debug(
29
29
  msg: "Entering main work loop",
30
30
  role: :pipeline_advancer_runner
31
31
  )
@@ -51,7 +51,7 @@ module Ductwork
51
51
  klass
52
52
  )
53
53
 
54
- logger.debug(
54
+ Ductwork.logger.debug(
55
55
  msg: "Creating new thread",
56
56
  role: :pipeline_advancer_runner,
57
57
  pipeline: klass
@@ -63,7 +63,7 @@ module Ductwork
63
63
  end
64
64
  thread.name = "ductwork.pipeline_advancer.#{klass}"
65
65
 
66
- logger.debug(
66
+ Ductwork.logger.debug(
67
67
  msg: "Created new thread",
68
68
  role: :pipeline_advancer_runner,
69
69
  thread: thread.name,
@@ -75,12 +75,12 @@ module Ductwork
75
75
  end
76
76
 
77
77
  def attempt_synchronize_threads
78
- logger.debug(
78
+ Ductwork.logger.debug(
79
79
  msg: "Attempting to synchronize threads",
80
80
  role: :pipeline_advancer_runner
81
81
  )
82
82
  threads.each { |thread| thread.join(0.1) }
83
- logger.debug(
83
+ Ductwork.logger.debug(
84
84
  msg: "Synchronizing threads timed out",
85
85
  role: :pipeline_advancer_runner
86
86
  )
@@ -97,11 +97,11 @@ module Ductwork
97
97
  end
98
98
 
99
99
  def report_heartbeat!
100
- logger.debug(msg: "Reporting heartbeat", role: :pipeline_advancer_runner)
100
+ Ductwork.logger.debug(msg: "Reporting heartbeat", role: :pipeline_advancer_runner)
101
101
  Ductwork.wrap_with_app_executor do
102
102
  Ductwork::Process.report_heartbeat!
103
103
  end
104
- logger.debug(msg: "Reported heartbeat", role: :pipeline_advancer_runner)
104
+ Ductwork.logger.debug(msg: "Reported heartbeat", role: :pipeline_advancer_runner)
105
105
  end
106
106
 
107
107
  def shutdown
@@ -113,7 +113,7 @@ module Ductwork
113
113
  end
114
114
 
115
115
  def log_shutting_down
116
- logger.debug(msg: "Shutting down", role: :pipeline_advancer_runner)
116
+ Ductwork.logger.debug(msg: "Shutting down", role: :pipeline_advancer_runner)
117
117
  end
118
118
 
119
119
  def stop_running_context
@@ -124,7 +124,7 @@ module Ductwork
124
124
  timeout = Ductwork.configuration.pipeline_shutdown_timeout
125
125
  deadline = Time.current + timeout
126
126
 
127
- logger.debug(
127
+ Ductwork.logger.debug(
128
128
  msg: "Attempting graceful shutdown",
129
129
  role: :pipeline_advancer_runner
130
130
  )
@@ -143,7 +143,7 @@ module Ductwork
143
143
  threads.each do |thread|
144
144
  if thread.alive?
145
145
  thread.kill
146
- logger.debug(
146
+ Ductwork.logger.debug(
147
147
  msg: "Killed thread",
148
148
  role: :pipeline_advancer_runner,
149
149
  thread: thread.name
@@ -160,10 +160,6 @@ module Ductwork
160
160
  ).delete
161
161
  end
162
162
  end
163
-
164
- def logger
165
- Ductwork.configuration.logger
166
- end
167
163
  end
168
164
  end
169
165
  end
@@ -22,14 +22,14 @@ module Ductwork
22
22
  end
23
23
 
24
24
  workers << { metadata:, pid:, block: }
25
- logger.debug(
25
+ Ductwork.logger.debug(
26
26
  msg: "Started child process (#{pid}) with metadata #{metadata}",
27
27
  pid: pid
28
28
  )
29
29
  end
30
30
 
31
31
  def run
32
- logger.debug(msg: "Entering main work loop", role: :supervisor, pid: ::Process.pid)
32
+ Ductwork.logger.debug(msg: "Entering main work loop", role: :supervisor, pid: ::Process.pid)
33
33
 
34
34
  while running
35
35
  sleep(Ductwork.configuration.supervisor_polling_timeout)
@@ -42,7 +42,7 @@ module Ductwork
42
42
  def shutdown
43
43
  @running = false
44
44
 
45
- logger.debug(msg: "Beginning shutdown", role: :supervisor)
45
+ Ductwork.logger.debug(msg: "Beginning shutdown", role: :supervisor)
46
46
  terminate_gracefully
47
47
  wait_for_workers_to_exit
48
48
  terminate_immediately
@@ -54,7 +54,7 @@ module Ductwork
54
54
  attr_reader :running
55
55
 
56
56
  def check_workers
57
- logger.debug(msg: "Checking workers are alive", role: :supervisor)
57
+ Ductwork.logger.debug(msg: "Checking workers are alive", role: :supervisor)
58
58
 
59
59
  workers.each do |worker|
60
60
  if process_dead?(worker[:pid])
@@ -63,7 +63,7 @@ module Ductwork
63
63
  worker[:block].call(worker[:metadata])
64
64
  end
65
65
  worker[:pid] = new_pid
66
- logger.debug(
66
+ Ductwork.logger.debug(
67
67
  msg: "Restarted process (#{old_pid}) as (#{new_pid})",
68
68
  role: :supervisor,
69
69
  old_pid: old_pid,
@@ -72,12 +72,12 @@ module Ductwork
72
72
  end
73
73
  end
74
74
 
75
- logger.debug(msg: "All workers are alive or revived", role: :supervisor)
75
+ Ductwork.logger.debug(msg: "All workers are alive or revived", role: :supervisor)
76
76
  end
77
77
 
78
78
  def terminate_gracefully
79
79
  workers.each do |worker|
80
- logger.debug(
80
+ Ductwork.logger.debug(
81
81
  msg: "Sending TERM signal to process (#{worker[:pid]})",
82
82
  role: :supervisor,
83
83
  pid: worker[:pid],
@@ -95,7 +95,7 @@ module Ductwork
95
95
  workers.each_with_index do |worker, index|
96
96
  if ::Process.wait(worker[:pid], ::Process::WNOHANG)
97
97
  workers[index] = nil
98
- logger.debug(
98
+ Ductwork.logger.debug(
99
99
  msg: "Child process (#{worker[:pid]}) stopped successfully",
100
100
  role: :supervisor,
101
101
  pid: worker[:pid]
@@ -108,7 +108,7 @@ module Ductwork
108
108
 
109
109
  def terminate_immediately
110
110
  workers.each_with_index do |worker, index|
111
- logger.debug(
111
+ Ductwork.logger.debug(
112
112
  msg: "Sending KILL signal to process (#{worker[:pid]})",
113
113
  role: :supervisor,
114
114
  pid: worker[:pid],
@@ -117,7 +117,7 @@ module Ductwork
117
117
  ::Process.kill(:KILL, worker[:pid])
118
118
  ::Process.wait(worker[:pid])
119
119
  workers[index] = nil
120
- logger.debug(
120
+ Ductwork.logger.debug(
121
121
  msg: "Child process (#{worker[:pid]}) killed after timeout",
122
122
  role: :supervisor,
123
123
  pid: worker[:pid]
@@ -151,10 +151,6 @@ module Ductwork
151
151
  def now
152
152
  ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
153
153
  end
154
-
155
- def logger
156
- Ductwork.configuration.logger
157
- end
158
154
  end
159
155
  end
160
156
  end
@@ -6,10 +6,9 @@ module Ductwork
6
6
  def self.start!
7
7
  supervisor = Ductwork::Processes::Supervisor.new
8
8
  pipelines_to_advance = Ductwork.configuration.pipelines
9
- logger = Ductwork.configuration.logger
10
9
 
11
10
  supervisor.add_worker(metadata: { pipelines: pipelines_to_advance }) do
12
- logger.debug(
11
+ Ductwork.logger.debug(
13
12
  msg: "Starting Pipeline Advancer process",
14
13
  role: :supervisor_runner
15
14
  )
@@ -19,7 +18,7 @@ module Ductwork
19
18
 
20
19
  pipelines_to_advance.each do |pipeline|
21
20
  supervisor.add_worker(metadata: { pipeline: }) do
22
- logger.debug(
21
+ Ductwork.logger.debug(
23
22
  msg: "Starting Job Worker Runner process",
24
23
  role: :supervisor_runner,
25
24
  pipeline: pipeline
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ductwork
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/ductwork.rb CHANGED
@@ -19,7 +19,7 @@ loader.setup
19
19
 
20
20
  module Ductwork
21
21
  class << self
22
- attr_accessor :app_executor, :configuration
22
+ attr_accessor :app_executor, :configuration, :logger
23
23
  attr_writer :defined_pipelines, :hooks
24
24
 
25
25
  def wrap_with_app_executor(&block)
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.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Ewing
@@ -86,7 +86,7 @@ dependencies:
86
86
  description: Ductwork lets you build complex pipelines quickly and easily using intuitive
87
87
  Ruby tooling and a natural DSL.
88
88
  email:
89
- - tewing10@gmail.com
89
+ - contact@getductwork.io
90
90
  executables: []
91
91
  extensions: []
92
92
  extra_rdoc_files: []
@@ -140,7 +140,7 @@ licenses:
140
140
  - LGPL-3.0-or-later
141
141
  metadata:
142
142
  homepage_uri: https://github.com/ductwork/ductwork
143
- changelog_uri: https://github.com/ductwork/ductwork/blob/main/README.md
143
+ changelog_uri: https://github.com/ductwork/ductwork/blob/main/CHANGELOG.md
144
144
  rubygems_mfa_required: 'true'
145
145
  documentation_uri: https://docs.getductwork.io/
146
146
  rdoc_options: []