good_job 1.10.0 → 1.11.2
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 +55 -0
- data/README.md +30 -0
- data/lib/good_job/active_job_extensions.rb +4 -0
- data/lib/good_job/active_job_extensions/concurrency.rb +68 -0
- data/lib/good_job/current_execution.rb +12 -5
- data/lib/good_job/job.rb +21 -0
- data/lib/good_job/lockable.rb +31 -24
- data/lib/good_job/notifier.rb +11 -3
- data/lib/good_job/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d78c670393e4cddbedfe9512fdda95855709d830618cd63cfde7e724f26ce6c8
|
4
|
+
data.tar.gz: 494d3b0d6ec2b05d0bcc3386c14baca6071635647ed808a9b3543b5f1ebc4bfb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bef7307febc465eab519c44a214287d5203bf0a024d83e46160dbbaf94b3a0fcd1317fbe99e446d9b0f01fb2bc8845a6dfe722fb123020aab96fe31b0c189d71
|
7
|
+
data.tar.gz: 737c03b9ad35169c9db43070abca6f644816876217fd333831db568f43eca6738dd9327eb7a403eb901ab6643e06445ba7764453d3f9b05e9e7c1159f7ba0963
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,60 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.11.2](https://github.com/bensheldon/good_job/tree/v1.11.2) (2021-07-20)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.11.1...v1.11.2)
|
6
|
+
|
7
|
+
**Fixed bugs:**
|
8
|
+
|
9
|
+
- Notifier waits to retry listening when database is unavailable [\#301](https://github.com/bensheldon/good_job/pull/301) ([bensheldon](https://github.com/bensheldon))
|
10
|
+
|
11
|
+
**Closed issues:**
|
12
|
+
|
13
|
+
- Handle database connection drops [\#296](https://github.com/bensheldon/good_job/issues/296)
|
14
|
+
- Using the `async` worker results in `ActiveModel::UnknownAttributeError unknown attribute 'create_with_advisory_lock' for GoodJob::Job`. [\#290](https://github.com/bensheldon/good_job/issues/290)
|
15
|
+
|
16
|
+
**Merged pull requests:**
|
17
|
+
|
18
|
+
- Rename development and test databases to be `good_job` [\#300](https://github.com/bensheldon/good_job/pull/300) ([bensheldon](https://github.com/bensheldon))
|
19
|
+
- Move generators spec into top-level spec directory; update dependencies [\#299](https://github.com/bensheldon/good_job/pull/299) ([bensheldon](https://github.com/bensheldon))
|
20
|
+
|
21
|
+
## [v1.11.1](https://github.com/bensheldon/good_job/tree/v1.11.1) (2021-07-07)
|
22
|
+
|
23
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.11.0...v1.11.1)
|
24
|
+
|
25
|
+
**Closed issues:**
|
26
|
+
|
27
|
+
- Database connection required while loading the code on 1.10.x [\#291](https://github.com/bensheldon/good_job/issues/291)
|
28
|
+
|
29
|
+
**Merged pull requests:**
|
30
|
+
|
31
|
+
- Defer accessing ActiveRecord `primary_key` in Lockable [\#293](https://github.com/bensheldon/good_job/pull/293) ([bensheldon](https://github.com/bensheldon))
|
32
|
+
|
33
|
+
## [v1.11.0](https://github.com/bensheldon/good_job/tree/v1.11.0) (2021-07-07)
|
34
|
+
|
35
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.10.1...v1.11.0)
|
36
|
+
|
37
|
+
**Implemented enhancements:**
|
38
|
+
|
39
|
+
- Add concurrency extension for ActiveJob [\#281](https://github.com/bensheldon/good_job/pull/281) ([bensheldon](https://github.com/bensheldon))
|
40
|
+
|
41
|
+
**Closed issues:**
|
42
|
+
|
43
|
+
- Investigate GoodJob concurrency [\#289](https://github.com/bensheldon/good_job/issues/289)
|
44
|
+
- Problem with migrating database on 1.10.0 [\#287](https://github.com/bensheldon/good_job/issues/287)
|
45
|
+
- Support migration --database option for install task? [\#267](https://github.com/bensheldon/good_job/issues/267)
|
46
|
+
- Add GoodJob to Ruby Toolbox [\#243](https://github.com/bensheldon/good_job/issues/243)
|
47
|
+
- Custom advisory locks to prevent certain jobs from being worked on concurrently? [\#206](https://github.com/bensheldon/good_job/issues/206)
|
48
|
+
|
49
|
+
## [v1.10.1](https://github.com/bensheldon/good_job/tree/v1.10.1) (2021-06-30)
|
50
|
+
|
51
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.10.0...v1.10.1)
|
52
|
+
|
53
|
+
**Merged pull requests:**
|
54
|
+
|
55
|
+
- Remove `FOR UPDATE SKIP LOCKED` from job locking sql statement [\#288](https://github.com/bensheldon/good_job/pull/288) ([bensheldon](https://github.com/bensheldon))
|
56
|
+
- Update GH Test Matrix with latest JRuby 9.2.19.0 [\#283](https://github.com/bensheldon/good_job/pull/283) ([tedhexaflow](https://github.com/tedhexaflow))
|
57
|
+
|
3
58
|
## [v1.10.0](https://github.com/bensheldon/good_job/tree/v1.10.0) (2021-06-29)
|
4
59
|
|
5
60
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.9.6...v1.10.0)
|
data/README.md
CHANGED
@@ -38,6 +38,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
|
|
38
38
|
- [Configuration options](#configuration-options)
|
39
39
|
- [Global options](#global-options)
|
40
40
|
- [Dashboard](#dashboard)
|
41
|
+
- [ActiveJob Concurrency](#activejob-concurrency)
|
41
42
|
- [Updating](#updating)
|
42
43
|
- [Go deeper](#go-deeper)
|
43
44
|
- [Exceptions, retries, and reliability](#exceptions-retries-and-reliability)
|
@@ -319,6 +320,35 @@ GoodJob includes a Dashboard as a mountable `Rails::Engine`.
|
|
319
320
|
end
|
320
321
|
```
|
321
322
|
|
323
|
+
### ActiveJob Concurrency
|
324
|
+
|
325
|
+
GoodJob can extend ActiveJob to provide limits on concurrently running jobs, either at time of _enqueue_ or at _perform_.
|
326
|
+
|
327
|
+
**Note:** Limiting concurrency at _enqueue_ requires Rails 6.0+ because Rails 5.2 does not support `throw :abort` in ActiveJob callbacks.
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
class MyJob < ApplicationJob
|
331
|
+
include GoodJob::ActiveJobExtensions::Concurrency
|
332
|
+
|
333
|
+
good_job_control_concurrency_with(
|
334
|
+
# Maximum number of jobs with the concurrency key to be concurrently enqueued
|
335
|
+
enqueue_limit: 2,
|
336
|
+
|
337
|
+
# Maximum number of jobs with the concurrency key to be concurrently performed
|
338
|
+
perform_limit: 1,
|
339
|
+
|
340
|
+
# A unique key to be globally locked against.
|
341
|
+
# Can be String or Lambda/Proc that is invoked in the context of the job.
|
342
|
+
# Note: Arguments passed to #perform_later must be accessed through `arguments` method.
|
343
|
+
key: -> { "Unique-#{arguments.first}" } # MyJob.perform_later("Alice") => "Unique-Alice"
|
344
|
+
)
|
345
|
+
|
346
|
+
def perform(first_name)
|
347
|
+
# do work
|
348
|
+
end
|
349
|
+
end
|
350
|
+
```
|
351
|
+
|
322
352
|
### Updating
|
323
353
|
|
324
354
|
GoodJob follows semantic versioning, though updates may be encouraged through deprecation warnings in minor versions.
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module GoodJob
|
2
|
+
module ActiveJobExtensions
|
3
|
+
module Concurrency
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
ConcurrencyExceededError = Class.new(StandardError)
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :good_job_concurrency_config, instance_accessor: false, default: {}
|
10
|
+
|
11
|
+
before_enqueue do |job|
|
12
|
+
# Always allow jobs to be retried because the current job's execution will complete momentarily
|
13
|
+
next if CurrentExecution.active_job_id == job.job_id
|
14
|
+
|
15
|
+
limit = job.class.good_job_concurrency_config.fetch(:enqueue_limit, Float::INFINITY)
|
16
|
+
next if limit.blank? || (0...Float::INFINITY).exclude?(limit)
|
17
|
+
|
18
|
+
key = job.good_job_concurrency_key
|
19
|
+
next if key.blank?
|
20
|
+
|
21
|
+
GoodJob::Job.new.with_advisory_lock(key: key, function: "pg_advisory_lock") do
|
22
|
+
# TODO: Why is `unscoped` necessary? Nested scope is bleeding into subsequent query?
|
23
|
+
enqueue_concurrency = GoodJob::Job.unscoped.where(concurrency_key: key).unfinished.count
|
24
|
+
# The job has not yet been enqueued, so check if adding it will go over the limit
|
25
|
+
throw :abort if enqueue_concurrency + 1 > limit
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
retry_on(
|
30
|
+
GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError,
|
31
|
+
attempts: Float::INFINITY,
|
32
|
+
wait: :exponentially_longer
|
33
|
+
)
|
34
|
+
|
35
|
+
before_perform do |job|
|
36
|
+
limit = job.class.good_job_concurrency_config.fetch(:perform_limit, Float::INFINITY)
|
37
|
+
next if limit.blank? || (0...Float::INFINITY).exclude?(limit)
|
38
|
+
|
39
|
+
key = job.good_job_concurrency_key
|
40
|
+
next if key.blank?
|
41
|
+
|
42
|
+
GoodJob::Job.new.with_advisory_lock(key: key, function: "pg_advisory_lock") do
|
43
|
+
perform_concurrency = GoodJob::Job.unscoped.where(concurrency_key: key).advisory_locked.count
|
44
|
+
# The current job has already been locked and will appear in the previous query
|
45
|
+
raise GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError if perform_concurrency > limit
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class_methods do
|
51
|
+
def good_job_control_concurrency_with(config)
|
52
|
+
self.good_job_concurrency_config = config
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def good_job_concurrency_key
|
57
|
+
key = self.class.good_job_concurrency_config[:key]
|
58
|
+
return if key.blank?
|
59
|
+
|
60
|
+
if key.respond_to? :call
|
61
|
+
instance_exec(&key)
|
62
|
+
else
|
63
|
+
key
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -4,11 +4,11 @@ module GoodJob
|
|
4
4
|
# Thread-local attributes for passing values from Instrumentation.
|
5
5
|
# (Cannot use ActiveSupport::CurrentAttributes because ActiveJob resets it)
|
6
6
|
module CurrentExecution
|
7
|
-
# @!attribute [rw]
|
7
|
+
# @!attribute [rw] active_job_id
|
8
8
|
# @!scope class
|
9
|
-
#
|
10
|
-
# @return [
|
11
|
-
thread_mattr_accessor :
|
9
|
+
# ActiveJob ID
|
10
|
+
# @return [String, nil]
|
11
|
+
thread_mattr_accessor :active_job_id
|
12
12
|
|
13
13
|
# @!attribute [rw] error_on_discard
|
14
14
|
# @!scope class
|
@@ -16,11 +16,18 @@ module GoodJob
|
|
16
16
|
# @return [Exception, nil]
|
17
17
|
thread_mattr_accessor :error_on_discard
|
18
18
|
|
19
|
+
# @!attribute [rw] error_on_retry
|
20
|
+
# @!scope class
|
21
|
+
# Error captured by retry_on
|
22
|
+
# @return [Exception, nil]
|
23
|
+
thread_mattr_accessor :error_on_retry
|
24
|
+
|
19
25
|
# Resets attributes
|
20
26
|
# @return [void]
|
21
27
|
def self.reset
|
22
|
-
self.
|
28
|
+
self.active_job_id = nil
|
23
29
|
self.error_on_discard = nil
|
30
|
+
self.error_on_retry = nil
|
24
31
|
end
|
25
32
|
|
26
33
|
# @return [Integer] Current process ID
|
data/lib/good_job/job.rb
CHANGED
@@ -15,6 +15,7 @@ module GoodJob
|
|
15
15
|
DEFAULT_PRIORITY = 0
|
16
16
|
|
17
17
|
self.table_name = 'good_jobs'.freeze
|
18
|
+
self.advisory_lockable_column = 'id'.freeze
|
18
19
|
|
19
20
|
attr_readonly :serialized_params
|
20
21
|
|
@@ -219,6 +220,21 @@ module GoodJob
|
|
219
220
|
DEPRECATION
|
220
221
|
end
|
221
222
|
|
223
|
+
if column_names.include?('concurrency_key')
|
224
|
+
good_job_args[:concurrency_key] = active_job.good_job_concurrency_key if active_job.respond_to?(:good_job_concurrency_key)
|
225
|
+
else
|
226
|
+
ActiveSupport::Deprecation.warn(<<~DEPRECATION)
|
227
|
+
GoodJob has pending database migrations. To create the migration files, run:
|
228
|
+
|
229
|
+
rails generate good_job:update
|
230
|
+
|
231
|
+
To apply the migration files, run:
|
232
|
+
|
233
|
+
rails db:migrate
|
234
|
+
|
235
|
+
DEPRECATION
|
236
|
+
end
|
237
|
+
|
222
238
|
good_job = GoodJob::Job.new(**good_job_args)
|
223
239
|
|
224
240
|
instrument_payload[:good_job] = good_job
|
@@ -264,6 +280,10 @@ module GoodJob
|
|
264
280
|
self.class.unscoped.unfinished.owns_advisory_locked.exists?(id: id)
|
265
281
|
end
|
266
282
|
|
283
|
+
def active_job_id
|
284
|
+
super || serialized_params['job_id']
|
285
|
+
end
|
286
|
+
|
267
287
|
private
|
268
288
|
|
269
289
|
# @return [ExecutionResult]
|
@@ -273,6 +293,7 @@ module GoodJob
|
|
273
293
|
)
|
274
294
|
|
275
295
|
GoodJob::CurrentExecution.reset
|
296
|
+
GoodJob::CurrentExecution.active_job_id = active_job_id
|
276
297
|
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, process_id: GoodJob::CurrentExecution.process_id, thread_name: GoodJob::CurrentExecution.thread_name }) do
|
277
298
|
value = ActiveJob::Base.execute(params)
|
278
299
|
|
data/lib/good_job/lockable.rb
CHANGED
@@ -23,20 +23,20 @@ module GoodJob
|
|
23
23
|
|
24
24
|
included do
|
25
25
|
# Default column to be used when creating Advisory Locks
|
26
|
-
|
26
|
+
class_attribute :advisory_lockable_column, instance_accessor: false, default: Concurrent::Delay.new { primary_key }
|
27
27
|
|
28
28
|
# Default Postgres function to be used for Advisory Locks
|
29
|
-
|
29
|
+
class_attribute :advisory_lockable_function, default: "pg_try_advisory_lock"
|
30
30
|
|
31
31
|
# Attempt to acquire an advisory lock on the selected records and
|
32
32
|
# return only those records for which a lock could be acquired.
|
33
|
-
# @!method advisory_lock(column:
|
33
|
+
# @!method advisory_lock(column: _advisory_lockable_column, function: advisory_lockable_function)
|
34
34
|
# @!scope class
|
35
35
|
# @param column [String, Symbol] column values to Advisory Lock against
|
36
36
|
# @param function [String, Symbol] Postgres Advisory Lock function name to use
|
37
37
|
# @return [ActiveRecord::Relation]
|
38
38
|
# A relation selecting only the records that were locked.
|
39
|
-
scope :advisory_lock, (lambda do |column:
|
39
|
+
scope :advisory_lock, (lambda do |column: _advisory_lockable_column, function: advisory_lockable_function|
|
40
40
|
original_query = self
|
41
41
|
|
42
42
|
cte_table = Arel::Table.new(:rows)
|
@@ -48,15 +48,9 @@ module GoodJob
|
|
48
48
|
end
|
49
49
|
|
50
50
|
composed_cte = Arel::Nodes::As.new(cte_table, Arel::Nodes::SqlLiteral.new([cte_type, "(", cte_query.to_sql, ")"].join(' ')))
|
51
|
-
|
52
|
-
# In addition to an advisory lock, there is also a FOR UPDATE SKIP LOCKED
|
53
|
-
# because this causes the query to skip jobs that were completed (and deleted)
|
54
|
-
# by another session in the time since the table snapshot was taken.
|
55
|
-
# In rare cases under high concurrency levels, leaving this out can result in double executions.
|
56
51
|
query = cte_table.project(cte_table[:id])
|
57
52
|
.with(composed_cte)
|
58
53
|
.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 }])))
|
59
|
-
.lock(Arel.sql("FOR UPDATE SKIP LOCKED"))
|
60
54
|
|
61
55
|
limit = original_query.arel.ast.limit
|
62
56
|
query.limit = limit.value if limit.present?
|
@@ -70,13 +64,13 @@ module GoodJob
|
|
70
64
|
#
|
71
65
|
# For details on +pg_locks+, see
|
72
66
|
# {https://www.postgresql.org/docs/current/view-pg-locks.html}.
|
73
|
-
# @!method joins_advisory_locks(column:
|
67
|
+
# @!method joins_advisory_locks(column: _advisory_lockable_column)
|
74
68
|
# @!scope class
|
75
69
|
# @param column [String, Symbol] column values to Advisory Lock against
|
76
70
|
# @return [ActiveRecord::Relation]
|
77
71
|
# @example Get the records that have a session awaiting a lock:
|
78
72
|
# MyLockableRecord.joins_advisory_locks.where("pg_locks.granted = ?", false)
|
79
|
-
scope :joins_advisory_locks, (lambda do |column:
|
73
|
+
scope :joins_advisory_locks, (lambda do |column: _advisory_lockable_column|
|
80
74
|
join_sql = <<~SQL.squish
|
81
75
|
LEFT JOIN pg_locks ON pg_locks.locktype = 'advisory'
|
82
76
|
AND pg_locks.objsubid = 1
|
@@ -88,26 +82,26 @@ module GoodJob
|
|
88
82
|
end)
|
89
83
|
|
90
84
|
# Find records that do not have an advisory lock on them.
|
91
|
-
# @!method advisory_unlocked(column:
|
85
|
+
# @!method advisory_unlocked(column: _advisory_lockable_column)
|
92
86
|
# @!scope class
|
93
87
|
# @param column [String, Symbol] column values to Advisory Lock against
|
94
88
|
# @return [ActiveRecord::Relation]
|
95
|
-
scope :advisory_unlocked, ->(column:
|
89
|
+
scope :advisory_unlocked, ->(column: _advisory_lockable_column) { joins_advisory_locks(column: column).where(pg_locks: { locktype: nil }) }
|
96
90
|
|
97
91
|
# Find records that have an advisory lock on them.
|
98
|
-
# @!method advisory_locked(column:
|
92
|
+
# @!method advisory_locked(column: _advisory_lockable_column)
|
99
93
|
# @!scope class
|
100
94
|
# @param column [String, Symbol] column values to Advisory Lock against
|
101
95
|
# @return [ActiveRecord::Relation]
|
102
|
-
scope :advisory_locked, ->(column:
|
96
|
+
scope :advisory_locked, ->(column: _advisory_lockable_column) { joins_advisory_locks(column: column).where.not(pg_locks: { locktype: nil }) }
|
103
97
|
|
104
98
|
# Find records with advisory locks owned by the current Postgres
|
105
99
|
# session/connection.
|
106
|
-
# @!method advisory_locked(column:
|
100
|
+
# @!method advisory_locked(column: _advisory_lockable_column)
|
107
101
|
# @!scope class
|
108
102
|
# @param column [String, Symbol] column values to Advisory Lock against
|
109
103
|
# @return [ActiveRecord::Relation]
|
110
|
-
scope :owns_advisory_locked, ->(column:
|
104
|
+
scope :owns_advisory_locked, ->(column: _advisory_lockable_column) { joins_advisory_locks(column: column).where('"pg_locks"."pid" = pg_backend_pid()') }
|
111
105
|
|
112
106
|
# Whether an advisory lock should be acquired in the same transaction
|
113
107
|
# that created the record.
|
@@ -149,7 +143,7 @@ module GoodJob
|
|
149
143
|
# MyLockableRecord.order(created_at: :asc).limit(2).with_advisory_lock do |record|
|
150
144
|
# do_something_with record
|
151
145
|
# end
|
152
|
-
def with_advisory_lock(column:
|
146
|
+
def with_advisory_lock(column: _advisory_lockable_column, function: advisory_lockable_function, unlock_session: false)
|
153
147
|
raise ArgumentError, "Must provide a block" unless block_given?
|
154
148
|
|
155
149
|
records = advisory_lock(column: column, function: function).to_a
|
@@ -160,13 +154,19 @@ module GoodJob
|
|
160
154
|
advisory_unlock_session
|
161
155
|
else
|
162
156
|
records.each do |record|
|
163
|
-
key = [table_name, record[
|
157
|
+
key = [table_name, record[_advisory_lockable_column]].join
|
164
158
|
record.advisory_unlock(key: key, function: advisory_unlockable_function(function))
|
165
159
|
end
|
166
160
|
end
|
167
161
|
end
|
168
162
|
end
|
169
163
|
|
164
|
+
# Allow advisory_lockable_column to be a `Concurrent::Delay`
|
165
|
+
def _advisory_lockable_column
|
166
|
+
column = advisory_lockable_column
|
167
|
+
column.respond_to?(:value) ? column.value : column
|
168
|
+
end
|
169
|
+
|
170
170
|
def supports_cte_materialization_specifiers?
|
171
171
|
return @_supports_cte_materialization_specifiers if defined?(@_supports_cte_materialization_specifiers)
|
172
172
|
|
@@ -207,9 +207,16 @@ module GoodJob
|
|
207
207
|
# @param function [String, Symbol] Postgres Advisory Lock function name to use
|
208
208
|
# @return [Boolean] whether the lock was acquired.
|
209
209
|
def advisory_lock(key: lockable_key, function: advisory_lockable_function)
|
210
|
-
query =
|
211
|
-
|
212
|
-
|
210
|
+
query = if function.include? "_try_"
|
211
|
+
<<~SQL.squish
|
212
|
+
SELECT #{function}(('x'||substr(md5($1::text), 1, 16))::bit(64)::bigint) AS locked
|
213
|
+
SQL
|
214
|
+
else
|
215
|
+
<<~SQL.squish
|
216
|
+
SELECT #{function}(('x'||substr(md5($1::text), 1, 16))::bit(64)::bigint)::text AS locked
|
217
|
+
SQL
|
218
|
+
end
|
219
|
+
|
213
220
|
binds = [[nil, key]]
|
214
221
|
self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Lock', binds).first['locked']
|
215
222
|
end
|
@@ -307,7 +314,7 @@ module GoodJob
|
|
307
314
|
# Default Advisory Lock key
|
308
315
|
# @return [String]
|
309
316
|
def lockable_key
|
310
|
-
[self.class.table_name, self[self.class.
|
317
|
+
[self.class.table_name, self[self.class._advisory_lockable_column]].join
|
311
318
|
end
|
312
319
|
|
313
320
|
delegate :pg_or_jdbc_query, to: :class
|
data/lib/good_job/notifier.rb
CHANGED
@@ -24,6 +24,8 @@ module GoodJob # :nodoc:
|
|
24
24
|
max_queue: 1,
|
25
25
|
fallback_policy: :discard,
|
26
26
|
}.freeze
|
27
|
+
# Seconds to wait if database cannot be connected to
|
28
|
+
RECONNECT_INTERVAL = 5
|
27
29
|
# Seconds to block while LISTENing for a message
|
28
30
|
WAIT_INTERVAL = 1
|
29
31
|
|
@@ -114,7 +116,13 @@ module GoodJob # :nodoc:
|
|
114
116
|
ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: thread_error })
|
115
117
|
end
|
116
118
|
|
117
|
-
|
119
|
+
return if shutdown?
|
120
|
+
|
121
|
+
if thread_error.is_a?(ActiveRecord::ConnectionNotEstablished) || thread_error.is_a?(ActiveRecord::StatementInvalid)
|
122
|
+
listen(delay: RECONNECT_INTERVAL)
|
123
|
+
else
|
124
|
+
listen
|
125
|
+
end
|
118
126
|
end
|
119
127
|
|
120
128
|
private
|
@@ -125,8 +133,8 @@ module GoodJob # :nodoc:
|
|
125
133
|
@executor = Concurrent::ThreadPoolExecutor.new(EXECUTOR_OPTIONS)
|
126
134
|
end
|
127
135
|
|
128
|
-
def listen
|
129
|
-
future = Concurrent::
|
136
|
+
def listen(delay: 0)
|
137
|
+
future = Concurrent::ScheduledTask.new(delay, args: [@recipients, executor, @listening], executor: @executor) do |thr_recipients, thr_executor, thr_listening|
|
130
138
|
with_listen_connection do |conn|
|
131
139
|
ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
|
132
140
|
conn.async_exec("LISTEN #{CHANNEL}").clear
|
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: 1.
|
4
|
+
version: 1.11.2
|
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-
|
11
|
+
date: 2021-07-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -362,6 +362,8 @@ files:
|
|
362
362
|
- lib/generators/good_job/templates/update/migrations/03_add_active_job_id_index_and_concurrency_key_index_to_good_jobs.rb
|
363
363
|
- lib/generators/good_job/update_generator.rb
|
364
364
|
- lib/good_job.rb
|
365
|
+
- lib/good_job/active_job_extensions.rb
|
366
|
+
- lib/good_job/active_job_extensions/concurrency.rb
|
365
367
|
- lib/good_job/adapter.rb
|
366
368
|
- lib/good_job/cli.rb
|
367
369
|
- lib/good_job/configuration.rb
|