good_job 2.9.4 → 2.9.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b97fd131b5bf8c808b9b40b42e0daad06c3e657e9f9f093bda45ef7b1ad003cd
4
- data.tar.gz: 4b988574b3e6c5d6877f835c7b9a2956099737a508b824da53e3e61005f4fcc7
3
+ metadata.gz: c65c653e99d224dcc5803bd851fbc8891ecafd9634c68e4b0feaee08eafc1ed0
4
+ data.tar.gz: 6b6d8a61555bfacdf6ba82e69afa48a3d05c1c9d9239770cea86878067151727
5
5
  SHA512:
6
- metadata.gz: caf7be40dd4b22cd6461e5dc8f83349ea377cdf7edfd0082d85ec35a9636ee58d1e4d890ba7d6aae9cb81e1357c1e3d709501ff68d173f3443d399903f4185ac
7
- data.tar.gz: 913bcd9082c2a66d48e85af5d8f8bab86fdf355636a59fe1a4328f77f0fc952b13ee722d9b867238bd4da6dd3301820f2f5dd1407758f7cf23ec7152edd149f7
6
+ metadata.gz: 9135ae904e9031c97d9029af23d15b504b03383fbff4c43634416199a130d04ddafbd121ababe7f9675d997d1b5be8db37d098e060f4a51193035abe087052c8
7
+ data.tar.gz: 30235ec67f81832b6c9b7401a01c7e167d239fef9d38b5be4d83d56c98cb061bb8737432092bce7e18e7136f67c4df14dc917f83c227f3d0e83b578b9fe58468
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.9.5](https://github.com/bensheldon/good_job/tree/v2.9.5) (2022-02-07)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.9.4...v2.9.5)
6
+
7
+ **Fixed bugs:**
8
+
9
+ - Transactions in "aborting" threads do not commit; causes GoodJob::Process record not destroyed on exit [\#489](https://github.com/bensheldon/good_job/issues/489)
10
+ - Deserialize ActiveJob arguments when manually retrying a job [\#513](https://github.com/bensheldon/good_job/pull/513) ([bensheldon](https://github.com/bensheldon))
11
+
12
+ **Closed issues:**
13
+
14
+ - Concurrency key proc is missing `arguments` when retrying a discarded job. [\#512](https://github.com/bensheldon/good_job/issues/512)
15
+ - Cron Schedule not visible in dashboard [\#496](https://github.com/bensheldon/good_job/issues/496)
16
+
17
+ **Merged pull requests:**
18
+
19
+ - Rename methods to `advisory_lock_key` and allow it to take a block instead of `with_advisory_lock` [\#511](https://github.com/bensheldon/good_job/pull/511) ([bensheldon](https://github.com/bensheldon))
20
+ - README: Limiting concurrency - fetch symbol instead of string [\#510](https://github.com/bensheldon/good_job/pull/510) ([BenSto](https://github.com/BenSto))
21
+ - Add arbitrary lock on class level too [\#499](https://github.com/bensheldon/good_job/pull/499) ([pandwoter](https://github.com/pandwoter))
22
+
3
23
  ## [v2.9.4](https://github.com/bensheldon/good_job/tree/v2.9.4) (2022-01-31)
4
24
 
5
25
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.9.3...v2.9.4)
data/README.md CHANGED
@@ -399,11 +399,9 @@ class MyJob < ApplicationJob
399
399
 
400
400
  # A unique key to be globally locked against.
401
401
  # Can be String or Lambda/Proc that is invoked in the context of the job.
402
- # Note: Arguments passed to #perform_later must be accessed through `arguments` method.
403
- key: -> { "Unique-#{arguments.first}" } # MyJob.perform_later("Alice") => "Unique-Alice"
404
-
405
- # If the method uses named parameters, they can be accessed like so:
406
- # key: -> { "Unique-#{arguments.first['name']}" } # MyJob.perform_later(name: "Alice")
402
+ # Note: Arguments passed to #perform_later can be accessed through ActiveJob's `arguments` method
403
+ # which is an array containing positional arguments and, optionally, a kwarg hash.
404
+ key: -> { "Unique-#{arguments.first}-#{arguments.last[:version]}" } # MyJob.perform_later("Alice", version: 'v2') => "Unique-Alice-v2"
407
405
  )
408
406
 
409
407
  def perform(first_name)
@@ -30,7 +30,7 @@ module GoodJob
30
30
  key = job.good_job_concurrency_key
31
31
  next(block.call) if key.blank?
32
32
 
33
- GoodJob::Execution.new.with_advisory_lock(key: key, function: "pg_advisory_lock") do
33
+ GoodJob::Execution.advisory_lock_key(key, function: "pg_advisory_lock") do
34
34
  enqueue_concurrency = if enqueue_limit
35
35
  GoodJob::Execution.where(concurrency_key: key).unfinished.advisory_unlocked.count
36
36
  else
@@ -61,7 +61,7 @@ module GoodJob
61
61
  key = job.good_job_concurrency_key
62
62
  next if key.blank?
63
63
 
64
- GoodJob::Execution.new.with_advisory_lock(key: key, function: "pg_advisory_lock") do
64
+ GoodJob::Execution.advisory_lock_key(key, function: "pg_advisory_lock") do
65
65
  allowed_active_job_ids = GoodJob::Execution.where(concurrency_key: key).advisory_locked.order(Arel.sql("COALESCE(performed_at, scheduled_at, created_at) ASC")).limit(perform_limit).pluck(:active_job_id)
66
66
  # The current job has already been locked and will appear in the previous query
67
67
  raise GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError unless allowed_active_job_ids.include? job.job_id
@@ -267,7 +267,9 @@ module GoodJob
267
267
  end
268
268
 
269
269
  def active_job
270
- ActiveJob::Base.deserialize(active_job_data)
270
+ ActiveJob::Base.deserialize(active_job_data).tap do |aj|
271
+ aj.send(:deserialize_arguments_if_needed)
272
+ end
271
273
  end
272
274
 
273
275
  private
@@ -148,6 +148,7 @@ module GoodJob
148
148
  raise ArgumentError, "Must provide a block" unless block_given?
149
149
 
150
150
  records = advisory_lock(column: column, function: function).to_a
151
+
151
152
  begin
152
153
  unscoped { yield(records) }
153
154
  ensure
@@ -161,6 +162,53 @@ module GoodJob
161
162
  end
162
163
  end
163
164
 
165
+ # Acquires an advisory lock on this record if it is not already locked by
166
+ # another database session. Be careful to ensure you release the lock when
167
+ # you are done with {#advisory_unlock_key} to release all remaining locks.
168
+ # @param key [String, Symbol] Key to Advisory Lock against
169
+ # @param function [String, Symbol] Postgres Advisory Lock function name to use
170
+ # @return [Boolean] whether the lock was acquired.
171
+ def advisory_lock_key(key, function: advisory_lockable_function)
172
+ query = if function.include? "_try_"
173
+ <<~SQL.squish
174
+ SELECT #{function}(('x'||substr(md5($1::text), 1, 16))::bit(64)::bigint) AS locked
175
+ SQL
176
+ else
177
+ <<~SQL.squish
178
+ SELECT #{function}(('x'||substr(md5($1::text), 1, 16))::bit(64)::bigint)::text AS locked
179
+ SQL
180
+ end
181
+
182
+ binds = [
183
+ ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new),
184
+ ]
185
+ locked = connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Lock', binds).first['locked']
186
+ return locked unless block_given?
187
+ return nil unless locked
188
+
189
+ begin
190
+ yield
191
+ ensure
192
+ advisory_unlock_key(key, function: advisory_unlockable_function(function))
193
+ end
194
+ end
195
+
196
+ # Releases an advisory lock on this record if it is locked by this database
197
+ # session. Note that advisory locks stack, so you must call
198
+ # {#advisory_unlock} and {#advisory_lock} the same number of times.
199
+ # @param key [String, Symbol] Key to lock against
200
+ # @param function [String, Symbol] Postgres Advisory Lock function name to use
201
+ # @return [Boolean] whether the lock was released.
202
+ def advisory_unlock_key(key, function: advisory_unlockable_function)
203
+ query = <<~SQL.squish
204
+ SELECT #{function}(('x'||substr(md5($1::text), 1, 16))::bit(64)::bigint) AS unlocked
205
+ SQL
206
+ binds = [
207
+ ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new),
208
+ ]
209
+ connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Unlock', binds).first['unlocked']
210
+ end
211
+
164
212
  def _advisory_lockable_column
165
213
  advisory_lockable_column || primary_key
166
214
  end
@@ -205,20 +253,7 @@ module GoodJob
205
253
  # @param function [String, Symbol] Postgres Advisory Lock function name to use
206
254
  # @return [Boolean] whether the lock was acquired.
207
255
  def advisory_lock(key: lockable_key, function: advisory_lockable_function)
208
- query = if function.include? "_try_"
209
- <<~SQL.squish
210
- SELECT #{function}(('x'||substr(md5($1::text), 1, 16))::bit(64)::bigint) AS locked
211
- SQL
212
- else
213
- <<~SQL.squish
214
- SELECT #{function}(('x'||substr(md5($1::text), 1, 16))::bit(64)::bigint)::text AS locked
215
- SQL
216
- end
217
-
218
- binds = [
219
- ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new),
220
- ]
221
- self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Lock', binds).first['locked']
256
+ self.class.advisory_lock_key(key, function: function)
222
257
  end
223
258
 
224
259
  # Releases an advisory lock on this record if it is locked by this database
@@ -228,13 +263,7 @@ module GoodJob
228
263
  # @param function [String, Symbol] Postgres Advisory Lock function name to use
229
264
  # @return [Boolean] whether the lock was released.
230
265
  def advisory_unlock(key: lockable_key, function: self.class.advisory_unlockable_function(advisory_lockable_function))
231
- query = <<~SQL.squish
232
- SELECT #{function}(('x'||substr(md5($1::text), 1, 16))::bit(64)::bigint) AS unlocked
233
- SQL
234
- binds = [
235
- ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new),
236
- ]
237
- self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Unlock', binds).first['unlocked']
266
+ self.class.advisory_unlock_key(key, function: function)
238
267
  end
239
268
 
240
269
  # Acquires an advisory lock on this record or raises
@@ -245,8 +274,7 @@ module GoodJob
245
274
  # @raise [RecordAlreadyAdvisoryLockedError]
246
275
  # @return [Boolean] +true+
247
276
  def advisory_lock!(key: lockable_key, function: advisory_lockable_function)
248
- result = advisory_lock(key: key, function: function)
249
- result || raise(RecordAlreadyAdvisoryLockedError)
277
+ self.class.advisory_lock_key(key, function: function) || raise(RecordAlreadyAdvisoryLockedError)
250
278
  end
251
279
 
252
280
  # Acquires an advisory lock on this record and safely releases it after the
@@ -266,9 +294,11 @@ module GoodJob
266
294
  raise ArgumentError, "Must provide a block" unless block_given?
267
295
 
268
296
  advisory_lock!(key: key, function: function)
269
- yield
270
- ensure
271
- advisory_unlock(key: key, function: self.class.advisory_unlockable_function(function)) unless $ERROR_INFO.is_a? RecordAlreadyAdvisoryLockedError
297
+ begin
298
+ yield
299
+ ensure
300
+ advisory_unlock(key: key, function: self.class.advisory_unlockable_function(function))
301
+ end
272
302
  end
273
303
 
274
304
  # Tests whether this record has an advisory lock on it.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '2.9.4'
4
+ VERSION = '2.9.5'
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.9.4
4
+ version: 2.9.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-01-31 00:00:00.000000000 Z
11
+ date: 2022-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob