good_job 4.2.1 → 4.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f21cb4322491fa83f330857033b5c1c0524509d57a93d795d3e4c7ce064e676c
4
- data.tar.gz: '05082cbbd0fd4a26587ac53a58d7042c546390505e68fb978ef675bdfe6a0fa6'
3
+ metadata.gz: ec5d4c89f52824a8023774ca2179f67579c21ae7772737a367ee912bc58db9bd
4
+ data.tar.gz: f8526687220d9bd8ec7222f897f2302ab7d42f8883627c43195390b7ab9cf0be
5
5
  SHA512:
6
- metadata.gz: 825bed536a7ee957773981cc926f04dad400e0f2a770e5749a18511927b3c09a4130370a5ee0d88e984bf8ed3a09cce53bd0285bc1066c1e4a837835ee44ab21
7
- data.tar.gz: 04371b7bb7fb075dc5d11817ff942443609bfd33cd7112ba35c9617447ae520035d7fcfafff127e170b77dba4d258af5ebc54448c9d20c07cd1516d584076fdb
6
+ metadata.gz: 3760a509091f29ca67d90366b06940cb4b12428bbe99a5fd451315ec9895291d2083168603f1f81a3b87ccf1a6df9bcf657d9c89a811768436fdd6a80aa60737
7
+ data.tar.gz: 9ca52b976c7399f386e141c7ea6d54bfbf3b1b8036ef6f1ac37cc24dede46966ab7644c1c8a5bcde491c3a768862fd1edbd66b7054e06a0e6955e5f7b3d8245f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [v4.3.0](https://github.com/bensheldon/good_job/tree/v4.3.0) (2024-09-14)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.2.1...v4.3.0)
6
+
7
+ **Fixed bugs:**
8
+
9
+ - Don't shadow gem loading errors during autoloading with the inline adapter [\#1486](https://github.com/bensheldon/good_job/pull/1486) ([Earlopain](https://github.com/Earlopain))
10
+ - Ensure before actions run on root route [\#1482](https://github.com/bensheldon/good_job/pull/1482) ([ebiven](https://github.com/ebiven))
11
+
12
+ **Closed issues:**
13
+
14
+ - Undefined method `unhandled_error` for nil [\#1485](https://github.com/bensheldon/good_job/issues/1485)
15
+ - bin/rails g good\_job:install fails [\#1481](https://github.com/bensheldon/good_job/issues/1481)
16
+ - Rails 7.2.1 breaks enqueuing without enqueue\_after\_transaction\_commit? method on queue adapter [\#1477](https://github.com/bensheldon/good_job/issues/1477)
17
+
18
+ **Merged pull requests:**
19
+
20
+ - Fix documentation for 2nd Batch callback parameter: consistently call it `context` [\#1476](https://github.com/bensheldon/good_job/pull/1476) ([martijnversluis](https://github.com/martijnversluis))
21
+ - Redefine `Batch#finished_at` to mean all callback jobs have finished too; add `Batch#jobs_finished_at` to allow not deleting batches until all their callback jobs complete [\#1454](https://github.com/bensheldon/good_job/pull/1454) ([bensheldon](https://github.com/bensheldon))
22
+
3
23
  ## [v4.2.1](https://github.com/bensheldon/good_job/tree/v4.2.1) (2024-08-29)
4
24
 
5
25
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.2.0...v4.2.1)
data/README.md CHANGED
@@ -681,15 +681,15 @@ Batches track a set of jobs, and enqueue an optional callback job when all of th
681
681
  OtherJob.perform_later
682
682
  end
683
683
 
684
- # When these jobs have finished, it will enqueue your `MyBatchCallbackJob.perform_later(batch, options)`
684
+ # When these jobs have finished, it will enqueue your `MyBatchCallbackJob.perform_later(batch, context)`
685
685
  class MyBatchCallbackJob < ApplicationJob
686
- # Callback jobs must accept a `batch` and `options` argument
687
- def perform(batch, params)
686
+ # Callback jobs must accept a `batch` and `context` argument
687
+ def perform(batch, context)
688
688
  # The batch object will contain the Batch's properties, which are mutable
689
689
  batch.properties[:user] # => <User id: 1, ...>
690
690
 
691
- # Params is a hash containing additional context (more may be added in the future)
692
- params[:event] # => :finish, :success, :discard
691
+ # Context is a hash containing additional context (more may be added in the future)
692
+ context[:event] # => :finish, :success, :discard
693
693
  end
694
694
  end
695
695
  ```
@@ -757,19 +757,19 @@ Batch callbacks are Active Job jobs that are enqueued at certain events during t
757
757
  - `:success` - Enqueued only when all jobs in the batch have finished and succeeded.
758
758
  - `:discard` - Enqueued immediately the first time a job in the batch is discarded.
759
759
 
760
- Callback jobs must accept a `batch` and `params` argument in their `perform` method:
760
+ Callback jobs must accept a `batch` and `context` argument in their `perform` method:
761
761
 
762
762
  ```ruby
763
763
  class MyBatchCallbackJob < ApplicationJob
764
- def perform(batch, params)
764
+ def perform(batch, context)
765
765
  # The batch object will contain the Batch's properties
766
766
  batch.properties[:user] # => <User id: 1, ...>
767
767
  # Batches are mutable
768
768
  batch.properties[:user] = User.find(2)
769
769
  batch.save
770
770
 
771
- # Params is a hash containing additional context (more may be added in the future)
772
- params[:event] # => :finish, :success, :discard
771
+ # Context is a hash containing additional context (more may be added in the future)
772
+ context[:event] # => :finish, :success, :discard
773
773
  end
774
774
  end
775
775
  ```
@@ -811,7 +811,7 @@ class WorkJob < ApplicationJob
811
811
  end
812
812
 
813
813
  class BatchJob < ApplicationJob
814
- def perform(batch, options)
814
+ def perform(batch, context)
815
815
  if batch.properties[:stage].nil?
816
816
  batch.enqueue(stage: 1) do
817
817
  WorkJob.perform_later('a')
@@ -94,6 +94,12 @@ module GoodJob
94
94
  redirect_to jobs_path, notice: t(".notice")
95
95
  end
96
96
 
97
+ def redirect_to_index
98
+ # Redirect to the jobs page, maintaining query parameters. This is
99
+ # necessary to support the `?poll=1` parameter that enables live polling.
100
+ redirect_to jobs_path(request.query_parameters)
101
+ end
102
+
97
103
  private
98
104
 
99
105
  def redirect_on_error(exception)
@@ -26,10 +26,12 @@ module GoodJob
26
26
  :enqueued_at,
27
27
  :finished_at,
28
28
  :discarded_at,
29
+ :jobs_finished_at,
29
30
  :enqueued?,
30
31
  :finished?,
31
32
  :succeeded?,
32
33
  :discarded?,
34
+ :jobs_finished?,
33
35
  :description,
34
36
  :description=,
35
37
  :on_finish,
@@ -95,8 +97,12 @@ module GoodJob
95
97
  record.transaction do
96
98
  record.with_advisory_lock(function: "pg_advisory_xact_lock") do
97
99
  record.enqueued_at_will_change!
100
+ record.jobs_finished_at_will_change! if GoodJob::BatchRecord.jobs_finished_at_migrated?
98
101
  record.finished_at_will_change!
99
- record.update!(enqueued_at: nil, finished_at: nil)
102
+
103
+ update_attributes = { discarded_at: nil, finished_at: nil }
104
+ update_attributes[:jobs_finished_at] = nil if GoodJob::BatchRecord.jobs_finished_at_migrated?
105
+ record.update!(**update_attributes)
100
106
  end
101
107
  end
102
108
  end
@@ -142,7 +148,9 @@ module GoodJob
142
148
  buffer = GoodJob::Adapter::InlineBuffer.capture do
143
149
  record.transaction do
144
150
  record.with_advisory_lock(function: "pg_advisory_xact_lock") do
145
- record.update!(discarded_at: nil, finished_at: nil)
151
+ update_attributes = { discarded_at: nil, finished_at: nil }
152
+ update_attributes[:jobs_finished_at] = nil if GoodJob::BatchRecord.jobs_finished_at_migrated?
153
+ record.update!(update_attributes)
146
154
  record.jobs.discarded.each(&:retry_job)
147
155
  record._continue_discard_or_finish(lock: false)
148
156
  end
@@ -38,6 +38,10 @@ module GoodJob
38
38
  query
39
39
  end)
40
40
 
41
+ def self.jobs_finished_at_migrated?
42
+ column_names.include?('jobs_finished_at')
43
+ end
44
+
41
45
  # Whether the batch has finished and no jobs were discarded
42
46
  # @return [Boolean]
43
47
  def succeeded?
@@ -52,22 +56,26 @@ module GoodJob
52
56
  attributes.except('serialized_properties').merge(properties: properties)
53
57
  end
54
58
 
55
- def _continue_discard_or_finish(execution = nil, lock: true)
56
- execution_discarded = execution && execution.finished_at.present? && execution.error.present?
59
+ def _continue_discard_or_finish(job = nil, lock: true)
60
+ job_discarded = job && job.finished_at.present? && job.error.present?
57
61
  buffer = GoodJob::Adapter::InlineBuffer.capture do
58
62
  advisory_lock_maybe(lock) do
59
63
  Batch.within_thread(batch_id: nil, batch_callback_id: id) do
60
64
  reload
61
- if execution_discarded && !discarded_at
65
+
66
+ if job_discarded && !discarded_at
62
67
  update(discarded_at: Time.current)
63
68
  on_discard.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :discard }) if on_discard.present?
64
69
  end
65
70
 
66
- if enqueued_at && !finished_at && jobs.where(finished_at: nil).count.zero?
67
- update(finished_at: Time.current)
71
+ if enqueued_at && !(self.class.jobs_finished_at_migrated? ? jobs_finished_at : finished_at) && jobs.where(finished_at: nil).count.zero?
72
+ self.class.jobs_finished_at_migrated? ? update(jobs_finished_at: Time.current) : update(finished_at: Time.current)
73
+
68
74
  on_success.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :success }) if !discarded_at && on_success.present?
69
75
  on_finish.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :finish }) if on_finish.present?
70
76
  end
77
+
78
+ update(finished_at: Time.current) if !finished_at && self.class.jobs_finished_at_migrated? && jobs_finished? && callback_jobs.where(finished_at: nil).count.zero?
71
79
  end
72
80
  end
73
81
  end
@@ -97,6 +105,14 @@ module GoodJob
97
105
  self.serialized_properties = value
98
106
  end
99
107
 
108
+ def jobs_finished?
109
+ self.class.jobs_finished_at_migrated? ? jobs_finished_at : finished_at
110
+ end
111
+
112
+ def jobs_finished_at
113
+ self.class.jobs_finished_at_migrated? ? self[:jobs_finished_at] : self[:finished_at]
114
+ end
115
+
100
116
  private
101
117
 
102
118
  def advisory_lock_maybe(value, &block)
@@ -40,6 +40,7 @@ module GoodJob
40
40
  set_callback :perform_unlocked, :after, :continue_discard_or_finish_batch
41
41
 
42
42
  belongs_to :batch, class_name: 'GoodJob::BatchRecord', inverse_of: :jobs, optional: true
43
+ belongs_to :callback_batch, class_name: 'GoodJob::BatchRecord', foreign_key: :batch_callback_id, inverse_of: :callback_jobs, optional: true
43
44
  belongs_to :locked_by_process, class_name: "GoodJob::Process", foreign_key: :locked_by_id, inverse_of: :locked_jobs, optional: true
44
45
  has_many :executions, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id', primary_key: "id", inverse_of: :job, dependent: :delete_all
45
46
 
@@ -743,6 +744,7 @@ module GoodJob
743
744
 
744
745
  def continue_discard_or_finish_batch
745
746
  batch._continue_discard_or_finish(self) if batch.present?
747
+ callback_batch._continue_discard_or_finish if callback_batch.present?
746
748
  end
747
749
 
748
750
  def active_job_data
data/config/routes.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  GoodJob::Engine.routes.draw do
4
- root to: redirect(path: 'jobs')
4
+ root 'jobs#redirect_to_index'
5
5
 
6
6
  resources :jobs, only: %i[index show destroy] do
7
7
  collection do
@@ -46,6 +46,7 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
46
46
  t.datetime :enqueued_at
47
47
  t.datetime :discarded_at
48
48
  t.datetime :finished_at
49
+ t.datetime :jobs_finished_at
49
50
  end
50
51
 
51
52
  create_table :good_job_executions, id: :uuid do |t|
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddJobsFinishedAtToGoodJobBatches < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ reversible do |dir|
6
+ dir.up do
7
+ # Ensure this incremental update migration is idempotent
8
+ # with monolithic install migration.
9
+ return if connection.column_exists?(:good_job_batches, :jobs_finished_at)
10
+ end
11
+ end
12
+
13
+ change_table :good_job_batches do |t|
14
+ t.datetime :jobs_finished_at
15
+ end
16
+ end
17
+ end
@@ -254,7 +254,7 @@ module GoodJob
254
254
  job.advisory_unlock
255
255
  job.run_callbacks(:perform_unlocked)
256
256
 
257
- raise result.unhandled_error if result.unhandled_error
257
+ raise result.unhandled_error if result&.unhandled_error
258
258
  end
259
259
  end
260
260
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GoodJob
4
4
  # GoodJob gem version.
5
- VERSION = '4.2.1'
5
+ VERSION = '4.3.0'
6
6
 
7
7
  # GoodJob version as Gem::Version object
8
8
  GEM_VERSION = Gem::Version.new(VERSION)
data/lib/good_job.rb CHANGED
@@ -171,10 +171,10 @@ module GoodJob
171
171
  _shutdown_all(Capsule.instances, :restart, timeout: timeout)
172
172
  end
173
173
 
174
- # Sends +#shutdown+ or +#restart+ to executable objects ({GoodJob::Notifier}, {GoodJob::Poller}, {GoodJob::Scheduler}, {GoodJob::MultiScheduler}, {GoodJob::CronManager})
174
+ # Sends +#shutdown+ or +#restart+ to executable objects ({GoodJob::Notifier}, {GoodJob::Poller}, {GoodJob::Scheduler}, {GoodJob::MultiScheduler}, {GoodJob::CronManager}, {GoodJob::SharedExecutor})
175
175
  # @param executables [Array<Notifier, Poller, Scheduler, MultiScheduler, CronManager, SharedExecutor>] Objects to shut down.
176
- # @param method_name [:symbol] Method to call, e.g. +:shutdown+ or +:restart+.
177
- # @param timeout [nil,Numeric]
176
+ # @param method_name [Symbol] Method to call, e.g. +:shutdown+ or +:restart+.
177
+ # @param timeout [nil, Numeric] Seconds to wait for actively executing jobs to finish.
178
178
  # @param after [Array<Notifier, Poller, Scheduler, MultiScheduler, CronManager, SharedExecutor>] Objects to shut down after initial executables shut down.
179
179
  # @return [void]
180
180
  def self._shutdown_all(executables, method_name = :shutdown, timeout: -1, after: [])
@@ -290,7 +290,7 @@ module GoodJob
290
290
  # For use in tests/CI to validate GoodJob is up-to-date.
291
291
  # @return [Boolean]
292
292
  def self.migrated?
293
- true
293
+ GoodJob::BatchRecord.jobs_finished_at_migrated?
294
294
  end
295
295
  end
296
296
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.1
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-29 00:00:00.000000000 Z
11
+ date: 2024-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -340,6 +340,7 @@ files:
340
340
  - lib/generators/good_job/install_generator.rb
341
341
  - lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb
342
342
  - lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb
343
+ - lib/generators/good_job/templates/update/migrations/02_add_jobs_finished_at_to_good_job_batches.rb.erb
343
344
  - lib/generators/good_job/update_generator.rb
344
345
  - lib/good_job.rb
345
346
  - lib/good_job/active_job_extensions/batches.rb