good_job 1.12.2 → 1.99.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: 0af2c2293c2a0970a95ac200776850daa396a91d75aeda3f4612de23ee951a5c
4
- data.tar.gz: c938c0efca849771e68b39634e56fbdacf2987a83bdf8115e8050b9c49da40c3
3
+ metadata.gz: fe59c2b49eaef9a76ecbf1af80c6ea765fb89a5d61f01b0436595e15e377019e
4
+ data.tar.gz: 15c0ee082903f8316de7656e855489492a00e90b3e75e55475f59824b49c0542
5
5
  SHA512:
6
- metadata.gz: 3b4da4eec468271fc88260fc0abcdb6faaaa9e43532e5587338fdca0a4f92f89d4e4f780678fe2c74a714adb0e7a1af263fb0578193382ee8181e11c44aea20d
7
- data.tar.gz: 91f4ae034471774f5ec1915750ccbf1d9d6ad9cc68df4f5ffa4c0bf6bca57928115e6871a59d30d6bab79b22faa7a4a293d55f55c1bf2cc659d55c0c60049040
6
+ metadata.gz: 22c7de0952050994cb220cda916ce85187775d2b79692b8ee094a0e6e76f84945ad528327fc3e83e50263e46272094949d2d6270a0332c19e8f79fb0841e0d80
7
+ data.tar.gz: '0229a23385ed171a50a39d54ab5295a5147110d203e84518643caa49c2dab16eee16263b54c86245a376e946402e9d39d52f05c3cde6ba2906f8f32fd77046e0'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.99.0](https://github.com/bensheldon/good_job/tree/v1.99.0) (2021-08-24)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.13.2...v1.99.0)
6
+
7
+ **Closed issues:**
8
+
9
+ - Set Advisory Lock on ActiveJob job uuid instead of GoodJob's job uuid [\#272](https://github.com/bensheldon/good_job/issues/272)
10
+
11
+ **Merged pull requests:**
12
+
13
+ - Add upgrade instructions for v1 to v2 [\#345](https://github.com/bensheldon/good_job/pull/345) ([bensheldon](https://github.com/bensheldon))
14
+ - Add transitional/temporary additional lock on good\_jobs-\[active\_job\_id\] [\#336](https://github.com/bensheldon/good_job/pull/336) ([bensheldon](https://github.com/bensheldon))
15
+
16
+ ## [v1.13.2](https://github.com/bensheldon/good_job/tree/v1.13.2) (2021-08-18)
17
+
18
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.13.1...v1.13.2)
19
+
20
+ **Merged pull requests:**
21
+
22
+ - Add deprecation notice that `async` mode will be renamed `async_all` in GoodJob v2.0 [\#339](https://github.com/bensheldon/good_job/pull/339) ([bensheldon](https://github.com/bensheldon))
23
+
24
+ ## [v1.13.1](https://github.com/bensheldon/good_job/tree/v1.13.1) (2021-08-18)
25
+
26
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.13.0...v1.13.1)
27
+
28
+ **Fixed bugs:**
29
+
30
+ - Don’t attempt to enforce concurrency limits with other queue adapters [\#333](https://github.com/bensheldon/good_job/pull/333) ([codyrobbins](https://github.com/codyrobbins))
31
+
32
+ ## [v1.13.0](https://github.com/bensheldon/good_job/tree/v1.13.0) (2021-08-18)
33
+
34
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.12.2...v1.13.0)
35
+
36
+ **Implemented enhancements:**
37
+
38
+ - Track if a GoodJob::Job has been subsequently retried [\#331](https://github.com/bensheldon/good_job/pull/331) ([bensheldon](https://github.com/bensheldon))
39
+ - Wrap and truncate error message, which can be a huge text [\#294](https://github.com/bensheldon/good_job/pull/294) ([morgoth](https://github.com/morgoth))
40
+
41
+ **Closed issues:**
42
+
43
+ - Add hyphen to lock string. e.g. "table\_name-column" instead of "table\_namecolumn [\#334](https://github.com/bensheldon/good_job/issues/334)
44
+ - Optimize db indexes in advance of v2.0.0 [\#332](https://github.com/bensheldon/good_job/issues/332)
45
+ - wait\_until in development? [\#330](https://github.com/bensheldon/good_job/issues/330)
46
+ - Race conditions in ActiveJob concurrency extension [\#325](https://github.com/bensheldon/good_job/issues/325)
47
+ - Store in database if a job has been ActiveJob retried [\#321](https://github.com/bensheldon/good_job/issues/321)
48
+ - Revisit and embrace concurrency control, scheduled jobs, and other extensions of ActiveJob [\#255](https://github.com/bensheldon/good_job/issues/255)
49
+ - Why 1 million jobs per day? [\#222](https://github.com/bensheldon/good_job/issues/222)
50
+
3
51
  ## [v1.12.2](https://github.com/bensheldon/good_job/tree/v1.12.2) (2021-08-13)
4
52
 
5
53
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.12.1...v1.12.2)
data/README.md CHANGED
@@ -10,7 +10,7 @@ GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails.
10
10
  - **Designed for ActiveJob.** Complete support for [async, queues, delays, priorities, timeouts, and retries](https://edgeguides.rubyonrails.org/active_job_basics.html) with near-zero configuration.
11
11
  - **Built for Rails.** Fully adopts Ruby on Rails [threading and code execution guidelines](https://guides.rubyonrails.org/threading_and_code_execution.html) with [Concurrent::Ruby](https://github.com/ruby-concurrency/concurrent-ruby).
12
12
  - **Backed by Postgres.** Relies upon Postgres integrity, session-level Advisory Locks to provide run-once safety and stay within the limits of `schema.rb`, and LISTEN/NOTIFY to reduce queuing latency.
13
- - **For most workloads.** Targets full-stack teams, economy-minded solo developers, and applications that enqueue less than 1-million jobs/day.
13
+ - **For most workloads.** Targets full-stack teams, economy-minded solo developers, and applications that enqueue 1-million jobs/day and more.
14
14
 
15
15
  For more of the story of GoodJob, read the [introductory blog post](https://island94.org/2020/07/introducing-goodjob-1-0).
16
16
 
@@ -41,6 +41,8 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
41
41
  - [ActiveJob concurrency](#activejob-concurrency)
42
42
  - [Cron-style repeating/recurring jobs](#cron-style-repeatingrecurring-jobs)
43
43
  - [Updating](#updating)
44
+ - [Upgrading minor versions](#upgrading-minor-versions)
45
+ - [Upgrading v1 to v2](#upgrading-v1-to-v2)
44
46
  - [Go deeper](#go-deeper)
45
47
  - [Exceptions, retries, and reliability](#exceptions-retries-and-reliability)
46
48
  - [Exceptions](#exceptions)
@@ -405,17 +407,45 @@ config.good_job.cron = {
405
407
 
406
408
  GoodJob follows semantic versioning, though updates may be encouraged through deprecation warnings in minor versions.
407
409
 
408
- To apply updates:
410
+ #### Upgrading minor versions
409
411
 
410
- ```bash
411
- bin/rails g good_job:update
412
- ```
412
+ Upgrading between minor versions (e.g. v1.4 to v1.5) should not introduce breaking changes, but can introduce new deprecation warnings and database migration notices.
413
413
 
414
- ...and run the resulting migration:
414
+ To perform upgrades to the GoodJob database tables:
415
415
 
416
- ```bash
417
- bin/rails db:migrate
418
- ```
416
+ 1. Generate new database migration files:
417
+
418
+ ```bash
419
+ bin/rails g good_job:update
420
+ ```
421
+
422
+ 1. Run the database migration locally
423
+
424
+ ```bash
425
+ bin/rails db:migrate
426
+ ```
427
+
428
+ 1. Commit the migration files and resulting `db/schema.rb` changes.
429
+ 1. Deploy the code, run the migrations against the production database, and restart server/worker processes.
430
+
431
+ #### Upgrading v1 to v2
432
+
433
+ GoodJob v2 introduces a new Advisory Lock key format that is different than the v1 advisory lock key format; it's therefore necessary to perform a simple, but staged production upgrade. If you are already using `>= v1.12+` no other changes are likely44
434
+ necessary.
435
+
436
+ 1. Upgrade your production environment to `v1.99.x` following the minor version upgrade process, including database migrations. `v1.99` is a transitional release that is safely compatible with both `v1.x` and `v2.0.0` because it uses both `v1`- and `v2`-formatted advisory locks.
437
+ 1. Address any deprecation warnings generated by `v1.99`.
438
+ 1. Upgrade your production environment to `v1.99.x` to `v2.0.x` again following the _minor_ upgrade process.
439
+
440
+ Notable changes:
441
+
442
+ - Renames `:async_server` execution mode to `:async`; renames prior `:async` execution mode to `:async_all`.
443
+ - Sets default Development environment's execution mode to `:async` with disabled polling.
444
+ - Triggers `GoodJob.on_thread_error` for unhandled ActiveJob exceptions.
445
+ - Renames `GoodJob.reperform_jobs_on_standard_error` accessor to `GoodJob.retry_on_unhandled_error`.
446
+ - Renames `GoodJob::Adapter.shutdown(wait:)` argument to `GoodJob::Adapter.shutdown(timeout:)`.
447
+ - Changes Advisory Lock key format from `good_jobs[ROW_ID]` to `good_jobs-[ACTIVE_JOB_ID]`.
448
+ - Expects presence of columns `good_jobs.active_job_id`, `good_jobs.concurrency_key`, `good_jobs.concurrency_key`, and `good_jobs.retried_good_job_id`.
419
449
 
420
450
  ## Go deeper
421
451
 
@@ -19,7 +19,7 @@
19
19
  <td><%= job.serialized_params['job_class'] %></td>
20
20
  <td><%= job.queue_name %></td>
21
21
  <td><%= job.scheduled_at || job.created_at %></td>
22
- <td><%= job.error %></td>
22
+ <td class="text-break"><%= truncate(job.error, length: 1_000) %></td>
23
23
  <td>
24
24
  <%= tag.button "Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
25
25
  data: {bs_toggle: "collapse", bs_target: "##{dom_id(job, 'params')}"},
@@ -16,6 +16,7 @@ class CreateGoodJobs < ActiveRecord::Migration[5.2]
16
16
  t.uuid :active_job_id
17
17
  t.text :concurrency_key
18
18
  t.text :cron_key
19
+ t.uuid :retried_good_job_id
19
20
  end
20
21
 
21
22
  add_index :good_jobs, :scheduled_at, where: "(finished_at IS NULL)", name: "index_good_jobs_on_scheduled_at"
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ class AddRetriedGoodJobIdToGoodJobs < ActiveRecord::Migration[5.2]
3
+ def change
4
+ reversible do |dir|
5
+ dir.up do
6
+ # Ensure this incremental update migration is idempotent
7
+ # with monolithic install migration.
8
+ return if connection.column_exists?(:good_jobs, :retried_good_job_id)
9
+ end
10
+ end
11
+
12
+ add_column :good_jobs, :retried_good_job_id, :uuid
13
+ end
14
+ end
@@ -10,6 +10,9 @@ module GoodJob
10
10
  class_attribute :good_job_concurrency_config, instance_accessor: false, default: {}
11
11
 
12
12
  around_enqueue do |job, block|
13
+ # Don't attempt to enforce concurrency limits with other queue adapters.
14
+ next(block.call) unless job.class.queue_adapter.is_a?(GoodJob::Adapter)
15
+
13
16
  # Always allow jobs to be retried because the current job's execution will complete momentarily
14
17
  next(block.call) if CurrentExecution.active_job_id == job.job_id
15
18
 
@@ -34,6 +37,9 @@ module GoodJob
34
37
  )
35
38
 
36
39
  before_perform do |job|
40
+ # Don't attempt to enforce concurrency limits with other queue adapters.
41
+ next unless job.class.queue_adapter.is_a?(GoodJob::Adapter)
42
+
37
43
  limit = job.class.good_job_concurrency_config.fetch(:perform_limit, Float::INFINITY)
38
44
  next if limit.blank? || (0...Float::INFINITY).exclude?(limit)
39
45
 
@@ -130,7 +130,7 @@ module GoodJob
130
130
  # Whether in +:async+ execution mode.
131
131
  # @return [Boolean]
132
132
  def execute_async?
133
- @configuration.execution_mode == :async ||
133
+ @configuration.execution_mode.in?([:async, :async_all]) ||
134
134
  @configuration.execution_mode == :async_server && in_server_process?
135
135
  end
136
136
 
@@ -7,7 +7,7 @@ module GoodJob
7
7
  #
8
8
  class Configuration
9
9
  # Valid execution modes.
10
- EXECUTION_MODES = [:async, :async_server, :external, :inline].freeze
10
+ EXECUTION_MODES = [:async, :async_all, :async_server, :external, :inline].freeze
11
11
  # Default number of threads to use per {Scheduler}
12
12
  DEFAULT_MAX_THREADS = 5
13
13
  # Default number of seconds between polls for jobs
@@ -58,7 +58,18 @@ module GoodJob
58
58
  end
59
59
 
60
60
  if mode
61
- mode.to_sym
61
+ mode_sym = mode.to_sym
62
+ if mode_sym == :async
63
+ ActiveSupport::Deprecation.warn <<~DEPRECATION
64
+ The next major version of GoodJob will redefine the meaning of 'async'
65
+ execution mode to be equivalent to 'async_server' and only execute
66
+ within the webserver process.
67
+
68
+ To continue using the v1.0 semantics of 'async', use `async_all` instead.
69
+
70
+ DEPRECATION
71
+ end
72
+ mode_sym
62
73
  elsif Rails.env.development? || Rails.env.test?
63
74
  :inline
64
75
  else
@@ -5,12 +5,6 @@ module GoodJob
5
5
  # Thread-local attributes for passing values from Instrumentation.
6
6
  # (Cannot use ActiveSupport::CurrentAttributes because ActiveJob resets it)
7
7
  module CurrentExecution
8
- # @!attribute [rw] active_job_id
9
- # @!scope class
10
- # ActiveJob ID
11
- # @return [String, nil]
12
- thread_mattr_accessor :active_job_id
13
-
14
8
  # @!attribute [rw] cron_key
15
9
  # @!scope class
16
10
  # Cron Key
@@ -29,14 +23,26 @@ module GoodJob
29
23
  # @return [Exception, nil]
30
24
  thread_mattr_accessor :error_on_retry
31
25
 
26
+ # @!attribute [rw] good_job
27
+ # @!scope class
28
+ # Cron Key
29
+ # @return [GoodJob::Job, nil]
30
+ thread_mattr_accessor :good_job
31
+
32
32
  # Resets attributes
33
33
  # @return [void]
34
34
  def self.reset
35
- self.active_job_id = nil
35
+ self.cron_key = nil
36
+ self.good_job = nil
36
37
  self.error_on_discard = nil
37
38
  self.error_on_retry = nil
38
39
  end
39
40
 
41
+ # @return [String] UUID of the currently executing GoodJob::Job
42
+ def self.active_job_id
43
+ good_job&.active_job_id
44
+ end
45
+
40
46
  # @return [Integer] Current process ID
41
47
  def self.process_id
42
48
  Process.pid
data/lib/good_job/job.rb CHANGED
@@ -52,6 +52,20 @@ module GoodJob
52
52
  end
53
53
  end
54
54
 
55
+ def self._migration_pending_warning
56
+ ActiveSupport::Deprecation.warn(<<~DEPRECATION)
57
+ GoodJob has pending database migrations. To create the migration files, run:
58
+
59
+ rails generate good_job:update
60
+
61
+ To apply the migration files, run:
62
+
63
+ rails db:migrate
64
+
65
+ DEPRECATION
66
+ nil
67
+ end
68
+
55
69
  # Get Jobs with given class name
56
70
  # @!method with_job_class
57
71
  # @!scope class
@@ -110,6 +124,18 @@ module GoodJob
110
124
  # @return [ActiveRecord::Relation]
111
125
  scope :running, -> { where.not(performed_at: nil).where(finished_at: nil) }
112
126
 
127
+ # Get Jobs that do not have subsequent retries
128
+ # @!method running
129
+ # @!scope class
130
+ # @return [ActiveRecord::Relation]
131
+ scope :head, -> { where(retried_good_job_id: nil) }
132
+
133
+ # Get Jobs have errored that will not be retried further
134
+ # @!method running
135
+ # @!scope class
136
+ # @return [ActiveRecord::Relation]
137
+ scope :dead, -> { head.where.not(error: nil) }
138
+
113
139
  # Get Jobs on queues that match the given queue string.
114
140
  # @!method queue_string(string)
115
141
  # @!scope class
@@ -163,7 +189,13 @@ module GoodJob
163
189
  break if good_job.blank?
164
190
  break :unlocked unless good_job&.executable?
165
191
 
166
- good_job.perform
192
+ begin
193
+ good_job.with_advisory_lock(key: "good_jobs-#{good_job.active_job_id}") do
194
+ good_job.perform
195
+ end
196
+ rescue RecordAlreadyAdvisoryLockedError => e
197
+ ExecutionResult.new(value: nil, handled_error: e)
198
+ end
167
199
  end
168
200
  end
169
201
 
@@ -199,7 +231,6 @@ module GoodJob
199
231
  def self.enqueue(active_job, scheduled_at: nil, create_with_advisory_lock: false)
200
232
  ActiveSupport::Notifications.instrument("enqueue_job.good_job", { active_job: active_job, scheduled_at: scheduled_at, create_with_advisory_lock: create_with_advisory_lock }) do |instrument_payload|
201
233
  good_job_args = {
202
- cron_key: CurrentExecution.cron_key,
203
234
  queue_name: active_job.queue_name.presence || DEFAULT_QUEUE_NAME,
204
235
  priority: active_job.priority || DEFAULT_PRIORITY,
205
236
  serialized_params: active_job.serialize,
@@ -210,31 +241,23 @@ module GoodJob
210
241
  if column_names.include?('active_job_id')
211
242
  good_job_args[:active_job_id] = active_job.job_id
212
243
  else
213
- ActiveSupport::Deprecation.warn(<<~DEPRECATION)
214
- GoodJob has pending database migrations. To create the migration files, run:
215
-
216
- rails generate good_job:update
217
-
218
- To apply the migration files, run:
219
-
220
- rails db:migrate
221
-
222
- DEPRECATION
244
+ _migration_pending_warning
223
245
  end
224
246
 
225
247
  if column_names.include?('concurrency_key')
226
248
  good_job_args[:concurrency_key] = active_job.good_job_concurrency_key if active_job.respond_to?(:good_job_concurrency_key)
227
249
  else
228
- ActiveSupport::Deprecation.warn(<<~DEPRECATION)
229
- GoodJob has pending database migrations. To create the migration files, run:
230
-
231
- rails generate good_job:update
232
-
233
- To apply the migration files, run:
234
-
235
- rails db:migrate
250
+ _migration_pending_warning
251
+ end
236
252
 
237
- DEPRECATION
253
+ if column_names.include?('cron_key')
254
+ if CurrentExecution.cron_key
255
+ good_job_args[:cron_key] = CurrentExecution.cron_key
256
+ elsif CurrentExecution.active_job_id == active_job.job_id
257
+ good_job_args[:cron_key] = CurrentExecution.good_job.cron_key
258
+ end
259
+ else
260
+ _migration_pending_warning
238
261
  end
239
262
 
240
263
  good_job = GoodJob::Job.new(**good_job_args)
@@ -244,6 +267,12 @@ module GoodJob
244
267
  good_job.save!
245
268
  active_job.provider_job_id = good_job.id
246
269
 
270
+ if column_names.include?('retried_good_job_id')
271
+ CurrentExecution.good_job.retried_good_job_id = good_job.id if CurrentExecution.good_job && CurrentExecution.good_job.active_job_id == active_job.job_id
272
+ else
273
+ _migration_pending_warning
274
+ end
275
+
247
276
  good_job
248
277
  end
249
278
  end
@@ -283,24 +312,19 @@ module GoodJob
283
312
  end
284
313
 
285
314
  def active_job_id
286
- super || serialized_params['job_id']
315
+ if self.class.column_names.include?('active_job_id')
316
+ super
317
+ else
318
+ self.class._migration_pending_warning
319
+ serialized_params['job_id']
320
+ end
287
321
  end
288
322
 
289
323
  def cron_key
290
324
  if self.class.column_names.include?('cron_key')
291
325
  super
292
326
  else
293
- ActiveSupport::Deprecation.warn(<<~DEPRECATION)
294
- GoodJob has pending database migrations. To create the migration files, run:
295
-
296
- rails generate good_job:update
297
-
298
- To apply the migration files, run:
299
-
300
- rails db:migrate
301
-
302
- DEPRECATION
303
-
327
+ self.class._migration_pending_warning
304
328
  nil
305
329
  end
306
330
  end
@@ -314,8 +338,7 @@ module GoodJob
314
338
  )
315
339
 
316
340
  GoodJob::CurrentExecution.reset
317
- GoodJob::CurrentExecution.active_job_id = active_job_id
318
- GoodJob::CurrentExecution.cron_key = cron_key
341
+ GoodJob::CurrentExecution.good_job = self
319
342
  ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, process_id: GoodJob::CurrentExecution.process_id, thread_name: GoodJob::CurrentExecution.thread_name }) do
320
343
  value = ActiveJob::Base.execute(params)
321
344
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '1.12.2'
4
+ VERSION = '1.99.0'
5
5
  end
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: 1.12.2
4
+ version: 1.99.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: 2021-08-13 00:00:00.000000000 Z
11
+ date: 2021-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -374,6 +374,7 @@ files:
374
374
  - lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb
375
375
  - lib/generators/good_job/templates/update/migrations/02_add_active_job_id_concurrency_key_cron_key_to_good_jobs.rb
376
376
  - lib/generators/good_job/templates/update/migrations/03_add_active_job_id_index_and_concurrency_key_index_to_good_jobs.rb
377
+ - lib/generators/good_job/templates/update/migrations/04_add_retried_good_job_id_to_good_jobs.rb
377
378
  - lib/generators/good_job/update_generator.rb
378
379
  - lib/good_job.rb
379
380
  - lib/good_job/active_job_extensions.rb