good_job 4.13.1 → 4.13.2

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: 3407384b57488071d4631ea643e46bd06c1d7a01d2edb9655a1e65be8d59a62d
4
- data.tar.gz: 322a40ebccd3081455d7ebefa16811b0a5a745dc3c49090fff422b1fa91593c0
3
+ metadata.gz: 3274686b09e03b2ec9b5d58f3b8bf70472e30c739b907697e7ad1f2ad7ce6b50
4
+ data.tar.gz: 0f2ee28df948382eb23cffd0535545462490737c62c3d9133be5afb7ef3cfc52
5
5
  SHA512:
6
- metadata.gz: a77366c7a3fa599505a8d25b1bf56c43633bcec29c68ce58d7bd787dee71813ffa51ff784d6f1ad46e9d8bb5d79029e4ca321e238e8e1b468b29aabbb89f3392
7
- data.tar.gz: af3dd4540b43b8fb47c8eb7c686e9d67e0942d17a6299dac06887c3b65072844654cdaeaae30d873c1b1c6ecde39a52b25d31a9eb1898c92fee51521e08d064c
6
+ metadata.gz: bea1412da3fc1d276688733cd2d5aa622939211b8cc4cbdaa52a0bc5543ded93f5350be14d0cdbed987b988cc9b205dd0dc144b209d7ecc5ced25dad26221ec7
7
+ data.tar.gz: 61240a584b06c7871f815d123a78a9c3066a8a739a49f2a9f403bdfcf255f97e691ad5fd5754a301f7b7e5c66a3cb09161d312348c68a022a958d5e5cbba27e1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [v4.13.2](https://github.com/bensheldon/good_job/tree/v4.13.2) (2026-01-29)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.13.1...v4.13.2)
6
+
7
+ **Fixed bugs:**
8
+
9
+ - Check for graceful shutdown inside job cleanup loops [\#1711](https://github.com/bensheldon/good_job/pull/1711) ([bdewater-thatch](https://github.com/bdewater-thatch))
10
+ - Add title to Good Job Dashboard layout [\#1701](https://github.com/bensheldon/good_job/pull/1701) ([mockdeep](https://github.com/mockdeep))
11
+
12
+ **Closed issues:**
13
+
14
+ - Test failures against rails-head [\#1704](https://github.com/bensheldon/good_job/issues/1704)
15
+
16
+ **Merged pull requests:**
17
+
18
+ - Add test for Migration generator with custom migration path [\#1709](https://github.com/bensheldon/good_job/pull/1709) ([bensheldon](https://github.com/bensheldon))
19
+ - Support Rails 8.2-dev `enqueue\_after\_transaction\_commit by deferring framework enqueuing preemptively [\#1707](https://github.com/bensheldon/good_job/pull/1707) ([bensheldon](https://github.com/bensheldon))
20
+ - Add Ruby 4.0 to CI [\#1705](https://github.com/bensheldon/good_job/pull/1705) ([Earlopain](https://github.com/Earlopain))
21
+ - Bump actions/upload-artifact from 5 to 6 [\#1703](https://github.com/bensheldon/good_job/pull/1703) ([dependabot[bot]](https://github.com/apps/dependabot))
22
+
3
23
  ## [v4.13.1](https://github.com/bensheldon/good_job/tree/v4.13.1) (2025-12-16)
4
24
 
5
25
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.13.0...v4.13.1)
@@ -151,8 +151,12 @@ module GoodJob
151
151
  update_attributes = { discarded_at: nil, finished_at: nil }
152
152
  update_attributes[:jobs_finished_at] = nil if GoodJob::BatchRecord.jobs_finished_at_migrated?
153
153
  record.update!(update_attributes)
154
- record.jobs.discarded.each(&:retry_job)
155
- record._continue_discard_or_finish(lock: false)
154
+
155
+ discarded_jobs = record.jobs.discarded
156
+ Job.defer_after_commit_maybe(discarded_jobs) do
157
+ discarded_jobs.each(&:retry_job)
158
+ record._continue_discard_or_finish(lock: false)
159
+ end
156
160
  end
157
161
  end
158
162
  end
@@ -59,25 +59,47 @@ module GoodJob
59
59
  job_discarded = job && job.finished_at.present? && job.error.present?
60
60
  buffer = GoodJob::Adapter::InlineBuffer.capture do
61
61
  advisory_lock_maybe(lock) do
62
- Batch.within_thread(batch_id: nil, batch_callback_id: id) do
63
- reload
64
-
65
- if job_discarded && !discarded_at
66
- update(discarded_at: Time.current)
67
- on_discard.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :discard }) if on_discard.present?
62
+ reload
63
+
64
+ if job_discarded && !discarded_at
65
+ update(discarded_at: Time.current)
66
+
67
+ if on_discard.present?
68
+ discard_job_class = on_discard.constantize
69
+ Job.defer_after_commit_maybe(discard_job_class) do
70
+ Batch.within_thread(batch_id: nil, batch_callback_id: id) do
71
+ discard_job_class.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :discard })
72
+ end
73
+ end
68
74
  end
75
+ end
69
76
 
70
- if enqueued_at && !(self.class.jobs_finished_at_migrated? ? jobs_finished_at : finished_at) && jobs.where(finished_at: nil).none?
71
- self.class.jobs_finished_at_migrated? ? update(jobs_finished_at: Time.current) : update(finished_at: Time.current)
77
+ if enqueued_at && !(self.class.jobs_finished_at_migrated? ? jobs_finished_at : finished_at) && jobs.where(finished_at: nil).none?
78
+ self.class.jobs_finished_at_migrated? ? update(jobs_finished_at: Time.current) : update(finished_at: Time.current)
72
79
 
73
- on_success.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :success }) if !discarded_at && on_success.present?
74
- on_finish.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :finish }) if on_finish.present?
80
+ if !discarded_at && on_success.present?
81
+ success_job_class = on_success.constantize
82
+ Job.defer_after_commit_maybe(success_job_class) do
83
+ Batch.within_thread(batch_id: nil, batch_callback_id: id) do
84
+ success_job_class.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :success })
85
+ end
86
+ end
75
87
  end
76
88
 
77
- update(finished_at: Time.current) if !finished_at && self.class.jobs_finished_at_migrated? && jobs_finished? && callback_jobs.where(finished_at: nil).none?
89
+ if on_finish.present?
90
+ finish_job_class = on_finish.constantize
91
+ Job.defer_after_commit_maybe(finish_job_class) do
92
+ Batch.within_thread(batch_id: nil, batch_callback_id: id) do
93
+ on_finish.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :finish })
94
+ end
95
+ end
96
+ end
78
97
  end
98
+
99
+ update(finished_at: Time.current) if !finished_at && self.class.jobs_finished_at_migrated? && jobs_finished? && callback_jobs.where(finished_at: nil).none?
79
100
  end
80
101
  end
102
+
81
103
  buffer.call
82
104
  end
83
105
 
@@ -420,6 +420,33 @@ module GoodJob
420
420
  [error.class.to_s, ERROR_MESSAGE_SEPARATOR, error.message].join
421
421
  end
422
422
 
423
+ # When code needs to optionally handle enqueue_after_transaction_commit
424
+ def self.defer_after_commit_maybe(good_job_or_active_job_classes)
425
+ if enqueue_after_commit?(good_job_or_active_job_classes)
426
+ ActiveRecord.after_all_transactions_commit { yield(true) }
427
+ else
428
+ yield(false)
429
+ end
430
+ end
431
+
432
+ def self.enqueue_after_commit?(good_job_or_active_job_classes)
433
+ good_job_or_active_job_classes = Array(good_job_or_active_job_classes)
434
+
435
+ feature_exists = ActiveRecord.respond_to?(:after_all_transactions_commit)
436
+ feature_exists && good_job_or_active_job_classes.any? do |klass|
437
+ active_job_class = case klass
438
+ when String
439
+ klass.constantize
440
+ when Job
441
+ klass.job_class.constantize
442
+ else
443
+ klass
444
+ end
445
+
446
+ active_job_class.respond_to?(:enqueue_after_transaction_commit)
447
+ end
448
+ end
449
+
423
450
  # TODO: it would be nice to enforce these values at the model
424
451
  # validates :active_job_id, presence: true
425
452
  # validates :scheduled_at, presence: true
@@ -499,41 +526,46 @@ module GoodJob
499
526
  # This action will create a new {Execution} record for the job.
500
527
  # @return [ActiveJob::Base]
501
528
  def retry_job
502
- with_advisory_lock do
503
- reload
504
- active_job = self.active_job(ignore_deserialization_errors: true)
505
-
506
- raise ActiveJobDeserializationError if active_job.nil?
507
- raise AdapterNotGoodJobError unless active_job.class.queue_adapter.is_a? GoodJob::Adapter
508
- raise ActionForStateMismatchError if finished_at.blank? || error.blank?
509
-
510
- # Update the executions count because the previous execution will not have been preserved
511
- # Do not update `exception_executions` because that comes from rescue_from's arguments
512
- active_job.executions = (active_job.executions || 0) + 1
513
-
514
- begin
515
- error_class, error_message = error.split(ERROR_MESSAGE_SEPARATOR).map(&:strip)
516
- error = error_class.constantize.new(error_message)
517
- rescue StandardError
518
- error = StandardError.new(error)
519
- end
529
+ Rails.application.executor.wrap do
530
+ with_advisory_lock do
531
+ reload
532
+ active_job = self.active_job(ignore_deserialization_errors: true)
533
+
534
+ raise ActiveJobDeserializationError if active_job.nil?
535
+ raise AdapterNotGoodJobError unless active_job.class.queue_adapter.is_a? GoodJob::Adapter
536
+ raise ActionForStateMismatchError if finished_at.blank? || error.blank?
537
+
538
+ # Update the executions count because the previous execution will not have been preserved
539
+ # Do not update `exception_executions` because that comes from rescue_from's arguments
540
+ active_job.executions = (active_job.executions || 0) + 1
541
+
542
+ begin
543
+ error_class, error_message = error.split(ERROR_MESSAGE_SEPARATOR).map(&:strip)
544
+ error = error_class.constantize.new(error_message)
545
+ rescue StandardError
546
+ error = StandardError.new(error)
547
+ end
520
548
 
521
- new_active_job = nil
522
- GoodJob::CurrentThread.within do |current_thread|
523
- current_thread.job = self
524
- current_thread.retry_now = true
549
+ new_active_job = nil
525
550
 
526
551
  transaction do
527
- # NOTE: Required until fixed in rails https://github.com/rails/rails/pull/52121
528
- I18n.with_locale(active_job.locale) do
529
- new_active_job = active_job.retry_job(wait: 0, error: error)
552
+ Job.defer_after_commit_maybe(active_job.class) do
553
+ GoodJob::CurrentThread.within do |current_thread|
554
+ current_thread.job = self
555
+ current_thread.retry_now = true
556
+
557
+ # NOTE: I18n.with_locale necessary until fixed in rails https://github.com/rails/rails/pull/52121
558
+ I18n.with_locale(active_job.locale) do
559
+ new_active_job = active_job.retry_job(wait: 0, error: error)
560
+ end
561
+ end
530
562
  end
531
563
  self.error_event = :retried if error
532
564
  save!
533
565
  end
534
- end
535
566
 
536
- new_active_job
567
+ new_active_job
568
+ end
537
569
  end
538
570
  end
539
571
 
@@ -32,6 +32,8 @@
32
32
 
33
33
  <%= tag.script "import \"application\";".html_safe, type: "module", nonce: content_security_policy_nonce %>
34
34
 
35
+ <title>Good Job Dashboard</title>
36
+ <%= tag.link rel: "icon", href: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="10 0 100 100"><text y=".90em" font-size="90">👍</text></svg>' %>
35
37
  <%= csrf_meta_tags %>
36
38
  <%= csp_meta_tag %>
37
39
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GoodJob
4
4
  # GoodJob gem version.
5
- VERSION = '4.13.1'
5
+ VERSION = '4.13.2'
6
6
 
7
7
  # GoodJob version as Gem::Version object
8
8
  GEM_VERSION = Gem::Version.new(VERSION)
data/lib/good_job.rb CHANGED
@@ -217,6 +217,8 @@ module GoodJob
217
217
  jobs_query = GoodJob::Job.finished_before(timestamp).order(finished_at: :asc).limit(in_batches_of)
218
218
  jobs_query = jobs_query.succeeded unless include_discarded
219
219
  loop do
220
+ break if GoodJob.current_thread_shutting_down?
221
+
220
222
  active_job_ids = jobs_query.pluck(:active_job_id)
221
223
  break if active_job_ids.empty?
222
224
 
@@ -230,6 +232,8 @@ module GoodJob
230
232
  batches_query = GoodJob::BatchRecord.finished_before(timestamp).limit(in_batches_of)
231
233
  batches_query = batches_query.succeeded unless include_discarded
232
234
  loop do
235
+ break if GoodJob.current_thread_shutting_down?
236
+
233
237
  deleted = batches_query.delete_all
234
238
  break if deleted.zero?
235
239
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.13.1
4
+ version: 4.13.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
@@ -426,7 +426,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
426
426
  - !ruby/object:Gem::Version
427
427
  version: '0'
428
428
  requirements: []
429
- rubygems_version: 3.6.9
429
+ rubygems_version: 4.0.2
430
430
  specification_version: 4
431
431
  summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
432
432
  test_files: []