good_job 1.99.0 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe59c2b49eaef9a76ecbf1af80c6ea765fb89a5d61f01b0436595e15e377019e
4
- data.tar.gz: 15c0ee082903f8316de7656e855489492a00e90b3e75e55475f59824b49c0542
3
+ metadata.gz: 11a59540e5e10acc24b9010cb14dc8cbce2f43e8c2f1fe886b6fcf0895a978f2
4
+ data.tar.gz: 6fea5677587740d390f6eaa3dc903cee7c26a8576f14dd91f85f8886637d634b
5
5
  SHA512:
6
- metadata.gz: 22c7de0952050994cb220cda916ce85187775d2b79692b8ee094a0e6e76f84945ad528327fc3e83e50263e46272094949d2d6270a0332c19e8f79fb0841e0d80
7
- data.tar.gz: '0229a23385ed171a50a39d54ab5295a5147110d203e84518643caa49c2dab16eee16263b54c86245a376e946402e9d39d52f05c3cde6ba2906f8f32fd77046e0'
6
+ metadata.gz: '087af5fa1eaf81eff5078b5e3303e202aa73ea022d7018d60f01c6abb5a808a9442278e87490f846475c626bb5b5dd320537d68c16697ca54e9f30147f5b9225'
7
+ data.tar.gz: d19960b918937484e0f37eb0456fc3352dc2f4394fab4b0477bde7dedab789f2ce63c48b4971e38d2ff3a5d8725f1761afd425426ad384766f34cf549c35009e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.0.2](https://github.com/bensheldon/good_job/tree/v2.0.2) (2021-08-27)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.99.1...v2.0.2)
6
+
7
+ **Closed issues:**
8
+
9
+ - Migrations generator assumes migrations are in db/migrate [\#352](https://github.com/bensheldon/good_job/issues/352)
10
+
11
+ **Merged pull requests:**
12
+
13
+ - v2.0: Generators support multiple databases: `--database` option, `migrations_paths`, custom `GoodJob.active_record_parent_class` [\#354](https://github.com/bensheldon/good_job/pull/354) ([bensheldon](https://github.com/bensheldon))
14
+ - README style/typo fixes: "web server" and possessive "Rails'" [\#350](https://github.com/bensheldon/good_job/pull/350) ([aried3r](https://github.com/aried3r))
15
+ - Add examples of setting config.good\_job.queues [\#349](https://github.com/bensheldon/good_job/pull/349) ([zachmargolis](https://github.com/zachmargolis))
16
+
17
+ ## [v1.99.1](https://github.com/bensheldon/good_job/tree/v1.99.1) (2021-08-27)
18
+
19
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.0.1...v1.99.1)
20
+
21
+ **Closed issues:**
22
+
23
+ - Does Good job support delay method? [\#344](https://github.com/bensheldon/good_job/issues/344)
24
+
25
+ ## [v2.0.1](https://github.com/bensheldon/good_job/tree/v2.0.1) (2021-08-24)
26
+
27
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.0.0...v2.0.1)
28
+
29
+ **Implemented enhancements:**
30
+
31
+ - Suppress backtrace of ConcurrencyExceededError [\#348](https://github.com/bensheldon/good_job/pull/348) ([reczy](https://github.com/reczy))
32
+
33
+ **Closed issues:**
34
+
35
+ - Is there any value in seeing a backtrace for ConcurrencyExceededError? [\#347](https://github.com/bensheldon/good_job/issues/347)
36
+ - Release GoodJob 2.0 [\#307](https://github.com/bensheldon/good_job/issues/307)
37
+ - Unhandled ActiveJob errors should trigger GoodJob.on\_thread\_error [\#247](https://github.com/bensheldon/good_job/issues/247)
38
+
39
+ ## [v2.0.0](https://github.com/bensheldon/good_job/tree/v2.0.0) (2021-08-24)
40
+
41
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.99.0...v2.0.0)
42
+
43
+ **Implemented enhancements:**
44
+
45
+ - Concurrency's enqueue\_limit should exclude performing jobs from count [\#317](https://github.com/bensheldon/good_job/issues/317)
46
+ - Rename `:async` to `:async_all`; `:async_server` to `:async` and set as Development environment default; do not poll in async development [\#343](https://github.com/bensheldon/good_job/pull/343) ([bensheldon](https://github.com/bensheldon))
47
+ - Exclude executing jobs from Concurrency's enqueue\_limit's count [\#342](https://github.com/bensheldon/good_job/pull/342) ([bensheldon](https://github.com/bensheldon))
48
+ - Unhandled ActiveJob errors should trigger GoodJob.on\_thread\_error [\#312](https://github.com/bensheldon/good_job/pull/312) ([bensheldon](https://github.com/bensheldon))
49
+
50
+ **Closed issues:**
51
+
52
+ - Swap behavior of `async` with `async_server`; rename `async` execution mode to be `async_all`; default `async` in Development; [\#340](https://github.com/bensheldon/good_job/issues/340)
53
+ - Add hyphen to lock key. e.g. "\[table\_name\]-\[column\]" instead of "\[table\_name\]\[column\]" [\#335](https://github.com/bensheldon/good_job/issues/335)
54
+ - Use `async_server` as default execution mode in Development environment [\#139](https://github.com/bensheldon/good_job/issues/139)
55
+
56
+ **Merged pull requests:**
57
+
58
+ - Remove v1.0 deprecation notices and incremental migrations [\#338](https://github.com/bensheldon/good_job/pull/338) ([bensheldon](https://github.com/bensheldon))
59
+ - Lock GoodJob::Job on active\_job\_id instead of the row id; adds separator hyphen to lock key [\#337](https://github.com/bensheldon/good_job/pull/337) ([bensheldon](https://github.com/bensheldon))
60
+
3
61
  ## [v1.99.0](https://github.com/bensheldon/good_job/tree/v1.99.0) (2021-08-24)
4
62
 
5
63
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.13.2...v1.99.0)
data/README.md CHANGED
@@ -86,6 +86,13 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
86
86
  $ bin/rails db:migrate
87
87
  ```
88
88
 
89
+ Optional: If using Rails' multiple databases with the `migrations_paths` configuration option, use the `--database` option:
90
+
91
+ ```bash
92
+ bin/rails g good_job:install --database animals
93
+ bin/rails db:migrate:animals
94
+ ```
95
+
89
96
  1. Configure the ActiveJob adapter:
90
97
 
91
98
  ```ruby
@@ -125,10 +132,10 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
125
132
  - GoodJob can also be configured to execute jobs within the web server process to save on resources. This is useful for low-workloads when economy is paramount.
126
133
 
127
134
  ```
128
- $ GOOD_JOB_EXECUTION_MODE=async_server rails server
135
+ $ GOOD_JOB_EXECUTION_MODE=async rails server
129
136
  ```
130
137
 
131
- Additional configuration is likely necessary, see the reference below for f configuration.
138
+ Additional configuration is likely necessary, see the reference below for configuration.
132
139
 
133
140
  ## Compatibility
134
141
 
@@ -213,16 +220,17 @@ Additional configuration can be provided via `config.good_job.OPTION = ...` for
213
220
  config.active_job.queue_adapter = :good_job
214
221
 
215
222
  # Configure options individually...
216
- config.good_job.execution_mode = :async_server
223
+ config.good_job.execution_mode = :async
217
224
  config.good_job.max_threads = 5
218
225
  config.good_job.poll_interval = 30 # seconds
219
226
  config.good_job.shutdown_timeout = 25 # seconds
220
227
  config.good_job.enable_cron = true
221
228
  config.good_job.cron = { example: { cron: '0 * * * *', class: 'ExampleJob' } }
229
+ config.good_job.queues = '*'
222
230
 
223
231
  # ...or all at once.
224
232
  config.good_job = {
225
- execution_mode: :async_server,
233
+ execution_mode: :async,
226
234
  max_threads: 5,
227
235
  poll_interval: 30,
228
236
  shutdown_timeout: 25,
@@ -233,6 +241,7 @@ config.good_job = {
233
241
  class: 'ExampleJob'
234
242
  },
235
243
  },
244
+ queues: '*',
236
245
  }
237
246
  ```
238
247
 
@@ -241,11 +250,11 @@ Available configuration options are:
241
250
  - `execution_mode` (symbol) specifies how and where jobs should be executed. You can also set this with the environment variable `GOOD_JOB_EXECUTION_MODE`. It can be any one of:
242
251
  - `:inline` executes jobs immediately in whatever process queued them (usually the web server process). This should only be used in test and development environments.
243
252
  - `:external` causes the adapter to enqueue jobs, but not execute them. When using this option (the default for production environments), you’ll need to use the command-line tool to actually execute your jobs.
244
- - `:async_server` executes jobs in separate threads within the Rails webserver process (`bundle exec rails server`). It can be more economical for small workloads because you don’t need a separate machine or environment for running your jobs, but if your web server is under heavy load or your jobs require a lot of resources, you should choose `:external` instead. When not in the Rails webserver, jobs will execute in `:external` mode to ensure jobs are not executed within `rails console`, `rails db:migrate`, `rails assets:prepare`, etc.
245
- - `:async` executes jobs in separate threads in _any_ Rails process.
246
- - `max_threads` (integer) sets the maximum number of threads to use when `execution_mode` is set to `:async` or `:async_server`. You can also set this with the environment variable `GOOD_JOB_MAX_THREADS`.
247
- - `queues` (string) determines which queues to execute jobs from when `execution_mode` is set to `:async` or `:async_server`. See the description of `good_job start` for more details on the format of this string. You can also set this with the environment variable `GOOD_JOB_QUEUES`.
248
- - `poll_interval` (integer) sets the number of seconds between polls for jobs when `execution_mode` is set to `:async` or `:async_server`. You can also set this with the environment variable `GOOD_JOB_POLL_INTERVAL`.
253
+ - `:async` (or `:async_server`) executes jobs in separate threads within the Rails web server process (`bundle exec rails server`). It can be more economical for small workloads because you don’t need a separate machine or environment for running your jobs, but if your web server is under heavy load or your jobs require a lot of resources, you should choose `:external` instead. When not in the Rails web server, jobs will execute in `:external` mode to ensure jobs are not executed within `rails console`, `rails db:migrate`, `rails assets:prepare`, etc.
254
+ - `:async_all` executes jobs in separate threads in _any_ Rails process.
255
+ - `max_threads` (integer) sets the maximum number of threads to use when `execution_mode` is set to `:async`. You can also set this with the environment variable `GOOD_JOB_MAX_THREADS`.
256
+ - `queues` (string) determines which queues to execute jobs from when `execution_mode` is set to `:async`. See the description of `good_job start` for more details on the format of this string. You can also set this with the environment variable `GOOD_JOB_QUEUES`.
257
+ - `poll_interval` (integer) sets the number of seconds between polls for jobs when `execution_mode` is set to `:async`. You can also set this with the environment variable `GOOD_JOB_POLL_INTERVAL`.
249
258
  - `max_cache` (integer) sets the maximum number of scheduled jobs that will be stored in memory to reduce execution latency when also polling for scheduled jobs. Caching 10,000 scheduled jobs uses approximately 20MB of memory. You can also set this with the environment variable `GOOD_JOB_MAX_CACHE`.
250
259
  - `shutdown_timeout` (float) number of seconds to wait for jobs to finish when shutting down before stopping the thread. Defaults to forever: `-1`. You can also set this with the environment variable `GOOD_JOB_SHUTDOWN_TIMEOUT`.
251
260
  - `enable_cron` (boolean) whether to run cron process. Defaults to `false`. You can also set this with the environment variable `GOOD_JOB_ENABLE_CRON`.
@@ -257,7 +266,7 @@ By default, GoodJob configures the following execution modes per environment:
257
266
 
258
267
  # config/environments/development.rb
259
268
  config.active_job.queue_adapter = :good_job
260
- config.good_job.execution_mode = :inline
269
+ config.good_job.execution_mode = :async
261
270
 
262
271
  # config/environments/test.rb
263
272
  config.active_job.queue_adapter = :good_job
@@ -387,7 +396,7 @@ config.good_job.enable_cron = ENV['DYNO'] == 'worker.1' # or `true` or via $GOOD
387
396
 
388
397
  # Configure cron with a hash that has a unique key for each recurring job
389
398
  config.good_job.cron = {
390
- # Every 15 minutes, enqueue `ExampleJob.set(priority: -10).perform_later(52, name: "Alice")`
399
+ # Every 15 minutes, enqueue `ExampleJob.set(priority: -10).perform_later(42, name: "Alice")`
391
400
  frequent_task: { # each recurring job must have a unique key
392
401
  cron: "*/15 * * * *", # cron-style scheduling format by fugit gem
393
402
  class: "ExampleJob", # reference the Job class with a string
@@ -419,6 +428,12 @@ To perform upgrades to the GoodJob database tables:
419
428
  bin/rails g good_job:update
420
429
  ```
421
430
 
431
+ Optional: If using Rails' multiple databases with the `migrations_paths` configuration option, use the `--database` option:
432
+
433
+ ```bash
434
+ $ bin/rails g good_job:update --database animals
435
+ ```
436
+
422
437
  1. Run the database migration locally
423
438
 
424
439
  ```bash
@@ -430,8 +445,7 @@ To perform upgrades to the GoodJob database tables:
430
445
 
431
446
  #### Upgrading v1 to v2
432
447
 
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.
448
+ 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 necessary.
435
449
 
436
450
  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
451
  1. Address any deprecation warnings generated by `v1.99`.
@@ -441,6 +455,7 @@ Notable changes:
441
455
 
442
456
  - Renames `:async_server` execution mode to `:async`; renames prior `:async` execution mode to `:async_all`.
443
457
  - Sets default Development environment's execution mode to `:async` with disabled polling.
458
+ - Excludes performing jobs from `enqueue_limit`'s count in `GoodJob::ActiveJobExtensions::Concurrency`.
444
459
  - Triggers `GoodJob.on_thread_error` for unhandled ActiveJob exceptions.
445
460
  - Renames `GoodJob.reperform_jobs_on_standard_error` accessor to `GoodJob.retry_on_unhandled_error`.
446
461
  - Renames `GoodJob::Adapter.shutdown(wait:)` argument to `GoodJob::Adapter.shutdown(timeout:)`.
@@ -599,7 +614,7 @@ Keep in mind, queue operations and management is an advanced discipline. This st
599
614
 
600
615
  ### Database connections
601
616
 
602
- Each GoodJob execution thread requires its own database connection that is automatically checked out from Rails’s connection pool. _Allowing GoodJob to create more threads than available database connections can lead to timeouts and is not recommended._ For example:
617
+ Each GoodJob execution thread requires its own database connection that is automatically checked out from Rails’ connection pool. _Allowing GoodJob to create more threads than available database connections can lead to timeouts and is not recommended._ For example:
603
618
 
604
619
  ```yaml
605
620
  # config/database.yml
@@ -608,7 +623,7 @@ pool: <%= [ENV.fetch("RAILS_MAX_THREADS", 5).to_i, ENV.fetch("GOOD_JOB_MAX_THREA
608
623
 
609
624
  ### Execute jobs async / in-process
610
625
 
611
- GoodJob can execute jobs "async" in the same process as the webserver (e.g. `bin/rail s`). GoodJob's async execution mode offers benefits of economy by not requiring a separate job worker process, but with the tradeoff of increased complexity. Async mode can be configured in two ways:
626
+ GoodJob can execute jobs "async" in the same process as the web server (e.g. `bin/rails s`). GoodJob's async execution mode offers benefits of economy by not requiring a separate job worker process, but with the tradeoff of increased complexity. Async mode can be configured in two ways:
612
627
 
613
628
  - Via Rails configuration:
614
629
 
@@ -617,11 +632,11 @@ GoodJob can execute jobs "async" in the same process as the webserver (e.g. `bin
617
632
  config.active_job.queue_adapter = :good_job
618
633
 
619
634
  # To change the execution mode
620
- config.good_job.execution_mode = :async_server
635
+ config.good_job.execution_mode = :async
621
636
 
622
637
  # Or with more configuration
623
638
  config.good_job = {
624
- execution_mode: :async_server,
639
+ execution_mode: :async,
625
640
  max_threads: 4,
626
641
  poll_interval: 30
627
642
  }
@@ -630,7 +645,7 @@ GoodJob can execute jobs "async" in the same process as the webserver (e.g. `bin
630
645
  - Or, with environment variables:
631
646
 
632
647
  ```bash
633
- $ GOOD_JOB_EXECUTION_MODE=async_server GOOD_JOB_MAX_THREADS=4 GOOD_JOB_POLL_INTERVAL=30 bin/rails server
648
+ $ GOOD_JOB_EXECUTION_MODE=async GOOD_JOB_MAX_THREADS=4 GOOD_JOB_POLL_INTERVAL=30 bin/rails server
634
649
  ```
635
650
 
636
651
  Depending on your application configuration, you may need to take additional steps:
@@ -642,7 +657,7 @@ Depending on your application configuration, you may need to take additional ste
642
657
  pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5).to_i + ENV.fetch("GOOD_JOB_MAX_THREADS", 4).to_i %>
643
658
  ```
644
659
 
645
- - When running Puma with workers (`WEB_CONCURRENCY > 0`) or another process-forking webserver, GoodJob's threadpool schedulers should be stopped before forking, restarted after fork, and cleanly shut down on exit. Stopping GoodJob's scheduler pre-fork is recommended to ensure that GoodJob does not continue executing jobs in the parent/controller process. For example, with Puma:
660
+ - When running Puma with workers (`WEB_CONCURRENCY > 0`) or another process-forking web server, GoodJob's threadpool schedulers should be stopped before forking, restarted after fork, and cleanly shut down on exit. Stopping GoodJob's scheduler pre-fork is recommended to ensure that GoodJob does not continue executing jobs in the parent/controller process. For example, with Puma:
646
661
 
647
662
  ```ruby
648
663
  # config/puma.rb
@@ -1,23 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
  require 'rails/generators'
3
3
  require 'rails/generators/active_record'
4
+
4
5
  module GoodJob
5
6
  #
6
7
  # Rails generator used for setting up GoodJob in a Rails application.
7
8
  # Run it with +bin/rails g good_job:install+ in your console.
8
9
  #
9
10
  class InstallGenerator < Rails::Generators::Base
10
- include Rails::Generators::Migration
11
+ include ActiveRecord::Generators::Migration
11
12
 
12
- class << self
13
- delegate :next_migration_number, to: ActiveRecord::Generators::Base
14
- end
13
+ TEMPLATES = File.join(File.dirname(__FILE__), "templates/install")
14
+ source_paths << TEMPLATES
15
15
 
16
- source_paths << File.join(File.dirname(__FILE__), "templates/install")
16
+ class_option :database, type: :string, aliases: %i(--db), desc: "The database for your migration. By default, the current environment's primary database is used."
17
17
 
18
18
  # Generates monolithic migration file that contains all database changes.
19
19
  def create_migration_file
20
- migration_template 'migrations/create_good_jobs.rb.erb', 'db/migrate/create_good_jobs.rb'
20
+ migration_template 'migrations/create_good_jobs.rb.erb', File.join(db_migrate_path, "create_good_jobs.rb")
21
21
  end
22
22
  end
23
23
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class CreateGoodJobs < ActiveRecord::Migration[5.2]
2
3
  def change
3
4
  enable_extension 'pgcrypto'
@@ -13,9 +13,17 @@ class CreateGoodJobs < ActiveRecord::Migration[5.2]
13
13
  t.text :error
14
14
 
15
15
  t.timestamps
16
+
17
+ t.uuid :active_job_id
18
+ t.text :concurrency_key
19
+ t.text :cron_key
20
+ t.uuid :retried_good_job_id
16
21
  end
17
22
 
18
23
  add_index :good_jobs, :scheduled_at, where: "(finished_at IS NULL)", name: "index_good_jobs_on_scheduled_at"
19
24
  add_index :good_jobs, [:queue_name, :scheduled_at], where: "(finished_at IS NULL)", name: :index_good_jobs_on_queue_name_and_scheduled_at
25
+ add_index :good_jobs, [:active_job_id, :created_at], name: :index_good_jobs_on_active_job_id_and_created_at
26
+ add_index :good_jobs, :concurrency_key, where: "(finished_at IS NULL)", name: :index_good_jobs_on_concurrency_key_when_unfinished
27
+ add_index :good_jobs, [:cron_key, :created_at], name: :index_good_jobs_on_cron_key_and_created_at
20
28
  end
21
29
  end
@@ -8,22 +8,20 @@ module GoodJob
8
8
  # Run it with +bin/rails g good_job:update+ in your console.
9
9
  #
10
10
  class UpdateGenerator < Rails::Generators::Base
11
- include Rails::Generators::Migration
12
-
13
- class << self
14
- delegate :next_migration_number, to: ActiveRecord::Generators::Base
15
- end
11
+ include ActiveRecord::Generators::Migration
16
12
 
17
13
  TEMPLATES = File.join(File.dirname(__FILE__), "templates/update")
18
14
  source_paths << TEMPLATES
19
15
 
16
+ class_option :database, type: :string, aliases: %i(--db), desc: "The database for your migration. By default, the current environment's primary database is used."
17
+
20
18
  # Generates incremental migration files unless they already exist.
21
19
  # All migrations should be idempotent e.g. +add_index+ is guarded with +if_index_exists?+
22
20
  def update_migration_files
23
21
  migration_templates = Dir.children(File.join(TEMPLATES, 'migrations')).sort
24
22
  migration_templates.each do |template_file|
25
23
  destination_file = template_file.match(/^\d*_(.*\.rb)/)[1] # 01_create_good_jobs.rb.erb => create_good_jobs.rb
26
- migration_template "migrations/#{template_file}", "db/migrate/#{destination_file}", skip: true
24
+ migration_template "migrations/#{template_file}", File.join(db_migrate_path, destination_file), skip: true
27
25
  end
28
26
  end
29
27
  end
@@ -4,7 +4,11 @@ module GoodJob
4
4
  module Concurrency
5
5
  extend ActiveSupport::Concern
6
6
 
7
- ConcurrencyExceededError = Class.new(StandardError)
7
+ class ConcurrencyExceededError < StandardError
8
+ def backtrace
9
+ [] # suppress backtrace
10
+ end
11
+ end
8
12
 
9
13
  included do
10
14
  class_attribute :good_job_concurrency_config, instance_accessor: false, default: {}
@@ -24,7 +28,7 @@ module GoodJob
24
28
 
25
29
  GoodJob::Job.new.with_advisory_lock(key: key, function: "pg_advisory_lock") do
26
30
  # TODO: Why is `unscoped` necessary? Nested scope is bleeding into subsequent query?
27
- enqueue_concurrency = GoodJob::Job.unscoped.where(concurrency_key: key).unfinished.count
31
+ enqueue_concurrency = GoodJob::Job.unscoped.where(concurrency_key: key).unfinished.advisory_unlocked.count
28
32
  # The job has not yet been enqueued, so check if adding it will go over the limit
29
33
  block.call unless enqueue_concurrency + 1 > limit
30
34
  end
@@ -4,16 +4,13 @@ module GoodJob
4
4
  # ActiveJob Adapter.
5
5
  #
6
6
  class Adapter
7
- # Valid execution modes.
8
- EXECUTION_MODES = [:async, :async_server, :external, :inline].freeze
9
-
10
7
  # @param execution_mode [Symbol, nil] specifies how and where jobs should be executed. You can also set this with the environment variable +GOOD_JOB_EXECUTION_MODE+.
11
8
  #
12
9
  # - +:inline+ executes jobs immediately in whatever process queued them (usually the web server process). This should only be used in test and development environments.
13
10
  # - +:external+ causes the adapter to enqueue jobs, but not execute them. When using this option (the default for production environments), you'll need to use the command-line tool to actually execute your jobs.
14
- # - +:async_server+ executes jobs in separate threads within the Rails webserver process (`bundle exec rails server`). It can be more economical for small workloads because you don't need a separate machine or environment for running your jobs, but if your web server is under heavy load or your jobs require a lot of resources, you should choose +:external+ instead.
15
- # When not in the Rails webserver, jobs will execute in +:external+ mode to ensure jobs are not executed within `rails console`, `rails db:migrate`, `rails assets:prepare`, etc.
16
- # - +:async+ executes jobs in any Rails process.
11
+ # - +:async+ (or +:async_server+) executes jobs in separate threads within the Rails web server process (`bundle exec rails server`). It can be more economical for small workloads because you don't need a separate machine or environment for running your jobs, but if your web server is under heavy load or your jobs require a lot of resources, you should choose +:external+ instead.
12
+ # When not in the Rails web server, jobs will execute in +:external+ mode to ensure jobs are not executed within `rails console`, `rails db:migrate`, `rails assets:prepare`, etc.
13
+ # - +:async_all+ executes jobs in any Rails process.
17
14
  #
18
15
  # The default value depends on the Rails environment:
19
16
  #
@@ -24,23 +21,6 @@ module GoodJob
24
21
  # @param queues [String, nil] determines which queues to execute jobs from when +execution_mode+ is set to +:async+. See {file:README.md#optimize-queues-threads-and-processes} for more details on the format of this string. You can also set this with the environment variable +GOOD_JOB_QUEUES+. Defaults to +"*"+.
25
22
  # @param poll_interval [Integer, nil] sets the number of seconds between polls for jobs when +execution_mode+ is set to +:async+. You can also set this with the environment variable +GOOD_JOB_POLL_INTERVAL+. Defaults to +1+.
26
23
  def initialize(execution_mode: nil, queues: nil, max_threads: nil, poll_interval: nil)
27
- if caller[0..4].find { |c| c.include?("/config/application.rb") || c.include?("/config/environments/") }
28
- ActiveSupport::Deprecation.warn(<<~DEPRECATION)
29
- GoodJob no longer recommends creating a GoodJob::Adapter instance:
30
-
31
- config.active_job.queue_adapter = GoodJob::Adapter.new...
32
-
33
- Instead, configure GoodJob through configuration:
34
-
35
- config.active_job.queue_adapter = :good_job
36
- config.good_job.execution_mode = :#{execution_mode}
37
- config.good_job.max_threads = #{max_threads}
38
- config.good_job.poll_interval = #{poll_interval}
39
- # etc...
40
-
41
- DEPRECATION
42
- end
43
-
44
24
  @configuration = GoodJob::Configuration.new(
45
25
  {
46
26
  execution_mode: execution_mode,
@@ -105,18 +85,8 @@ module GoodJob
105
85
  # * +-1+, the scheduler will wait until the shutdown is complete.
106
86
  # * +0+, the scheduler will immediately shutdown and stop any threads.
107
87
  # * A positive number will wait that many seconds before stopping any remaining active threads.
108
- # @param wait [Boolean, nil] Deprecated. Use +timeout:+ instead.
109
88
  # @return [void]
110
- def shutdown(timeout: :default, wait: nil)
111
- timeout = if wait.nil?
112
- timeout
113
- else
114
- ActiveSupport::Deprecation.warn(
115
- "Using `GoodJob::Adapter.shutdown` with `wait:` kwarg is deprecated; use `timeout:` kwarg instead e.g. GoodJob::Adapter.shutdown(timeout: #{wait ? '-1' : 'nil'})"
116
- )
117
- wait ? -1 : nil
118
- end
119
-
89
+ def shutdown(timeout: :default)
120
90
  timeout = if timeout == :default
121
91
  @configuration.shutdown_timeout
122
92
  else
@@ -130,15 +100,15 @@ module GoodJob
130
100
  # Whether in +:async+ execution mode.
131
101
  # @return [Boolean]
132
102
  def execute_async?
133
- @configuration.execution_mode.in?([:async, :async_all]) ||
134
- @configuration.execution_mode == :async_server && in_server_process?
103
+ @configuration.execution_mode == :async_all ||
104
+ @configuration.execution_mode.in?([:async, :async_server]) && in_server_process?
135
105
  end
136
106
 
137
107
  # Whether in +:external+ execution mode.
138
108
  # @return [Boolean]
139
109
  def execute_externally?
140
110
  @configuration.execution_mode == :external ||
141
- @configuration.execution_mode == :async_server && !in_server_process?
111
+ @configuration.execution_mode.in?([:async, :async_server]) && !in_server_process?
142
112
  end
143
113
 
144
114
  # Whether in +:inline+ execution mode.
@@ -12,6 +12,8 @@ module GoodJob
12
12
  DEFAULT_MAX_THREADS = 5
13
13
  # Default number of seconds between polls for jobs
14
14
  DEFAULT_POLL_INTERVAL = 10
15
+ # Default poll interval for async in development environment
16
+ DEFAULT_DEVELOPMENT_ASYNC_POLL_INTERVAL = -1
15
17
  # Default number of threads to use per {Scheduler}
16
18
  DEFAULT_MAX_CACHE = 10000
17
19
  # Default number of seconds to preserve jobs for {CLI#cleanup_preserved_jobs}
@@ -58,19 +60,10 @@ module GoodJob
58
60
  end
59
61
 
60
62
  if mode
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
73
- elsif Rails.env.development? || Rails.env.test?
63
+ mode.to_sym
64
+ elsif Rails.env.development?
65
+ :async
66
+ elsif Rails.env.test?
74
67
  :inline
75
68
  else
76
69
  :external
@@ -109,12 +102,19 @@ module GoodJob
109
102
  # poll (using this interval) for new queued jobs to execute.
110
103
  # @return [Integer]
111
104
  def poll_interval
112
- (
105
+ interval = (
113
106
  options[:poll_interval] ||
114
107
  rails_config[:poll_interval] ||
115
- env['GOOD_JOB_POLL_INTERVAL'] ||
116
- DEFAULT_POLL_INTERVAL
117
- ).to_i
108
+ env['GOOD_JOB_POLL_INTERVAL']
109
+ )
110
+
111
+ if interval
112
+ interval.to_i
113
+ elsif Rails.env.development? && execution_mode.in?([:async, :async_all, :async_server])
114
+ DEFAULT_DEVELOPMENT_ASYNC_POLL_INTERVAL
115
+ else
116
+ DEFAULT_POLL_INTERVAL
117
+ end
118
118
  end
119
119
 
120
120
  # The maximum number of future-scheduled jobs to store in memory.
@@ -147,12 +147,13 @@ module GoodJob
147
147
  def enable_cron
148
148
  value = ActiveModel::Type::Boolean.new.cast(
149
149
  options[:enable_cron] ||
150
- rails_config[:enable_cron] ||
151
- env['GOOD_JOB_ENABLE_CRON'] ||
152
- false
150
+ rails_config[:enable_cron] ||
151
+ env['GOOD_JOB_ENABLE_CRON'] ||
152
+ false
153
153
  )
154
154
  value && cron.size.positive?
155
155
  end
156
+
156
157
  alias enable_cron? enable_cron
157
158
 
158
159
  def cron
data/lib/good_job/job.rb CHANGED
@@ -16,7 +16,7 @@ module GoodJob
16
16
  DEFAULT_PRIORITY = 0
17
17
 
18
18
  self.table_name = 'good_jobs'
19
- self.advisory_lockable_column = 'id'
19
+ self.advisory_lockable_column = 'active_job_id'
20
20
 
21
21
  attr_readonly :serialized_params
22
22
 
@@ -52,20 +52,6 @@ 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
-
69
55
  # Get Jobs with given class name
70
56
  # @!method with_job_class
71
57
  # @!scope class
@@ -78,14 +64,7 @@ module GoodJob
78
64
  # @!method unfinished
79
65
  # @!scope class
80
66
  # @return [ActiveRecord::Relation]
81
- scope :unfinished, (lambda do
82
- if column_names.include?('finished_at')
83
- where(finished_at: nil)
84
- else
85
- ActiveSupport::Deprecation.warn('GoodJob expects a good_jobs.finished_at column to exist. Please see the GoodJob README.md for migration instructions.')
86
- nil
87
- end
88
- end)
67
+ scope :unfinished, -> { where(finished_at: nil) }
89
68
 
90
69
  # Get Jobs that are not scheduled for a later time than now (i.e. jobs that
91
70
  # are not scheduled or scheduled for earlier than the current time).
@@ -231,6 +210,7 @@ module GoodJob
231
210
  def self.enqueue(active_job, scheduled_at: nil, create_with_advisory_lock: false)
232
211
  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|
233
212
  good_job_args = {
213
+ active_job_id: active_job.job_id,
234
214
  queue_name: active_job.queue_name.presence || DEFAULT_QUEUE_NAME,
235
215
  priority: active_job.priority || DEFAULT_PRIORITY,
236
216
  serialized_params: active_job.serialize,
@@ -238,26 +218,12 @@ module GoodJob
238
218
  create_with_advisory_lock: create_with_advisory_lock,
239
219
  }
240
220
 
241
- if column_names.include?('active_job_id')
242
- good_job_args[:active_job_id] = active_job.job_id
243
- else
244
- _migration_pending_warning
245
- end
246
-
247
- if column_names.include?('concurrency_key')
248
- good_job_args[:concurrency_key] = active_job.good_job_concurrency_key if active_job.respond_to?(:good_job_concurrency_key)
249
- else
250
- _migration_pending_warning
251
- end
221
+ good_job_args[:concurrency_key] = active_job.good_job_concurrency_key if active_job.respond_to?(:good_job_concurrency_key)
252
222
 
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
223
+ if CurrentExecution.cron_key
224
+ good_job_args[:cron_key] = CurrentExecution.cron_key
225
+ elsif CurrentExecution.active_job_id == active_job.job_id
226
+ good_job_args[:cron_key] = CurrentExecution.good_job.cron_key
261
227
  end
262
228
 
263
229
  good_job = GoodJob::Job.new(**good_job_args)
@@ -267,11 +233,7 @@ module GoodJob
267
233
  good_job.save!
268
234
  active_job.provider_job_id = good_job.id
269
235
 
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
236
+ CurrentExecution.good_job.retried_good_job_id = good_job.id if CurrentExecution.good_job && CurrentExecution.good_job.active_job_id == active_job.job_id
275
237
 
276
238
  good_job
277
239
  end
@@ -311,24 +273,6 @@ module GoodJob
311
273
  self.class.unscoped.unfinished.owns_advisory_locked.exists?(id: id)
312
274
  end
313
275
 
314
- def active_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
321
- end
322
-
323
- def cron_key
324
- if self.class.column_names.include?('cron_key')
325
- super
326
- else
327
- self.class._migration_pending_warning
328
- nil
329
- end
330
- end
331
-
332
276
  private
333
277
 
334
278
  # @return [ExecutionResult]
@@ -51,7 +51,7 @@ module GoodJob
51
51
  composed_cte = Arel::Nodes::As.new(cte_table, Arel::Nodes::SqlLiteral.new([cte_type, "(", cte_query.to_sql, ")"].join(' ')))
52
52
  query = cte_table.project(cte_table[:id])
53
53
  .with(composed_cte)
54
- .where(Arel.sql(sanitize_sql_for_conditions(["#{function}(('x' || substr(md5(:table_name || #{connection.quote_table_name(cte_table.name)}.#{connection.quote_column_name(column)}::text), 1, 16))::bit(64)::bigint)", { table_name: table_name }])))
54
+ .where(Arel.sql(sanitize_sql_for_conditions(["#{function}(('x' || substr(md5(:table_name || '-' || #{connection.quote_table_name(cte_table.name)}.#{connection.quote_column_name(column)}::text), 1, 16))::bit(64)::bigint)", { table_name: table_name }])))
55
55
 
56
56
  limit = original_query.arel.ast.limit
57
57
  query.limit = limit.value if limit.present?
@@ -75,8 +75,8 @@ module GoodJob
75
75
  join_sql = <<~SQL.squish
76
76
  LEFT JOIN pg_locks ON pg_locks.locktype = 'advisory'
77
77
  AND pg_locks.objsubid = 1
78
- AND pg_locks.classid = ('x' || substr(md5(:table_name || #{quoted_table_name}.#{connection.quote_column_name(column)}::text), 1, 16))::bit(32)::int
79
- AND pg_locks.objid = (('x' || substr(md5(:table_name || #{quoted_table_name}.#{connection.quote_column_name(column)}::text), 1, 16))::bit(64) << 32)::bit(32)::int
78
+ AND pg_locks.classid = ('x' || substr(md5(:table_name || '-' || #{quoted_table_name}.#{connection.quote_column_name(column)}::text), 1, 16))::bit(32)::int
79
+ AND pg_locks.objid = (('x' || substr(md5(:table_name || '-' || #{quoted_table_name}.#{connection.quote_column_name(column)}::text), 1, 16))::bit(64) << 32)::bit(32)::int
80
80
  SQL
81
81
 
82
82
  joins(sanitize_sql_for_conditions([join_sql, { table_name: table_name }]))
@@ -315,7 +315,7 @@ module GoodJob
315
315
  # Default Advisory Lock key
316
316
  # @return [String]
317
317
  def lockable_key
318
- [self.class.table_name, self[self.class._advisory_lockable_column]].join
318
+ "#{self.class.table_name}-#{self[self.class._advisory_lockable_column]}"
319
319
  end
320
320
 
321
321
  delegate :pg_or_jdbc_query, to: :class
@@ -168,7 +168,9 @@ module GoodJob # :nodoc:
168
168
  # @!visibility private
169
169
  # @return [void]
170
170
  def task_observer(time, output, thread_error)
171
- GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
171
+ error = thread_error || (output.is_a?(GoodJob::ExecutionResult) ? output.unhandled_error : nil)
172
+ GoodJob.on_thread_error.call(error) if error && GoodJob.on_thread_error.respond_to?(:call)
173
+
172
174
  instrument("finished_job_task", { result: output, error: thread_error, time: time })
173
175
  create_task if output
174
176
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '1.99.0'
4
+ VERSION = '2.0.2'
5
5
  end
data/lib/good_job.rb CHANGED
@@ -55,25 +55,6 @@ module GoodJob
55
55
  # @return [Boolean, nil]
56
56
  mattr_accessor :retry_on_unhandled_error, default: true
57
57
 
58
- # @deprecated Use {GoodJob#retry_on_unhandled_error} instead.
59
- # @return [Boolean, nil]
60
- def self.reperform_jobs_on_standard_error
61
- ActiveSupport::Deprecation.warn(
62
- "Calling 'GoodJob.reperform_jobs_on_standard_error' is deprecated. Please use 'retry_on_unhandled_error'"
63
- )
64
- retry_on_unhandled_error
65
- end
66
-
67
- # @deprecated Use {GoodJob#retry_on_unhandled_error=} instead.
68
- # @param value [Boolean]
69
- # @return [Boolean]
70
- def self.reperform_jobs_on_standard_error=(value)
71
- ActiveSupport::Deprecation.warn(
72
- "Setting 'GoodJob.reperform_jobs_on_standard_error=' is deprecated. Please use 'retry_on_unhandled_error='"
73
- )
74
- self.retry_on_unhandled_error = value
75
- end
76
-
77
58
  # @!attribute [rw] on_thread_error
78
59
  # @!scope class
79
60
  # This callable will be called when an exception reaches GoodJob (default: +nil+).
@@ -96,16 +77,7 @@ module GoodJob
96
77
  # * +1..+, the scheduler will wait that many seconds before stopping any remaining active tasks.
97
78
  # @param wait [Boolean] whether to wait for shutdown
98
79
  # @return [void]
99
- def self.shutdown(timeout: -1, wait: nil)
100
- timeout = if wait.nil?
101
- timeout
102
- else
103
- ActiveSupport::Deprecation.warn(
104
- "Using `GoodJob.shutdown` with `wait:` kwarg is deprecated; use `timeout:` kwarg instead e.g. GoodJob.shutdown(timeout: #{wait ? '-1' : 'nil'})"
105
- )
106
- wait ? -1 : nil
107
- end
108
-
80
+ def self.shutdown(timeout: -1)
109
81
  _shutdown_all(_executables, timeout: timeout)
110
82
  end
111
83
 
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.99.0
4
+ version: 2.0.2
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-24 00:00:00.000000000 Z
11
+ date: 2021-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -372,9 +372,6 @@ files:
372
372
  - lib/generators/good_job/install_generator.rb
373
373
  - lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb
374
374
  - lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb
375
- - lib/generators/good_job/templates/update/migrations/02_add_active_job_id_concurrency_key_cron_key_to_good_jobs.rb
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
378
375
  - lib/generators/good_job/update_generator.rb
379
376
  - lib/good_job.rb
380
377
  - lib/good_job/active_job_extensions.rb
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
- class AddActiveJobIdConcurrencyKeyCronKeyToGoodJobs < 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, :active_job_id)
9
- end
10
- end
11
-
12
- add_column :good_jobs, :active_job_id, :uuid
13
- add_column :good_jobs, :concurrency_key, :text
14
- add_column :good_jobs, :cron_key, :text
15
- end
16
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
- class AddActiveJobIdIndexAndConcurrencyKeyIndexToGoodJobs < ActiveRecord::Migration[5.2]
3
- disable_ddl_transaction!
4
-
5
- UPDATE_BATCH_SIZE = 1_000
6
-
7
- class GoodJobJobs < ActiveRecord::Base
8
- self.table_name = "good_jobs"
9
- end
10
-
11
- def change
12
- reversible do |dir|
13
- dir.up do
14
- # Ensure this incremental update migration is idempotent
15
- # with monolithic install migration.
16
- return if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_active_job_id_and_created_at)
17
- end
18
- end
19
-
20
- add_index :good_jobs, [:active_job_id, :created_at], algorithm: :concurrently, name: :index_good_jobs_on_active_job_id_and_created_at
21
- add_index :good_jobs, :concurrency_key, where: "(finished_at IS NULL)", algorithm: :concurrently, name: :index_good_jobs_on_concurrency_key_when_unfinished
22
- add_index :good_jobs, [:cron_key, :created_at], algorithm: :concurrently, name: :index_good_jobs_on_cron_key_and_created_at
23
-
24
- reversible do |dir|
25
- dir.up do
26
- start_time = Time.current
27
- loop do
28
- break if GoodJobJobs.where(active_job_id: nil, finished_at: nil).where("created_at < ?", start_time).limit(UPDATE_BATCH_SIZE).update_all("active_job_id = (serialized_params->>'job_id')::uuid").zero?
29
- end
30
- end
31
- end
32
- end
33
- end
@@ -1,14 +0,0 @@
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