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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe9ce3416f1a816d3aa317139da71333f36c69dec121371c163a4965d61a2a6a
4
- data.tar.gz: 1d3cd7fd9bb43ccc0b0f94bbdf99cf2cd3865f4316ea8178aa67e4c3dfb9a97b
3
+ metadata.gz: a15eb306ad205685ef586d40c1f54cea489e900143e7080fa3da72b3272846a6
4
+ data.tar.gz: 4739ce28860fda800a785d32802a94083f7012496f465360240e895b15533879
5
5
  SHA512:
6
- metadata.gz: 6aa38c7714d0e5e959d0dfcd48fc9cfcbbae475d40cdd9d9f3aa452178b470de500cc1c0513c00a80e4d8b2e762c73b2a55b1233a638d6acb7c3320ecdb16975
7
- data.tar.gz: 96ec7cc7fbdb4cdc232a1ddfd046df5aa7976336e639d77fe3ff8dfdc9d2814cebdf88d8450aab2afa7f80a4b911282b96632fe04602c6dba5548a4435fce41f
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 does not support `throw :abort` in ActiveJob callbacks.
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 to be concurrently enqueued
359
- enqueue_limit: 2,
358
+ # Maximum number of unfinished jobs to allow with the concurrency key
359
+ total_limit: 1,
360
360
 
361
- # Maximum number of jobs with the concurrency key to be concurrently performed
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
- <th>GoodJob ID</th>
6
- <th>ActiveJob ID</th>
7
- <th>Job Class</th>
8
- <th>Queue</th>
9
- <th>Scheduled At</th>
10
- <th>Error</th>
11
- <th>ActiveJob Params</th>
12
- <th>Actions</th>
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&nbsp;
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
- limit = job.class.good_job_concurrency_config.fetch(:enqueue_limit, Float::INFINITY)
24
- next(block.call) if limit.blank? || (0...Float::INFINITY).exclude?(limit)
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
- # TODO: Why is `unscoped` necessary? Nested scope is bleeding into subsequent query?
31
- enqueue_concurrency = GoodJob::Job.unscoped.where(concurrency_key: key).unfinished.advisory_unlocked.count
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 > limit
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
- limit = job.class.good_job_concurrency_config.fetch(:perform_limit, Float::INFINITY)
48
- next if limit.blank? || (0...Float::INFINITY).exclude?(limit)
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(limit).pluck(:active_job_id)
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
@@ -155,8 +155,7 @@ module GoodJob
155
155
  advisory_unlock_session
156
156
  else
157
157
  records.each do |record|
158
- key = [table_name, record[_advisory_lockable_column]].join
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
- "#{self.class.table_name}-#{self[self.class._advisory_lockable_column]}"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '2.0.5'
4
+ VERSION = '2.1.0'
5
5
  end
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.5
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-06 00:00:00.000000000 Z
11
+ date: 2021-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob