good_job 3.4.2 → 3.4.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -3
- data/README.md +15 -10
- data/app/assets/good_job/style.css +0 -5
- data/app/models/good_job/execution.rb +8 -1
- data/app/views/good_job/jobs/_table.erb +1 -0
- data/app/views/good_job/shared/_filter.erb +4 -0
- data/config/locales/en.yml +0 -1
- data/config/locales/es.yml +0 -1
- data/config/locales/nl.yml +0 -1
- data/config/locales/ru.yml +0 -1
- data/lib/good_job/active_job_extensions/concurrency.rb +23 -10
- data/lib/good_job/adapter.rb +1 -0
- data/lib/good_job/version.rb +1 -1
- metadata +2 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 844b8a9661a1a6eefe86a2e58d829405b92bc7b54ad51ba6aeafea22c11ca080
|
4
|
+
data.tar.gz: c09eab3a34fe0ba218d79c0576bc1d9fe8cb41182c6d2bbf0af28c9bbbd4ce9a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d492d0763ab41e64f3670bc15176f6eec58d4fea9b4be7aa850cb2eaff4dba56d94ce42c6b0d123046dc2f0f2d183ad69e1fe6792b3e0b7fc077a32fecdff78
|
7
|
+
data.tar.gz: bb956e43b86d88a4492d715ddef00710d5667d6aed2a33b9fe9c532cfb1515f74a03e8bc7d7b86b4655296ed5ab22ad8813d9794c281304f1672853364928d1e
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,52 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v3.4.5](https://github.com/bensheldon/good_job/tree/v3.4.5) (2022-09-12)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.4.4...v3.4.5)
|
6
|
+
|
7
|
+
**Fixed bugs:**
|
8
|
+
|
9
|
+
- Dashboard: Remove translation\_missing red highlighting; remove number\_to\_human.hundreds; add form labels [\#708](https://github.com/bensheldon/good_job/pull/708) ([bensheldon](https://github.com/bensheldon))
|
10
|
+
|
11
|
+
**Closed issues:**
|
12
|
+
|
13
|
+
- pg\_xact No Such File error in logs [\#709](https://github.com/bensheldon/good_job/issues/709)
|
14
|
+
- Broken upgrade to v3. [\#703](https://github.com/bensheldon/good_job/issues/703)
|
15
|
+
|
16
|
+
**Merged pull requests:**
|
17
|
+
|
18
|
+
- Sentry integration Docs [\#711](https://github.com/bensheldon/good_job/pull/711) ([remy727](https://github.com/remy727))
|
19
|
+
- Add an `Execution` `after_perform_unlocked` callback [\#706](https://github.com/bensheldon/good_job/pull/706) ([bensheldon](https://github.com/bensheldon))
|
20
|
+
|
21
|
+
## [v3.4.4](https://github.com/bensheldon/good_job/tree/v3.4.4) (2022-08-20)
|
22
|
+
|
23
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.4.3...v3.4.4)
|
24
|
+
|
25
|
+
**Fixed bugs:**
|
26
|
+
|
27
|
+
- Keep locale param when submitting dashboard filter [\#707](https://github.com/bensheldon/good_job/pull/707) ([aki77](https://github.com/aki77))
|
28
|
+
|
29
|
+
**Merged pull requests:**
|
30
|
+
|
31
|
+
- Fix document [\#704](https://github.com/bensheldon/good_job/pull/704) ([aki77](https://github.com/aki77))
|
32
|
+
- Describe pessimistic usecases [\#702](https://github.com/bensheldon/good_job/pull/702) ([shouichi](https://github.com/shouichi))
|
33
|
+
|
34
|
+
## [v3.4.3](https://github.com/bensheldon/good_job/tree/v3.4.3) (2022-08-15)
|
35
|
+
|
36
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.4.2...v3.4.3)
|
37
|
+
|
38
|
+
**Closed issues:**
|
39
|
+
|
40
|
+
- How to run multiple workers? [\#699](https://github.com/bensheldon/good_job/issues/699)
|
41
|
+
- Getting Postgres Errors on killing development server after setting up Goodjob [\#692](https://github.com/bensheldon/good_job/issues/692)
|
42
|
+
|
43
|
+
**Merged pull requests:**
|
44
|
+
|
45
|
+
- Fix Project v2 GitHub Actions [\#701](https://github.com/bensheldon/good_job/pull/701) ([bensheldon](https://github.com/bensheldon))
|
46
|
+
- Remove development dependencies: memory\_profiler, rbtrace, sigdump [\#700](https://github.com/bensheldon/good_job/pull/700) ([bensheldon](https://github.com/bensheldon))
|
47
|
+
- Allow concurrency limits to be configured dynamically with lambda/proc [\#696](https://github.com/bensheldon/good_job/pull/696) ([baka-san](https://github.com/baka-san))
|
48
|
+
- Add additional details to Concurrency Control explanation [\#695](https://github.com/bensheldon/good_job/pull/695) ([bensheldon](https://github.com/bensheldon))
|
49
|
+
|
3
50
|
## [v3.4.2](https://github.com/bensheldon/good_job/tree/v3.4.2) (2022-08-13)
|
4
51
|
|
5
52
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.4.1...v3.4.2)
|
@@ -18,7 +65,7 @@
|
|
18
65
|
|
19
66
|
**Merged pull requests:**
|
20
67
|
|
21
|
-
- Enqueues jobs with I18n default locale [\#698](https://github.com/bensheldon/good_job/pull/698) ([esasse](https://github.com/esasse))
|
68
|
+
- Enqueues Cron jobs with I18n default locale [\#698](https://github.com/bensheldon/good_job/pull/698) ([esasse](https://github.com/esasse))
|
22
69
|
|
23
70
|
## [v3.4.1](https://github.com/bensheldon/good_job/tree/v3.4.1) (2022-08-06)
|
24
71
|
|
@@ -109,8 +156,6 @@
|
|
109
156
|
|
110
157
|
**Closed issues:**
|
111
158
|
|
112
|
-
- Calculating database connections [\#669](https://github.com/bensheldon/good_job/issues/669)
|
113
|
-
- Unable to Replace GoodJob's Logger [\#667](https://github.com/bensheldon/good_job/issues/667)
|
114
159
|
- Readme should consistently encourage usage of `config.good_job....` instead of `GoodJob.` configuration [\#628](https://github.com/bensheldon/good_job/issues/628)
|
115
160
|
- Improve the "Gem development" section of README? [\#551](https://github.com/bensheldon/good_job/issues/551)
|
116
161
|
- Simplify Rails initialization to only be a mountable Engine [\#543](https://github.com/bensheldon/good_job/issues/543)
|
data/README.md
CHANGED
@@ -232,7 +232,7 @@ Rails.application.configure do
|
|
232
232
|
# Configure options individually...
|
233
233
|
config.good_job.preserve_job_records = true
|
234
234
|
config.good_job.retry_on_unhandled_error = false
|
235
|
-
config.good_job.on_thread_error = -> (exception) {
|
235
|
+
config.good_job.on_thread_error = -> (exception) { Sentry.capture_exception(exception) }
|
236
236
|
config.good_job.execution_mode = :async
|
237
237
|
config.good_job.queues = '*'
|
238
238
|
config.good_job.max_threads = 5
|
@@ -245,7 +245,7 @@ Rails.application.configure do
|
|
245
245
|
config.good_job = {
|
246
246
|
preserve_job_records: true,
|
247
247
|
retry_on_unhandled_error: false,
|
248
|
-
on_thread_error: -> (exception) {
|
248
|
+
on_thread_error: -> (exception) { Sentry.capture_exception(exception) },
|
249
249
|
execution_mode: :async,
|
250
250
|
queues: '*',
|
251
251
|
max_threads: 5,
|
@@ -283,11 +283,11 @@ Available configuration options are:
|
|
283
283
|
- `inline_execution_respects_schedule` (boolean) Opt-in to future behavior of inline execution respecting scheduled jobs. Defaults to `false`.
|
284
284
|
- `logger` ([Rails Logger](https://api.rubyonrails.org/classes/ActiveSupport/Logger.html)) lets you set a custom logger for GoodJob. It should be an instance of a Rails `Logger` (Default: `Rails.logger`).
|
285
285
|
- `preserve_job_records` (boolean) keeps job records in your database even after jobs are completed. (Default: `true`)
|
286
|
-
- `retry_on_unhandled_error` (boolean) causes jobs to be re-queued and retried if they raise an instance of `StandardError`. Be advised this may lead to jobs being repeated infinitely ([see below for more on retries](#retries)). Instances of `Exception`, like SIGINT, will *always* be retried, regardless of this attribute’s value. (Default: `
|
286
|
+
- `retry_on_unhandled_error` (boolean) causes jobs to be re-queued and retried if they raise an instance of `StandardError`. Be advised this may lead to jobs being repeated infinitely ([see below for more on retries](#retries)). Instances of `Exception`, like SIGINT, will *always* be retried, regardless of this attribute’s value. (Default: `false`)
|
287
287
|
- `on_thread_error` (proc, lambda, or callable) will be called when an Exception. It can be useful for logging errors to bug tracking services, like Sentry or Airbrake. Example:
|
288
288
|
|
289
289
|
```ruby
|
290
|
-
config.good_job.on_thread_error = -> (exception) {
|
290
|
+
config.good_job.on_thread_error = -> (exception) { Sentry.capture_exception(exception) }
|
291
291
|
```
|
292
292
|
|
293
293
|
By default, GoodJob configures the following execution modes per environment:
|
@@ -406,15 +406,18 @@ class MyJob < ApplicationJob
|
|
406
406
|
|
407
407
|
good_job_control_concurrency_with(
|
408
408
|
# Maximum number of unfinished jobs to allow with the concurrency key
|
409
|
+
# Can be an Integer or Lambda/Proc that is invoked in the context of the job
|
409
410
|
total_limit: 1,
|
410
411
|
|
411
412
|
# Or, if more control is needed:
|
412
413
|
# Maximum number of jobs with the concurrency key to be
|
413
414
|
# concurrently enqueued (excludes performing jobs)
|
415
|
+
# Can be an Integer or Lambda/Proc that is invoked in the context of the job
|
414
416
|
enqueue_limit: 2,
|
415
417
|
|
416
418
|
# Maximum number of jobs with the concurrency key to be
|
417
419
|
# concurrently performed (excludes enqueued jobs)
|
420
|
+
# Can be an Integer or Lambda/Proc that is invoked in the context of the job
|
418
421
|
perform_limit: 1,
|
419
422
|
|
420
423
|
# Note: Under heavy load, the total number of jobs may exceed the
|
@@ -444,10 +447,12 @@ job.good_job_concurrency_key #=> "Unique-Alice"
|
|
444
447
|
|
445
448
|
#### How concurrency controls work
|
446
449
|
|
447
|
-
GoodJob's concurrency control strategy for `perform_limit` is "optimistic retry with an incremental backoff".
|
450
|
+
GoodJob's concurrency control strategy for `perform_limit` is "optimistic retry with an incremental backoff". The [code is readable](https://github.com/bensheldon/good_job/blob/main/lib/good_job/active_job_extensions/concurrency.rb).
|
448
451
|
|
449
|
-
- "Optimistic" meaning that the implementation's performance trade-off assumes that collisions are atypical (e.g. two users enqueue the same job at the same time) rather than regular (e.g. the system enqueues thousands of colliding jobs at the same time).
|
452
|
+
- "Optimistic" meaning that the implementation's performance trade-off assumes that collisions are atypical (e.g. two users enqueue the same job at the same time) rather than regular (e.g. the system enqueues thousands of colliding jobs at the same time). Depending on your concurrency requirements, you may also want to manage concurrency through the number of GoodJob threads and processes that are performing a given queue.
|
450
453
|
- "Retry with an incremental backoff" means that when `perform_limit` is exceeded, the job will raise a `GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError` which is caught by a `retry_on` handler which re-schedules the job to execute in the near future with an incremental backoff.
|
454
|
+
- First-in-first-out job execution order is not preserved when a job is retried with incremental back-off.
|
455
|
+
- For pessimistic usecases that collisions are expected, use number of threads/processes (e.g., `good_job --queue "serial:1;-serial:5"`) to control concurrency. It is also a good idea to use `perform_limit` as backstop.
|
451
456
|
|
452
457
|
### Cron-style repeating/recurring jobs
|
453
458
|
|
@@ -562,7 +567,7 @@ If errors do reach GoodJob, you can assign a callable to `GoodJob.on_thread_erro
|
|
562
567
|
|
563
568
|
```ruby
|
564
569
|
# config/initializers/good_job.rb
|
565
|
-
GoodJob.on_thread_error = -> (exception) {
|
570
|
+
GoodJob.on_thread_error = -> (exception) { Sentry.capture_exception(exception) }
|
566
571
|
```
|
567
572
|
|
568
573
|
#### Retries
|
@@ -596,13 +601,13 @@ class ApplicationJob < ActiveJob::Base
|
|
596
601
|
retry_on StandardError, wait: :exponentially_longer, attempts: Float::INFINITY
|
597
602
|
|
598
603
|
retry_on SpecialError, attempts: 5 do |_job, exception|
|
599
|
-
|
604
|
+
Sentry.capture_exception(exception)
|
600
605
|
end
|
601
606
|
|
602
607
|
around_perform do |_job, block|
|
603
608
|
block.call
|
604
609
|
rescue StandardError => e
|
605
|
-
|
610
|
+
Sentry.capture_exception(e)
|
606
611
|
raise
|
607
612
|
end
|
608
613
|
# ...
|
@@ -625,7 +630,7 @@ ActionMailer::MailDeliveryJob.retry_on StandardError, wait: :exponentially_longe
|
|
625
630
|
ActionMailer::MailDeliveryJob.around_perform do |_job, block|
|
626
631
|
block.call
|
627
632
|
rescue StandardError => e
|
628
|
-
|
633
|
+
Sentry.capture_exception(e)
|
629
634
|
raise
|
630
635
|
end
|
631
636
|
```
|
@@ -20,6 +20,8 @@ module GoodJob
|
|
20
20
|
self.table_name = 'good_jobs'
|
21
21
|
self.advisory_lockable_column = 'active_job_id'
|
22
22
|
|
23
|
+
define_model_callbacks :perform_unlocked, only: :after
|
24
|
+
|
23
25
|
# Parse a string representing a group of queues into a more readable data
|
24
26
|
# structure.
|
25
27
|
# @param string [String] Queue string
|
@@ -202,13 +204,18 @@ module GoodJob
|
|
202
204
|
# raised, if any (if the job raised, then the second array entry will be
|
203
205
|
# +nil+). If there were no jobs to execute, returns +nil+.
|
204
206
|
def self.perform_with_advisory_lock(parsed_queues: nil)
|
207
|
+
execution = nil
|
208
|
+
result = nil
|
205
209
|
unfinished.dequeueing_ordered(parsed_queues).only_scheduled.limit(1).with_advisory_lock(unlock_session: true) do |executions|
|
206
210
|
execution = executions.first
|
207
211
|
break if execution.blank?
|
208
212
|
break :unlocked unless execution&.executable?
|
209
213
|
|
210
|
-
execution.perform
|
214
|
+
result = execution.perform
|
211
215
|
end
|
216
|
+
execution&.run_callbacks(:perform_unlocked)
|
217
|
+
|
218
|
+
result
|
212
219
|
end
|
213
220
|
|
214
221
|
# Fetches the scheduled execution time of the next eligible Execution(s).
|
@@ -4,6 +4,7 @@
|
|
4
4
|
<header class="list-group-item bg-light">
|
5
5
|
<div class="row small text-muted text-uppercase align-items-center">
|
6
6
|
<div class="col-auto">
|
7
|
+
<%= label_tag('toggle_job_ids', "Toggle all jobs", class: "visually-hidden") %>
|
7
8
|
<%= check_box_tag('toggle_job_ids', "1", false, data: { "checkbox-toggle-all": "job_ids" }) %>
|
8
9
|
</div>
|
9
10
|
<div class="col-4">
|
@@ -5,8 +5,10 @@
|
|
5
5
|
<%= form_with(url: "", method: :get, local: true, id: "filter_form", class: "container-fluid") do |form| %>
|
6
6
|
<%= hidden_field_tag :poll, params[:poll] %>
|
7
7
|
<%= hidden_field_tag :state, params[:state] %>
|
8
|
+
<%= hidden_field_tag :locale, params[:locale] if params[:locale] %>
|
8
9
|
<div class="d-flex flex-row w-100">
|
9
10
|
<div class="me-2">
|
11
|
+
<%= label_tag "job_queue_filter", "Queue name", class: "visually-hidden" %>
|
10
12
|
<select name="queue_name" id="job_queue_filter" class="form-select form-select-sm">
|
11
13
|
<option value="" <%= "selected='selected'" if params[:queue_name].blank? %>>All queues</option>
|
12
14
|
|
@@ -17,6 +19,7 @@
|
|
17
19
|
</div>
|
18
20
|
|
19
21
|
<div class="me-2">
|
22
|
+
<%= label_tag "job_class_filter", "Job name", class: "visually-hidden" %>
|
20
23
|
<select name="job_class" id="job_class_filter" class="form-select form-select-sm">
|
21
24
|
<option value="" <%= "selected='selected'" if params[:job_class].blank? %>>All jobs</option>
|
22
25
|
|
@@ -27,6 +30,7 @@
|
|
27
30
|
</div>
|
28
31
|
|
29
32
|
<div class="me-2 flex-fill">
|
33
|
+
<%= label_tag "query", "Search", class: "visually-hidden" %>
|
30
34
|
<%= search_field_tag "query", params[:query], class: "form-control form-control-sm", placeholder: "Search by class, job id, job params, and error text." %>
|
31
35
|
</div>
|
32
36
|
|
data/config/locales/en.yml
CHANGED
data/config/locales/es.yml
CHANGED
data/config/locales/nl.yml
CHANGED
data/config/locales/ru.yml
CHANGED
@@ -31,11 +31,17 @@ module GoodJob
|
|
31
31
|
next(block.call) if CurrentThread.active_job_id == job.job_id
|
32
32
|
|
33
33
|
enqueue_limit = job.class.good_job_concurrency_config[:enqueue_limit]
|
34
|
-
|
34
|
+
enqueue_limit = instance_exec(&enqueue_limit) if enqueue_limit.respond_to?(:call)
|
35
|
+
enqueue_limit = nil unless enqueue_limit.present? && (0...Float::INFINITY).cover?(enqueue_limit)
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
unless enqueue_limit
|
38
|
+
total_limit = job.class.good_job_concurrency_config[:total_limit]
|
39
|
+
total_limit = instance_exec(&total_limit) if total_limit.respond_to?(:call)
|
40
|
+
total_limit = nil unless total_limit.present? && (0...Float::INFINITY).cover?(total_limit)
|
41
|
+
end
|
42
|
+
|
43
|
+
limit = enqueue_limit || total_limit
|
44
|
+
next(block.call) unless limit
|
39
45
|
|
40
46
|
# Only generate the concurrency key on the initial enqueue in case it is dynamic
|
41
47
|
job.good_job_concurrency_key ||= job._good_job_concurrency_key
|
@@ -50,7 +56,7 @@ module GoodJob
|
|
50
56
|
end
|
51
57
|
|
52
58
|
# The job has not yet been enqueued, so check if adding it will go over the limit
|
53
|
-
block.call unless enqueue_concurrency + 1 >
|
59
|
+
block.call unless (enqueue_concurrency + 1) > limit
|
54
60
|
end
|
55
61
|
end
|
56
62
|
|
@@ -64,11 +70,18 @@ module GoodJob
|
|
64
70
|
# Don't attempt to enforce concurrency limits with other queue adapters.
|
65
71
|
next unless job.class.queue_adapter.is_a?(GoodJob::Adapter)
|
66
72
|
|
67
|
-
perform_limit = job.class.good_job_concurrency_config[:perform_limit]
|
68
|
-
|
73
|
+
perform_limit = job.class.good_job_concurrency_config[:perform_limit]
|
74
|
+
perform_limit = instance_exec(&perform_limit) if perform_limit.respond_to?(:call)
|
75
|
+
perform_limit = nil unless perform_limit.present? && (0...Float::INFINITY).cover?(perform_limit)
|
76
|
+
|
77
|
+
unless perform_limit
|
78
|
+
total_limit = job.class.good_job_concurrency_config[:total_limit]
|
79
|
+
total_limit = instance_exec(&total_limit) if total_limit.respond_to?(:call)
|
80
|
+
total_limit = nil unless total_limit.present? && (0...Float::INFINITY).cover?(total_limit)
|
81
|
+
end
|
69
82
|
|
70
|
-
|
71
|
-
next unless
|
83
|
+
limit = perform_limit || total_limit
|
84
|
+
next unless limit
|
72
85
|
|
73
86
|
key = job.good_job_concurrency_key
|
74
87
|
next if key.blank?
|
@@ -79,7 +92,7 @@ module GoodJob
|
|
79
92
|
end
|
80
93
|
|
81
94
|
GoodJob::Execution.advisory_lock_key(key, function: "pg_advisory_lock") do
|
82
|
-
allowed_active_job_ids = GoodJob::Execution.unfinished.where(concurrency_key: key).advisory_locked.order(Arel.sql("COALESCE(performed_at, scheduled_at, created_at) ASC")).limit(
|
95
|
+
allowed_active_job_ids = GoodJob::Execution.unfinished.where(concurrency_key: key).advisory_locked.order(Arel.sql("COALESCE(performed_at, scheduled_at, created_at) ASC")).limit(limit).pluck(:active_job_id)
|
83
96
|
# The current job has already been locked and will appear in the previous query
|
84
97
|
raise GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError unless allowed_active_job_ids.include? job.job_id
|
85
98
|
end
|
data/lib/good_job/adapter.rb
CHANGED
data/lib/good_job/version.rb
CHANGED
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: 3.4.
|
4
|
+
version: 3.4.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -290,20 +290,6 @@ dependencies:
|
|
290
290
|
- - ">="
|
291
291
|
- !ruby/object:Gem::Version
|
292
292
|
version: '0'
|
293
|
-
- !ruby/object:Gem::Dependency
|
294
|
-
name: sigdump
|
295
|
-
requirement: !ruby/object:Gem::Requirement
|
296
|
-
requirements:
|
297
|
-
- - ">="
|
298
|
-
- !ruby/object:Gem::Version
|
299
|
-
version: '0'
|
300
|
-
type: :development
|
301
|
-
prerelease: false
|
302
|
-
version_requirements: !ruby/object:Gem::Requirement
|
303
|
-
requirements:
|
304
|
-
- - ">="
|
305
|
-
- !ruby/object:Gem::Version
|
306
|
-
version: '0'
|
307
293
|
- !ruby/object:Gem::Dependency
|
308
294
|
name: yard
|
309
295
|
requirement: !ruby/object:Gem::Requirement
|