good_job 2.0.5 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +7 -4
- data/engine/app/views/good_job/shared/_jobs_table.erb +19 -11
- data/lib/good_job/active_job_extensions/concurrency.rb +20 -8
- data/lib/good_job/lockable.rb +15 -3
- data/lib/good_job/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a15eb306ad205685ef586d40c1f54cea489e900143e7080fa3da72b3272846a6
|
4
|
+
data.tar.gz: 4739ce28860fda800a785d32802a94083f7012496f465360240e895b15533879
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f02dbda1d52e6340b9cb93b3dd5389271f1356844391c4c14ffcc651b25edd7cc1d6bcdfe96f164e7ee8faec47b00daca5e2cd0d11ec2a616a15450801425ce
|
7
|
+
data.tar.gz: 335871d05b792214b2a168726c154c7267d5c6081b448cd1623462d11013d45bbb0b49adc5aea488de6e13f74f556ec8b1134684b1e5505f334324dfeeb948e8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v2.1.0](https://github.com/bensheldon/good_job/tree/v2.1.0) (2021-09-09)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v2.0.5...v2.1.0)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Add `total_limit:` option to GoodJob::Concurrency to be inclusive of counting both enqueued and performing jobs [\#369](https://github.com/bensheldon/good_job/pull/369) ([bensheldon](https://github.com/bensheldon))
|
10
|
+
- Add button to toggle all job params in Dashboard [\#365](https://github.com/bensheldon/good_job/pull/365) ([bensheldon](https://github.com/bensheldon))
|
11
|
+
|
12
|
+
**Closed issues:**
|
13
|
+
|
14
|
+
- Cron-like jobs not always executed, possible reasons? [\#359](https://github.com/bensheldon/good_job/issues/359)
|
15
|
+
|
16
|
+
**Merged pull requests:**
|
17
|
+
|
18
|
+
- When shelling out in tests, send SIGKILL if process does not exit [\#371](https://github.com/bensheldon/good_job/pull/371) ([bensheldon](https://github.com/bensheldon))
|
19
|
+
- Fix unlock key for Lockable\#with\_advisory\_lock [\#368](https://github.com/bensheldon/good_job/pull/368) ([bensheldon](https://github.com/bensheldon))
|
20
|
+
- Have all tests use stubbed TestJob [\#364](https://github.com/bensheldon/good_job/pull/364) ([bensheldon](https://github.com/bensheldon))
|
21
|
+
|
3
22
|
## [v2.0.5](https://github.com/bensheldon/good_job/tree/v2.0.5) (2021-09-06)
|
4
23
|
|
5
24
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v2.0.4...v2.0.5)
|
data/README.md
CHANGED
@@ -348,17 +348,20 @@ GoodJob includes a Dashboard as a mountable `Rails::Engine`.
|
|
348
348
|
|
349
349
|
GoodJob can extend ActiveJob to provide limits on concurrently running jobs, either at time of _enqueue_ or at _perform_. Limiting concurrency can help prevent duplicate, double or unecessary jobs from being enqueued, or race conditions when performing, for example when interacting with 3rd-party APIs.
|
350
350
|
|
351
|
-
**Note:** Limiting concurrency at _enqueue_ requires Rails 6.0+ because Rails 5.2
|
351
|
+
**Note:** Limiting concurrency at _enqueue_ requires Rails 6.0+ because Rails 5.2 cannot halt ActiveJob callbacks.
|
352
352
|
|
353
353
|
```ruby
|
354
354
|
class MyJob < ApplicationJob
|
355
355
|
include GoodJob::ActiveJobExtensions::Concurrency
|
356
356
|
|
357
357
|
good_job_control_concurrency_with(
|
358
|
-
# Maximum number of jobs with the concurrency key
|
359
|
-
|
358
|
+
# Maximum number of unfinished jobs to allow with the concurrency key
|
359
|
+
total_limit: 1,
|
360
360
|
|
361
|
-
#
|
361
|
+
# Or, if more control is needed:
|
362
|
+
# Maximum number of jobs with the concurrency key to be concurrently enqueued (excludes performing jobs)
|
363
|
+
enqueue_limit: 2,
|
364
|
+
# Maximum number of jobs with the concurrency key to be concurrently performed (excludes enqueued jobs)
|
362
365
|
perform_limit: 1,
|
363
366
|
|
364
367
|
# A unique key to be globally locked against.
|
@@ -2,14 +2,22 @@
|
|
2
2
|
<div class="table-responsive">
|
3
3
|
<table class="table card-table table-bordered table-hover table-sm mb-0">
|
4
4
|
<thead>
|
5
|
-
<
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
5
|
+
<tr>
|
6
|
+
<th>GoodJob ID</th>
|
7
|
+
<th>ActiveJob ID</th>
|
8
|
+
<th>Job Class</th>
|
9
|
+
<th>Queue</th>
|
10
|
+
<th>Scheduled At</th>
|
11
|
+
<th>Error</th>
|
12
|
+
<th>
|
13
|
+
ActiveJob Params
|
14
|
+
<%= tag.button "Toggle", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
15
|
+
data: { bs_toggle: "collapse", bs_target: ".job-params" },
|
16
|
+
aria: { expanded: false, controls: jobs.map { |job| "##{dom_id(job, "params")}" }.join(" ") }
|
17
|
+
%>
|
18
|
+
</th>
|
19
|
+
<th>Actions</th>
|
20
|
+
</tr>
|
13
21
|
</thead>
|
14
22
|
<tbody>
|
15
23
|
<% jobs.each do |job| %>
|
@@ -22,10 +30,10 @@
|
|
22
30
|
<td class="text-break"><%= truncate(job.error, length: 1_000) %></td>
|
23
31
|
<td>
|
24
32
|
<%= tag.button "Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
25
|
-
data: {bs_toggle: "collapse", bs_target: "##{dom_id(job, 'params')}"},
|
26
|
-
aria: {expanded: false, controls: dom_id(job, "params")}
|
33
|
+
data: { bs_toggle: "collapse", bs_target: "##{dom_id(job, 'params')}" },
|
34
|
+
aria: { expanded: false, controls: dom_id(job, "params") }
|
27
35
|
%>
|
28
|
-
<%= tag.pre JSON.pretty_generate(job.serialized_params), id: dom_id(job, "params"), class: "collapse" %>
|
36
|
+
<%= tag.pre JSON.pretty_generate(job.serialized_params), id: dom_id(job, "params"), class: "collapse job-params" %>
|
29
37
|
</td>
|
30
38
|
<td>
|
31
39
|
<%= button_to job_path(job.id), method: :delete, class: "btn btn-sm btn-outline-danger", title: "Delete job" do %>
|
@@ -20,17 +20,26 @@ module GoodJob
|
|
20
20
|
# Always allow jobs to be retried because the current job's execution will complete momentarily
|
21
21
|
next(block.call) if CurrentExecution.active_job_id == job.job_id
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
enqueue_limit = job.class.good_job_concurrency_config[:enqueue_limit]
|
24
|
+
total_limit = job.class.good_job_concurrency_config[:total_limit]
|
25
|
+
|
26
|
+
has_limit = (enqueue_limit.present? && (0...Float::INFINITY).cover?(enqueue_limit)) ||
|
27
|
+
(total_limit.present? && (0...Float::INFINITY).cover?(total_limit))
|
28
|
+
next(block.call) unless has_limit
|
25
29
|
|
26
30
|
key = job.good_job_concurrency_key
|
27
31
|
next(block.call) if key.blank?
|
28
32
|
|
29
33
|
GoodJob::Job.new.with_advisory_lock(key: key, function: "pg_advisory_lock") do
|
30
|
-
|
31
|
-
|
34
|
+
enqueue_concurrency = if enqueue_limit
|
35
|
+
# TODO: Why is `unscoped` necessary? Nested scope is bleeding into subsequent query?
|
36
|
+
GoodJob::Job.unscoped.where(concurrency_key: key).unfinished.advisory_unlocked.count
|
37
|
+
else
|
38
|
+
GoodJob::Job.unscoped.where(concurrency_key: key).unfinished.count
|
39
|
+
end
|
40
|
+
|
32
41
|
# The job has not yet been enqueued, so check if adding it will go over the limit
|
33
|
-
block.call unless enqueue_concurrency + 1 >
|
42
|
+
block.call unless enqueue_concurrency + 1 > (enqueue_limit || total_limit)
|
34
43
|
end
|
35
44
|
end
|
36
45
|
|
@@ -44,14 +53,17 @@ module GoodJob
|
|
44
53
|
# Don't attempt to enforce concurrency limits with other queue adapters.
|
45
54
|
next unless job.class.queue_adapter.is_a?(GoodJob::Adapter)
|
46
55
|
|
47
|
-
|
48
|
-
|
56
|
+
perform_limit = job.class.good_job_concurrency_config[:perform_limit] ||
|
57
|
+
job.class.good_job_concurrency_config[:total_limit]
|
58
|
+
|
59
|
+
has_limit = perform_limit.present? && (0...Float::INFINITY).cover?(perform_limit)
|
60
|
+
next unless has_limit
|
49
61
|
|
50
62
|
key = job.good_job_concurrency_key
|
51
63
|
next if key.blank?
|
52
64
|
|
53
65
|
GoodJob::Job.new.with_advisory_lock(key: key, function: "pg_advisory_lock") do
|
54
|
-
allowed_active_job_ids = GoodJob::Job.unscoped.where(concurrency_key: key).advisory_locked.order(Arel.sql("COALESCE(performed_at, scheduled_at, created_at) ASC")).limit(
|
66
|
+
allowed_active_job_ids = GoodJob::Job.unscoped.where(concurrency_key: key).advisory_locked.order(Arel.sql("COALESCE(performed_at, scheduled_at, created_at) ASC")).limit(perform_limit).pluck(:active_job_id)
|
55
67
|
# The current job has already been locked and will appear in the previous query
|
56
68
|
raise GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError unless allowed_active_job_ids.include? job.job_id
|
57
69
|
end
|
data/lib/good_job/lockable.rb
CHANGED
@@ -155,8 +155,7 @@ module GoodJob
|
|
155
155
|
advisory_unlock_session
|
156
156
|
else
|
157
157
|
records.each do |record|
|
158
|
-
key
|
159
|
-
record.advisory_unlock(key: key, function: advisory_unlockable_function(function))
|
158
|
+
record.advisory_unlock(key: record.lockable_column_key(column: column), function: advisory_unlockable_function(function))
|
160
159
|
end
|
161
160
|
end
|
162
161
|
end
|
@@ -286,6 +285,13 @@ module GoodJob
|
|
286
285
|
self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Locked?', binds).any?
|
287
286
|
end
|
288
287
|
|
288
|
+
# Tests whether this record does not have an advisory lock on it.
|
289
|
+
# @param key [String, Symbol] Key to test lock against
|
290
|
+
# @return [Boolean]
|
291
|
+
def advisory_unlocked?(key: lockable_key)
|
292
|
+
!advisory_locked?(key: key)
|
293
|
+
end
|
294
|
+
|
289
295
|
# Tests whether this record is locked by the current database session.
|
290
296
|
# @param key [String, Symbol] Key to test lock against
|
291
297
|
# @return [Boolean]
|
@@ -315,7 +321,13 @@ module GoodJob
|
|
315
321
|
# Default Advisory Lock key
|
316
322
|
# @return [String]
|
317
323
|
def lockable_key
|
318
|
-
|
324
|
+
lockable_column_key
|
325
|
+
end
|
326
|
+
|
327
|
+
# Default Advisory Lock key for column-based locking
|
328
|
+
# @return [String]
|
329
|
+
def lockable_column_key(column: self.class._advisory_lockable_column)
|
330
|
+
"#{self.class.table_name}-#{self[column]}"
|
319
331
|
end
|
320
332
|
|
321
333
|
delegate :pg_or_jdbc_query, to: :class
|
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: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|