good_job 1.99.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -2786
- data/README.md +14 -27
- data/lib/generators/good_job/install_generator.rb +6 -6
- data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +1 -0
- data/lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb +8 -0
- data/lib/generators/good_job/update_generator.rb +6 -4
- data/lib/good_job/active_job_extensions/concurrency.rb +1 -1
- data/lib/good_job/adapter.rb +6 -36
- data/lib/good_job/configuration.rb +21 -20
- data/lib/good_job/job.rb +9 -65
- data/lib/good_job/lockable.rb +4 -4
- data/lib/good_job/scheduler.rb +3 -1
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +1 -29
- metadata +3 -6
- data/lib/generators/good_job/templates/update/migrations/02_add_active_job_id_concurrency_key_cron_key_to_good_jobs.rb +0 -16
- data/lib/generators/good_job/templates/update/migrations/03_add_active_job_id_index_and_concurrency_key_index_to_good_jobs.rb +0 -36
- data/lib/generators/good_job/templates/update/migrations/04_add_retried_good_job_id_to_good_jobs.rb +0 -14
data/README.md
CHANGED
@@ -86,13 +86,6 @@ 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
|
-
|
96
89
|
1. Configure the ActiveJob adapter:
|
97
90
|
|
98
91
|
```ruby
|
@@ -132,7 +125,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
|
|
132
125
|
- 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.
|
133
126
|
|
134
127
|
```
|
135
|
-
$ GOOD_JOB_EXECUTION_MODE=
|
128
|
+
$ GOOD_JOB_EXECUTION_MODE=async rails server
|
136
129
|
```
|
137
130
|
|
138
131
|
Additional configuration is likely necessary, see the reference below for f configuration.
|
@@ -220,7 +213,7 @@ Additional configuration can be provided via `config.good_job.OPTION = ...` for
|
|
220
213
|
config.active_job.queue_adapter = :good_job
|
221
214
|
|
222
215
|
# Configure options individually...
|
223
|
-
config.good_job.execution_mode = :
|
216
|
+
config.good_job.execution_mode = :async
|
224
217
|
config.good_job.max_threads = 5
|
225
218
|
config.good_job.poll_interval = 30 # seconds
|
226
219
|
config.good_job.shutdown_timeout = 25 # seconds
|
@@ -229,7 +222,7 @@ config.good_job.cron = { example: { cron: '0 * * * *', class: 'ExampleJob' } }
|
|
229
222
|
|
230
223
|
# ...or all at once.
|
231
224
|
config.good_job = {
|
232
|
-
execution_mode: :
|
225
|
+
execution_mode: :async,
|
233
226
|
max_threads: 5,
|
234
227
|
poll_interval: 30,
|
235
228
|
shutdown_timeout: 25,
|
@@ -248,11 +241,11 @@ Available configuration options are:
|
|
248
241
|
- `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:
|
249
242
|
- `:inline` executes jobs immediately in whatever process queued them (usually the web server process). This should only be used in test and development environments.
|
250
243
|
- `: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.
|
251
|
-
- `: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.
|
252
|
-
- `:
|
253
|
-
- `max_threads` (integer) sets the maximum number of threads to use when `execution_mode` is set to `:async
|
254
|
-
- `queues` (string) determines which queues to execute jobs from when `execution_mode` is set to `:async
|
255
|
-
- `poll_interval` (integer) sets the number of seconds between polls for jobs when `execution_mode` is set to `:async
|
244
|
+
- `:async` (or `: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_all` 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`. 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`. 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`. You can also set this with the environment variable `GOOD_JOB_POLL_INTERVAL`.
|
256
249
|
- `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`.
|
257
250
|
- `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`.
|
258
251
|
- `enable_cron` (boolean) whether to run cron process. Defaults to `false`. You can also set this with the environment variable `GOOD_JOB_ENABLE_CRON`.
|
@@ -264,7 +257,7 @@ By default, GoodJob configures the following execution modes per environment:
|
|
264
257
|
|
265
258
|
# config/environments/development.rb
|
266
259
|
config.active_job.queue_adapter = :good_job
|
267
|
-
config.good_job.execution_mode = :
|
260
|
+
config.good_job.execution_mode = :async
|
268
261
|
|
269
262
|
# config/environments/test.rb
|
270
263
|
config.active_job.queue_adapter = :good_job
|
@@ -426,12 +419,6 @@ To perform upgrades to the GoodJob database tables:
|
|
426
419
|
bin/rails g good_job:update
|
427
420
|
```
|
428
421
|
|
429
|
-
Optional: If using Rails' multiple databases with the `migrations_paths` configuration option, use the `--database` option:
|
430
|
-
|
431
|
-
```bash
|
432
|
-
$ bin/rails g good_job:update --database animals
|
433
|
-
```
|
434
|
-
|
435
422
|
1. Run the database migration locally
|
436
423
|
|
437
424
|
```bash
|
@@ -443,8 +430,7 @@ To perform upgrades to the GoodJob database tables:
|
|
443
430
|
|
444
431
|
#### Upgrading v1 to v2
|
445
432
|
|
446
|
-
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
|
447
|
-
necessary.
|
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 necessary.
|
448
434
|
|
449
435
|
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.
|
450
436
|
1. Address any deprecation warnings generated by `v1.99`.
|
@@ -454,6 +440,7 @@ Notable changes:
|
|
454
440
|
|
455
441
|
- Renames `:async_server` execution mode to `:async`; renames prior `:async` execution mode to `:async_all`.
|
456
442
|
- Sets default Development environment's execution mode to `:async` with disabled polling.
|
443
|
+
- Excludes performing jobs from `enqueue_limit`'s count in `GoodJob::ActiveJobExtensions::Concurrency`.
|
457
444
|
- Triggers `GoodJob.on_thread_error` for unhandled ActiveJob exceptions.
|
458
445
|
- Renames `GoodJob.reperform_jobs_on_standard_error` accessor to `GoodJob.retry_on_unhandled_error`.
|
459
446
|
- Renames `GoodJob::Adapter.shutdown(wait:)` argument to `GoodJob::Adapter.shutdown(timeout:)`.
|
@@ -630,11 +617,11 @@ GoodJob can execute jobs "async" in the same process as the webserver (e.g. `bin
|
|
630
617
|
config.active_job.queue_adapter = :good_job
|
631
618
|
|
632
619
|
# To change the execution mode
|
633
|
-
config.good_job.execution_mode = :
|
620
|
+
config.good_job.execution_mode = :async
|
634
621
|
|
635
622
|
# Or with more configuration
|
636
623
|
config.good_job = {
|
637
|
-
execution_mode: :
|
624
|
+
execution_mode: :async,
|
638
625
|
max_threads: 4,
|
639
626
|
poll_interval: 30
|
640
627
|
}
|
@@ -643,7 +630,7 @@ GoodJob can execute jobs "async" in the same process as the webserver (e.g. `bin
|
|
643
630
|
- Or, with environment variables:
|
644
631
|
|
645
632
|
```bash
|
646
|
-
$ GOOD_JOB_EXECUTION_MODE=
|
633
|
+
$ GOOD_JOB_EXECUTION_MODE=async GOOD_JOB_MAX_THREADS=4 GOOD_JOB_POLL_INTERVAL=30 bin/rails server
|
647
634
|
```
|
648
635
|
|
649
636
|
Depending on your application configuration, you may need to take additional steps:
|
@@ -1,23 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'rails/generators'
|
3
3
|
require 'rails/generators/active_record'
|
4
|
-
|
5
4
|
module GoodJob
|
6
5
|
#
|
7
6
|
# Rails generator used for setting up GoodJob in a Rails application.
|
8
7
|
# Run it with +bin/rails g good_job:install+ in your console.
|
9
8
|
#
|
10
9
|
class InstallGenerator < Rails::Generators::Base
|
11
|
-
include
|
10
|
+
include Rails::Generators::Migration
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
class << self
|
13
|
+
delegate :next_migration_number, to: ActiveRecord::Generators::Base
|
14
|
+
end
|
15
15
|
|
16
|
-
|
16
|
+
source_paths << File.join(File.dirname(__FILE__), "templates/install")
|
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',
|
20
|
+
migration_template 'migrations/create_good_jobs.rb.erb', 'db/migrate/create_good_jobs.rb'
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -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,20 +8,22 @@ 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
|
11
|
+
include Rails::Generators::Migration
|
12
|
+
|
13
|
+
class << self
|
14
|
+
delegate :next_migration_number, to: ActiveRecord::Generators::Base
|
15
|
+
end
|
12
16
|
|
13
17
|
TEMPLATES = File.join(File.dirname(__FILE__), "templates/update")
|
14
18
|
source_paths << TEMPLATES
|
15
19
|
|
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
|
-
|
18
20
|
# Generates incremental migration files unless they already exist.
|
19
21
|
# All migrations should be idempotent e.g. +add_index+ is guarded with +if_index_exists?+
|
20
22
|
def update_migration_files
|
21
23
|
migration_templates = Dir.children(File.join(TEMPLATES, 'migrations')).sort
|
22
24
|
migration_templates.each do |template_file|
|
23
25
|
destination_file = template_file.match(/^\d*_(.*\.rb)/)[1] # 01_create_good_jobs.rb.erb => create_good_jobs.rb
|
24
|
-
migration_template "migrations/#{template_file}",
|
26
|
+
migration_template "migrations/#{template_file}", "db/migrate/#{destination_file}", skip: true
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
@@ -24,7 +24,7 @@ module GoodJob
|
|
24
24
|
|
25
25
|
GoodJob::Job.new.with_advisory_lock(key: key, function: "pg_advisory_lock") do
|
26
26
|
# TODO: Why is `unscoped` necessary? Nested scope is bleeding into subsequent query?
|
27
|
-
enqueue_concurrency = GoodJob::Job.unscoped.where(concurrency_key: key).unfinished.count
|
27
|
+
enqueue_concurrency = GoodJob::Job.unscoped.where(concurrency_key: key).unfinished.advisory_unlocked.count
|
28
28
|
# The job has not yet been enqueued, so check if adding it will go over the limit
|
29
29
|
block.call unless enqueue_concurrency + 1 > limit
|
30
30
|
end
|
data/lib/good_job/adapter.rb
CHANGED
@@ -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.
|
11
|
+
# - +:async+ (or +: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
12
|
# 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
|
-
# - +:
|
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
|
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
|
134
|
-
@configuration.execution_mode
|
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
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
117
|
-
|
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
|
-
|
151
|
-
|
152
|
-
|
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 = '
|
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, (
|
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
|
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
|
254
|
-
|
255
|
-
|
256
|
-
|
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
|
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]
|
data/lib/good_job/lockable.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/good_job/version.rb
CHANGED
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
|
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:
|
4
|
+
version: 2.0.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:
|
11
|
+
date: 2021-08-24 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
|
@@ -428,7 +425,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
428
425
|
- !ruby/object:Gem::Version
|
429
426
|
version: '0'
|
430
427
|
requirements: []
|
431
|
-
rubygems_version: 3.
|
428
|
+
rubygems_version: 3.2.13
|
432
429
|
signing_key:
|
433
430
|
specification_version: 4
|
434
431
|
summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|
@@ -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
|