ductwork 0.26.0 → 1.1.0

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.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/audit-clock-drift/SKILL.md +11 -0
  3. data/.claude/skills/audit-database-indexes/SKILL.md +11 -0
  4. data/.claude/skills/audit-database-support/SKILL.md +18 -0
  5. data/.claude/skills/audit-durability/SKILL.md +17 -0
  6. data/.saturnci/database.yml +1 -0
  7. data/CHANGELOG-PRO.md +63 -0
  8. data/CHANGELOG.md +149 -1
  9. data/CLAUDE.md +34 -0
  10. data/README.md +24 -14
  11. data/app/controllers/ductwork/application_controller.rb +3 -3
  12. data/app/controllers/ductwork/dashboards_controller.rb +1 -1
  13. data/app/controllers/ductwork/pipelines_controller.rb +4 -3
  14. data/app/helpers/ductwork/application_helper.rb +2 -2
  15. data/app/views/ductwork/dashboards/show.html.erb +12 -12
  16. data/app/views/ductwork/pipelines/index.html.erb +12 -12
  17. data/app/views/ductwork/pipelines/show.html.erb +13 -13
  18. data/app/views/ductwork/step_errors/index.html.erb +2 -2
  19. data/lib/ductwork/branch_claim.rb +185 -0
  20. data/lib/ductwork/cli.rb +82 -35
  21. data/lib/ductwork/configuration.rb +73 -3
  22. data/lib/ductwork/context.rb +17 -19
  23. data/lib/ductwork/database_clock.rb +84 -0
  24. data/lib/ductwork/dsl/branch_builder.rb +6 -1
  25. data/lib/ductwork/dsl/definition_builder.rb +24 -1
  26. data/lib/ductwork/{job_claim.rb → execution_claim.rb} +7 -6
  27. data/lib/ductwork/fault_injection.rb +35 -0
  28. data/lib/ductwork/migration_helper.rb +1 -1
  29. data/lib/ductwork/models/advancement.rb +48 -0
  30. data/lib/ductwork/models/attempt.rb +9 -0
  31. data/lib/ductwork/models/availability.rb +2 -0
  32. data/lib/ductwork/models/branch.rb +800 -0
  33. data/lib/ductwork/models/branch_link.rb +10 -0
  34. data/lib/ductwork/models/execution.rb +199 -2
  35. data/lib/ductwork/models/job.rb +8 -121
  36. data/lib/ductwork/models/pipeline.rb +150 -272
  37. data/lib/ductwork/models/process.rb +177 -5
  38. data/lib/ductwork/models/result.rb +2 -1
  39. data/lib/ductwork/models/run.rb +119 -1
  40. data/lib/ductwork/models/step.rb +26 -6
  41. data/lib/ductwork/models/transition.rb +15 -0
  42. data/lib/ductwork/models/tuple.rb +1 -4
  43. data/lib/ductwork/models/workflow.rb +3 -0
  44. data/lib/ductwork/optimistic_locking_execution_claim.rb +97 -0
  45. data/lib/ductwork/processes/health_check.rb +87 -0
  46. data/lib/ductwork/processes/job_worker.rb +86 -20
  47. data/lib/ductwork/processes/job_worker_runner.rb +25 -37
  48. data/lib/ductwork/processes/pipeline_advancer.rb +48 -76
  49. data/lib/ductwork/processes/pipeline_advancer_runner.rb +34 -44
  50. data/lib/ductwork/processes/process_supervisor.rb +87 -10
  51. data/lib/ductwork/processes/thread_supervisor.rb +55 -21
  52. data/lib/ductwork/processes/thread_supervisor_runner.rb +8 -5
  53. data/lib/ductwork/processes/worker_health_check.rb +55 -0
  54. data/lib/ductwork/row_locking_execution_claim.rb +77 -0
  55. data/lib/ductwork/testing/rspec.rb +28 -11
  56. data/lib/ductwork/version.rb +1 -1
  57. data/lib/ductwork.rb +5 -0
  58. data/lib/generators/ductwork/install/install_generator.rb +14 -4
  59. data/lib/generators/ductwork/install/templates/config/ductwork.yml +4 -0
  60. data/lib/generators/ductwork/install/templates/db/create_ductwork_advancements.rb +29 -0
  61. data/lib/generators/ductwork/install/templates/db/create_ductwork_attempts.rb +20 -0
  62. data/lib/generators/ductwork/install/templates/db/create_ductwork_availabilities.rb +18 -5
  63. data/lib/generators/ductwork/install/templates/db/create_ductwork_branch_links.rb +27 -0
  64. data/lib/generators/ductwork/install/templates/db/create_ductwork_branches.rb +37 -0
  65. data/lib/generators/ductwork/install/templates/db/create_ductwork_executions.rb +8 -1
  66. data/lib/generators/ductwork/install/templates/db/create_ductwork_pipelines.rb +0 -8
  67. data/lib/generators/ductwork/install/templates/db/create_ductwork_processes.rb +2 -0
  68. data/lib/generators/ductwork/install/templates/db/create_ductwork_results.rb +1 -0
  69. data/lib/generators/ductwork/install/templates/db/create_ductwork_runs.rb +45 -6
  70. data/lib/generators/ductwork/install/templates/db/create_ductwork_steps.rb +20 -5
  71. data/lib/generators/ductwork/install/templates/db/create_ductwork_transitions.rb +43 -0
  72. data/lib/generators/ductwork/install/templates/db/create_ductwork_tuples.rb +3 -3
  73. data/lib/generators/ductwork/update/templates/db/add_crash_count_to_ductwork_advancements.rb +7 -0
  74. data/lib/generators/ductwork/update/templates/db/add_crash_count_to_ductwork_executions.rb +14 -0
  75. data/lib/generators/ductwork/update/templates/db/add_indexes_to_ductwork_results.rb +7 -0
  76. data/lib/generators/ductwork/update/templates/db/add_indexes_to_ductwork_runs.rb +9 -0
  77. data/lib/generators/ductwork/update/templates/db/add_indexes_to_ductwork_transitions.rb +16 -0
  78. data/lib/generators/ductwork/update/templates/db/add_pipeline_started_index_to_ductwork_runs.rb +7 -0
  79. data/lib/generators/ductwork/update/templates/db/add_process_id_to_ductwork_executions.rb +35 -0
  80. data/lib/generators/ductwork/update/templates/db/add_role_to_ductwork_processes.rb +20 -0
  81. data/lib/generators/ductwork/update/templates/db/associate_branches_to_runs.rb +74 -0
  82. data/lib/generators/ductwork/update/templates/db/associate_steps_to_branches.rb +24 -0
  83. data/lib/generators/ductwork/update/templates/db/associate_steps_to_runs.rb +72 -0
  84. data/lib/generators/ductwork/update/templates/db/associate_tuples_to_runs.rb +73 -0
  85. data/lib/generators/ductwork/update/templates/db/backfill_branch_ids_on_steps.rb +21 -0
  86. data/lib/generators/ductwork/update/templates/db/create_ductwork_advancements.rb +28 -0
  87. data/lib/generators/ductwork/update/templates/db/create_ductwork_branch_links.rb +27 -0
  88. data/lib/generators/ductwork/update/templates/db/create_ductwork_branches.rb +37 -0
  89. data/lib/generators/ductwork/update/templates/db/create_ductwork_runs.rb +55 -0
  90. data/lib/generators/ductwork/update/templates/db/create_ductwork_transitions.rb +32 -0
  91. data/lib/generators/ductwork/update/templates/db/denormalize_pipeline_klass_on_availabilities.rb +11 -4
  92. data/lib/generators/ductwork/update/templates/db/migrate_tables_to_uuid_primary_key.rb +5 -0
  93. data/lib/generators/ductwork/update/templates/db/rename_runs_to_attempts.rb +7 -0
  94. data/lib/generators/ductwork/update/templates/db/update_process_associations.rb +29 -0
  95. data/lib/generators/ductwork/update/update_generator.rb +99 -0
  96. metadata +50 -9
  97. data/lib/ductwork/optimistic_locking_job_claim.rb +0 -88
  98. data/lib/ductwork/row_locking_job_claim.rb +0 -75
@@ -3,7 +3,7 @@
3
3
  module Ductwork
4
4
  module Processes
5
5
  class JobWorker
6
- attr_reader :thread, :last_heartbeat_at, :job, :pipeline
6
+ attr_reader :thread, :last_heartbeat_at, :execution, :pipeline
7
7
 
8
8
  def initialize(pipeline, id)
9
9
  @pipeline = pipeline
@@ -18,12 +18,19 @@ module Ductwork
18
18
  @thread.name = name
19
19
  end
20
20
 
21
- alias restart start
21
+ def restart
22
+ cleanup_dead_thread!
23
+ start
24
+ end
22
25
 
23
26
  def alive?
24
27
  thread&.alive? || false
25
28
  end
26
29
 
30
+ def stuck?
31
+ execution.nil? && last_heartbeat_too_long_ago?
32
+ end
33
+
27
34
  def stop
28
35
  running_context.shutdown!
29
36
  end
@@ -45,7 +52,7 @@ module Ductwork
45
52
 
46
53
  attr_reader :id, :running_context
47
54
 
48
- def work_loop
55
+ def work_loop # rubocop:todo Metrics
49
56
  run_hooks_for(:start)
50
57
 
51
58
  Ductwork.logger.debug(
@@ -55,29 +62,74 @@ module Ductwork
55
62
  )
56
63
 
57
64
  while running_context.running?
58
- Ductwork.logger.debug(
59
- msg: "Attempting to claim job",
60
- role: :job_worker,
61
- pipeline: pipeline
62
- )
63
-
64
- @job = Ductwork.wrap_with_app_executor do
65
- Job.claim_latest(pipeline)
66
- end
65
+ owner_process_id = nil
67
66
 
68
- if job.present?
69
- Ductwork.wrap_with_app_executor do
70
- job.execute(pipeline)
67
+ begin
68
+ Ductwork.logger.debug(
69
+ msg: "Attempting to claim job",
70
+ role: :job_worker,
71
+ pipeline: pipeline
72
+ )
73
+
74
+ @execution = Ductwork.wrap_with_app_executor do
75
+ owner_process_id = Ductwork::Process.current.id
76
+ Ductwork::ExecutionClaim.new(pipeline, owner_process_id).latest
71
77
  end
72
78
 
73
- @job = nil
74
- else
75
- Ductwork.logger.debug(
76
- msg: "No job to claim, looping",
79
+ if execution.present?
80
+ Ductwork::FaultInjection.checkpoint(:after_job_claim)
81
+
82
+ Ductwork.wrap_with_app_executor do
83
+ execution.call(pipeline, owner_process_id)
84
+ end
85
+
86
+ @execution = nil
87
+ else
88
+ Ductwork.logger.debug(
89
+ msg: "No job to claim, looping",
90
+ role: :job_worker,
91
+ pipeline: pipeline
92
+ )
93
+ sleep(polling_timeout)
94
+ end
95
+ rescue Ductwork::Execution::CommitFailed => e
96
+ Ductwork.logger.error(
97
+ msg: "Execution commit failed",
98
+ error_klass: e.class.name,
99
+ error_message: e.message,
100
+ job_id: execution&.job_id,
101
+ job_klass: execution&.job&.klass,
77
102
  role: :job_worker,
78
103
  pipeline: pipeline
79
104
  )
80
- sleep(polling_timeout)
105
+
106
+ @execution = nil
107
+ rescue StandardError => e
108
+ if execution.present?
109
+ Ductwork.wrap_with_app_executor do
110
+ execution.crashed!
111
+ end
112
+ end
113
+
114
+ Ductwork.logger.error(
115
+ msg: "Unexpected error in work loop",
116
+ error_klass: e.class.name,
117
+ error_message: e.message,
118
+ job_id: execution&.job_id,
119
+ job_klass: execution&.job&.klass,
120
+ role: :job_worker,
121
+ pipeline: pipeline
122
+ )
123
+ ensure
124
+ if execution.present? && execution.reload.completed_at.nil?
125
+ Ductwork.wrap_with_app_executor do
126
+ execution.crashed!
127
+ rescue StandardError
128
+ nil
129
+ end
130
+ end
131
+
132
+ @execution = nil
81
133
  end
82
134
 
83
135
  @last_heartbeat_at = Time.current
@@ -92,6 +144,20 @@ module Ductwork
92
144
  run_hooks_for(:stop)
93
145
  end
94
146
 
147
+ def cleanup_dead_thread!
148
+ if execution.present? && execution.reload.completed_at.nil?
149
+ execution.crashed!
150
+ end
151
+ ensure
152
+ @execution = nil
153
+ end
154
+
155
+ def last_heartbeat_too_long_ago?
156
+ threshold = Ductwork.configuration.supervisor_reaper_timeout
157
+
158
+ (Time.current - last_heartbeat_at) > threshold
159
+ end
160
+
95
161
  def run_hooks_for(event)
96
162
  Ductwork.hooks[:worker].fetch(event, []).each do |block|
97
163
  Ductwork.wrap_with_app_executor do
@@ -24,7 +24,7 @@ module Ductwork
24
24
  end
25
25
 
26
26
  def run
27
- create_process_record!
27
+ adopt_or_create_process!
28
28
  start_job_workers
29
29
 
30
30
  Ductwork.logger.debug(
@@ -36,7 +36,7 @@ module Ductwork
36
36
  while running?
37
37
  # TODO: Increase or make configurable
38
38
  sleep(5)
39
- check_thread_health
39
+ check_workers_health
40
40
  report_heartbeat!
41
41
  end
42
42
 
@@ -47,13 +47,9 @@ module Ductwork
47
47
 
48
48
  attr_reader :pipelines, :running_context, :job_workers
49
49
 
50
- def create_process_record!
50
+ def adopt_or_create_process!
51
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
- )
52
+ Ductwork::Process.adopt_or_create_current!(:job_worker)
57
53
  end
58
54
  end
59
55
 
@@ -78,37 +74,32 @@ module Ductwork
78
74
  running_context.running?
79
75
  end
80
76
 
81
- def check_thread_health
82
- Ductwork.logger.debug(
83
- msg: "Checking thread health",
84
- role: :job_worker_runner,
85
- pipelines: pipelines
86
- )
87
- job_workers.each do |job_worker|
88
- if !job_worker.alive?
89
- job_worker.restart
90
-
91
- Ductwork.logger.warn(
92
- msg: "Restarted job worker",
93
- role: :job_worker_runner,
94
- pipeline: job_worker.pipeline,
95
- thread: job_worker.name
96
- )
97
- end
98
- end
99
- Ductwork.logger.debug(
100
- msg: "Checked thread health",
101
- role: :job_worker_runner,
102
- pipelines: pipelines
77
+ def check_workers_health
78
+ Ductwork::Processes::WorkerHealthCheck
79
+ .new(job_workers, :job_worker_runner)
80
+ .check
81
+ rescue StandardError => e
82
+ Ductwork.logger.warn(
83
+ msg: "Checking worker health failed",
84
+ error_klass: e.class.to_s,
85
+ error_message: e.message,
86
+ role: :job_worker_runner
103
87
  )
104
88
  end
105
89
 
106
90
  def report_heartbeat!
107
91
  Ductwork.logger.debug(msg: "Reporting heartbeat", role: :job_worker_runner)
108
92
  Ductwork.wrap_with_app_executor do
109
- Ductwork::Process.report_heartbeat!
93
+ Ductwork::Process.report_heartbeat!(:job_worker)
110
94
  end
111
95
  Ductwork.logger.debug(msg: "Reported heartbeat", role: :job_worker_runner)
96
+ rescue StandardError => e
97
+ Ductwork.logger.warn(
98
+ msg: "Reporting heartbeat failed",
99
+ error_klass: e.class.to_s,
100
+ error_message: e.message,
101
+ role: :job_worker_runner
102
+ )
112
103
  end
113
104
 
114
105
  def shutdown!
@@ -116,7 +107,7 @@ module Ductwork
116
107
  job_workers.each(&:stop)
117
108
  await_threads_graceful_shutdown
118
109
  kill_remaining_job_workers
119
- delete_process_record!
110
+ reap_process_record!
120
111
  end
121
112
 
122
113
  def await_threads_graceful_shutdown
@@ -152,12 +143,9 @@ module Ductwork
152
143
  end
153
144
  end
154
145
 
155
- def delete_process_record!
146
+ def reap_process_record!
156
147
  Ductwork.wrap_with_app_executor do
157
- Ductwork::Process.find_by!(
158
- pid: ::Process.pid,
159
- machine_identifier: Ductwork::MachineIdentifier.fetch
160
- ).delete
148
+ Ductwork::Process.current&.reap!(:job_worker_runner, force: true)
161
149
  end
162
150
  end
163
151
  end
@@ -3,7 +3,7 @@
3
3
  module Ductwork
4
4
  module Processes
5
5
  class PipelineAdvancer
6
- attr_reader :thread, :last_heartbeat_at, :pipeline
6
+ attr_reader :thread, :last_heartbeat_at, :branch
7
7
 
8
8
  def initialize(klass, index = nil)
9
9
  @klass = klass
@@ -18,12 +18,19 @@ module Ductwork
18
18
  @thread.name = name
19
19
  end
20
20
 
21
- alias restart start
21
+ def restart
22
+ cleanup_dead_thread!
23
+ start
24
+ end
22
25
 
23
26
  def alive?
24
27
  thread&.alive? || false
25
28
  end
26
29
 
30
+ def stuck?
31
+ branch.nil? && last_heartbeat_too_long_ago?
32
+ end
33
+
27
34
  def stop
28
35
  running_context.shutdown!
29
36
  end
@@ -43,9 +50,9 @@ module Ductwork
43
50
 
44
51
  private
45
52
 
46
- attr_reader :klass, :index, :running_context
53
+ attr_reader :klass, :index, :running_context, :original_claim_token
47
54
 
48
- def work_loop # rubocop:todo Metrics
55
+ def work_loop
49
56
  run_hooks_for(:start)
50
57
 
51
58
  Ductwork.logger.debug(
@@ -55,85 +62,27 @@ module Ductwork
55
62
  )
56
63
 
57
64
  while running_context.running?
58
- id = Ductwork.wrap_with_app_executor do
59
- Ductwork::Pipeline
60
- .in_progress
61
- .where(klass: klass, claimed_for_advancing_at: nil)
62
- .where(steps: Ductwork::Step.where(status: :advancing))
63
- .where.not(steps: Ductwork::Step.where.not(status: %w[advancing completed]))
64
- .order(:last_advanced_at)
65
- .limit(1)
66
- .pluck(:id)
67
- .first
68
- end
65
+ claimed = false
69
66
 
70
- if id.present?
71
- rows_updated = Ductwork.wrap_with_app_executor do
72
- Ductwork::Pipeline
73
- .where(id: id, claimed_for_advancing_at: nil)
74
- .update_all(
75
- claimed_for_advancing_at: Time.current,
76
- status: "advancing"
77
- )
78
- end
67
+ Ductwork.wrap_with_app_executor do
68
+ claimed = Branch.with_latest_claimed(klass) do |branch, transition, advancement|
69
+ @branch = branch
70
+ @original_claim_token = branch.claim_token
71
+
72
+ branch.advance!(transition, advancement)
79
73
 
80
- if rows_updated == 1
81
- Ductwork.logger.debug(
82
- msg: "Pipeline claimed",
83
- pipeline_id: id,
84
- pipeline: klass,
85
- role: :pipeline_advancer
86
- )
87
-
88
- @pipeline = Ductwork.wrap_with_app_executor do
89
- @pipeline = Ductwork::Pipeline.find(id)
90
- pipeline.advance!
91
-
92
- Ductwork.logger.debug(
93
- msg: "Pipeline advanced",
94
- pipeline_id: id,
95
- pipeline: klass,
96
- role: :pipeline_advancer
97
- )
98
-
99
- # rubocop:todo Metrics/BlockNesting
100
- status = if pipeline.completed?
101
- "completed"
102
- elsif pipeline.dampened?
103
- "dampened"
104
- else
105
- "in_progress"
106
- end
107
- # rubocop:enable Metrics/BlockNesting
108
- ensure
109
- # release the pipeline and set last advanced at so it doesn't
110
- # block. we're not using a queue so we have to use a db
111
- # timestamp
112
- pipeline.update!(
113
- claimed_for_advancing_at: nil,
114
- last_advanced_at: Time.current,
115
- status: status || "in_progress"
116
- )
117
- end
118
- else
119
- Ductwork.logger.debug(
120
- msg: "Did not claim pipeline, avoided race condition",
121
- pipeline_id: id,
122
- pipeline: klass,
123
- role: :pipeline_advancer
124
- )
74
+ Ductwork::FaultInjection.checkpoint(:after_branch_advancement)
75
+ ensure
76
+ @branch = nil
77
+ @original_claim_token = nil
125
78
  end
126
- else
127
- Ductwork.logger.debug(
128
- msg: "No pipeline needs advancing",
129
- pipeline: klass,
130
- role: :pipeline_advancer
131
- )
132
79
  end
133
80
 
134
81
  @last_heartbeat_at = Time.current
135
82
 
136
- sleep(polling_timeout)
83
+ if !claimed
84
+ sleep(polling_timeout)
85
+ end
137
86
  end
138
87
 
139
88
  Ductwork.logger.debug(
@@ -145,6 +94,29 @@ module Ductwork
145
94
  run_hooks_for(:stop)
146
95
  end
147
96
 
97
+ def cleanup_dead_thread!
98
+ if branch.present?
99
+ advancement = branch
100
+ .transitions
101
+ .joins(:advancements)
102
+ .where(ductwork_advancements: { completed_at: nil })
103
+ .first
104
+ &.advancements
105
+ &.find_by(completed_at: nil)
106
+
107
+ advancement&.thread_crashed!(original_claim_token)
108
+ end
109
+ ensure
110
+ @branch = nil
111
+ @original_claim_token = nil
112
+ end
113
+
114
+ def last_heartbeat_too_long_ago?
115
+ threshold = Ductwork.configuration.supervisor_reaper_timeout
116
+
117
+ (Time.current - last_heartbeat_at) > threshold
118
+ end
119
+
148
120
  def run_hooks_for(event)
149
121
  Ductwork.hooks[:advancer].fetch(event, []).each do |block|
150
122
  Ductwork.wrap_with_app_executor do
@@ -24,7 +24,7 @@ module Ductwork
24
24
  end
25
25
 
26
26
  def run
27
- create_process!
27
+ adopt_or_create_process!
28
28
  start_pipeline_advancers
29
29
 
30
30
  Ductwork.logger.debug(
@@ -36,7 +36,7 @@ module Ductwork
36
36
  while running_context.running?
37
37
  # TODO: Increase or make configurable
38
38
  sleep(5)
39
- check_thread_health
39
+ check_workers_health
40
40
  report_heartbeat!
41
41
  end
42
42
 
@@ -49,60 +49,53 @@ module Ductwork
49
49
 
50
50
  def start_pipeline_advancers
51
51
  klasses.each do |klass|
52
- advancer = Ductwork::Processes::PipelineAdvancer.new(klass)
53
- advancers.push(advancer)
54
- advancer.start
55
-
56
- Ductwork.logger.debug(
57
- msg: "Created new pipeline advancer",
58
- role: :pipeline_advancer_runner,
59
- pipeline: klass,
60
- thread: advancer.name
61
- )
62
- end
63
- end
52
+ Ductwork.configuration.pipeline_advancer_count(klass).times do |index|
53
+ advancer = Ductwork::Processes::PipelineAdvancer.new(klass, index)
54
+ advancers.push(advancer)
55
+ advancer.start
64
56
 
65
- def check_thread_health
66
- Ductwork.logger.debug(
67
- msg: "Checking threads health",
68
- role: :pipeline_advancer_runner,
69
- pipelines: klasses
70
- )
71
- advancers.each do |advancer|
72
- if !advancer.alive?
73
- advancer.restart
74
-
75
- Ductwork.logger.warn(
76
- msg: "Restarted pipeline advancer",
57
+ Ductwork.logger.debug(
58
+ msg: "Created new pipeline advancer",
77
59
  role: :pipeline_advancer_runner,
78
- pipeline: advancer.pipeline.class.to_s,
60
+ pipeline: klass,
79
61
  thread: advancer.name
80
62
  )
81
63
  end
82
64
  end
83
- Ductwork.logger.debug(
84
- msg: "Checked thread health",
85
- role: :pipeline_advancer_runner,
86
- pipelines: klasses
65
+ end
66
+
67
+ def check_workers_health
68
+ Ductwork::Processes::WorkerHealthCheck
69
+ .new(advancers, :pipeline_advancer_runner)
70
+ .check
71
+ rescue StandardError => e
72
+ Ductwork.logger.warn(
73
+ msg: "Checking worker health failed",
74
+ error_klass: e.class.to_s,
75
+ error_message: e.message,
76
+ role: :pipeline_advancer_runner
87
77
  )
88
78
  end
89
79
 
90
- def create_process!
80
+ def adopt_or_create_process!
91
81
  Ductwork.wrap_with_app_executor do
92
- Ductwork::Process.create!(
93
- pid: ::Process.pid,
94
- machine_identifier: Ductwork::MachineIdentifier.fetch,
95
- last_heartbeat_at: Time.current
96
- )
82
+ Ductwork::Process.adopt_or_create_current!(:pipeline_advancer)
97
83
  end
98
84
  end
99
85
 
100
86
  def report_heartbeat!
101
87
  Ductwork.logger.debug(msg: "Reporting heartbeat", role: :pipeline_advancer_runner)
102
88
  Ductwork.wrap_with_app_executor do
103
- Ductwork::Process.report_heartbeat!
89
+ Ductwork::Process.report_heartbeat!(:pipeline_advancer)
104
90
  end
105
91
  Ductwork.logger.debug(msg: "Reported heartbeat", role: :pipeline_advancer_runner)
92
+ rescue StandardError => e
93
+ Ductwork.logger.warn(
94
+ msg: "Reporting heartbeat failed",
95
+ error_klass: e.class.to_s,
96
+ error_message: e.message,
97
+ role: :pipeline_advancer_runner
98
+ )
106
99
  end
107
100
 
108
101
  def shutdown!
@@ -111,7 +104,7 @@ module Ductwork
111
104
  advancers.each(&:stop)
112
105
  await_threads_graceful_shutdown
113
106
  kill_remaining_threads
114
- delete_process!
107
+ reap_process_record!
115
108
  end
116
109
 
117
110
  def log_shutting_down
@@ -154,12 +147,9 @@ module Ductwork
154
147
  end
155
148
  end
156
149
 
157
- def delete_process!
150
+ def reap_process_record!
158
151
  Ductwork.wrap_with_app_executor do
159
- Ductwork::Process.find_by!(
160
- pid: ::Process.pid,
161
- machine_identifier: Ductwork::MachineIdentifier.fetch
162
- ).delete
152
+ Ductwork::Process.current&.reap!(:pipeline_advancer_runner, force: true)
163
153
  end
164
154
  end
165
155
  end