good_job 2.0.5 → 2.1.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 +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
|