sidekiq-unique-jobs 8.0.5 → 8.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -5
  3. data/README.md +1 -3
  4. data/lib/sidekiq_unique_jobs/batch_delete.rb +1 -1
  5. data/lib/sidekiq_unique_jobs/changelog.rb +1 -1
  6. data/lib/sidekiq_unique_jobs/config.rb +4 -4
  7. data/lib/sidekiq_unique_jobs/constants.rb +1 -0
  8. data/lib/sidekiq_unique_jobs/job.rb +4 -4
  9. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +1 -1
  10. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +2 -0
  11. data/lib/sidekiq_unique_jobs/lock.rb +13 -10
  12. data/lib/sidekiq_unique_jobs/locksmith.rb +3 -3
  13. data/lib/sidekiq_unique_jobs/lua/unlock.lua +16 -9
  14. data/lib/sidekiq_unique_jobs/middleware/client.rb +1 -1
  15. data/lib/sidekiq_unique_jobs/middleware/server.rb +1 -1
  16. data/lib/sidekiq_unique_jobs/middleware.rb +5 -5
  17. data/lib/sidekiq_unique_jobs/orphans/manager.rb +6 -5
  18. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +2 -1
  19. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +26 -7
  20. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +8 -0
  21. data/lib/sidekiq_unique_jobs/script/caller.rb +1 -1
  22. data/lib/sidekiq_unique_jobs/script/client.rb +94 -0
  23. data/lib/sidekiq_unique_jobs/script/config.rb +68 -0
  24. data/lib/sidekiq_unique_jobs/script/dsl.rb +60 -0
  25. data/lib/sidekiq_unique_jobs/script/logging.rb +95 -0
  26. data/lib/sidekiq_unique_jobs/script/lua_error.rb +96 -0
  27. data/lib/sidekiq_unique_jobs/script/script.rb +75 -0
  28. data/lib/sidekiq_unique_jobs/script/scripts.rb +123 -0
  29. data/lib/sidekiq_unique_jobs/script/template.rb +41 -0
  30. data/lib/sidekiq_unique_jobs/script/timing.rb +35 -0
  31. data/lib/sidekiq_unique_jobs/script.rb +32 -1
  32. data/lib/sidekiq_unique_jobs/server.rb +2 -0
  33. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +13 -35
  34. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +4 -4
  35. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +3 -3
  36. data/lib/sidekiq_unique_jobs/testing.rb +4 -4
  37. data/lib/sidekiq_unique_jobs/version.rb +1 -1
  38. data/lib/sidekiq_unique_jobs/web/helpers.rb +3 -3
  39. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +1 -1
  40. data/lib/sidekiq_unique_jobs/web/views/lock.erb +2 -2
  41. data/lib/sidekiq_unique_jobs/web/views/locks.erb +1 -1
  42. data/lib/sidekiq_unique_jobs/web.rb +18 -17
  43. data/lib/sidekiq_unique_jobs.rb +5 -4
  44. metadata +12 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1573748a7de5be552d85b9e74143425fd99b2e85e4e6be1ccdbbd8ba9ecc0f37
4
- data.tar.gz: 520fe1d492c9e088df5b6dc138d8bc4024516486c216e8223fe0f55462cfe691
3
+ metadata.gz: de442ce815fcfc00992295bd4160cfcf122815282e652cccca8c73ce1e62b351
4
+ data.tar.gz: f01d32c217f81f41abf34666c43acdddcb8148e6f7ca0df8146d408f9ed14f8e
5
5
  SHA512:
6
- metadata.gz: 23542e9860f30b16f14c1907790e1bd491e98524dad73b726b88592b8e0cbcf06fa4b535837fc534e4ccf4a80d619840bdf7ec01c3104e3053f1ec5661ea92d5
7
- data.tar.gz: d61c18630ab31064bac8ed9fad52fc84a08ad9bc384ac39d9ed8a44e6e4b741aafa0ebd2845a9f465da8e811e634dab0a8f92ee768c9bb19f9ba8cdb64b10190
6
+ metadata.gz: aec1ab15a3a3c1959bc137aaad674fa9e0854ca354b2226c352901f4d7d7c9cc6c8ef8d805b3b1ad680672eebca52a332de916a5e10d5f4d8b7b439bb677c7c0
7
+ data.tar.gz: 9af5d9ce6dda38757fb33aa33d86c337cd40751fc55bad37ad5c3f98cb04225209953843b7ec137e9295b8da2c2a40a640c7735f1d623c17851621b577b7a03e
data/CHANGELOG.md CHANGED
@@ -1,8 +1,80 @@
1
1
  # Changelog
2
2
 
3
+ ## [v8.0.8](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v8.0.8) (2024-02-12)
4
+
5
+ [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v8.0.7...v8.0.8)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - fix: ensure a new lock isn't conflicting with itself [\#830](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/830) ([mhenrixon](https://github.com/mhenrixon))
10
+
11
+ **Fixed bugs:**
12
+
13
+ - until\_and\_while\_executing not entering perform method on initial run [\#824](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/824)
14
+ - fix\(digest\): write digest on middleware call [\#774](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/774) ([mhenrixon](https://github.com/mhenrixon))
15
+
16
+ **Closed issues:**
17
+
18
+ - incompatibility with sidekiq-failures [\#790](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/790)
19
+ - Jobs queued during existing job inherit lock digest [\#766](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/766)
20
+
21
+ ## [v8.0.7](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v8.0.7) (2024-02-05)
22
+
23
+ [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v8.0.6...v8.0.7)
24
+
25
+ **Implemented enhancements:**
26
+
27
+ - chore\(ci\): add test coverage from bropoplpush [\#828](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/828) ([mhenrixon](https://github.com/mhenrixon))
28
+
29
+ **Fixed bugs:**
30
+
31
+ - fix\(xss\): sanitize parameters [\#829](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/829) ([mhenrixon](https://github.com/mhenrixon))
32
+
33
+ **Closed issues:**
34
+
35
+ - No 'Changelog' link is being displayed on https://rubygems.org/gems/sidekiq-unique-jobs [\#825](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/825)
36
+ - Using rspec matcher and getting: `NameError: uninitialized constant SidekiqUniqueJobs::Lock::BaseLock::Validator` [\#741](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/741)
37
+
38
+ **Merged pull requests:**
39
+
40
+ - Fix Testing Instructions [\#827](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/827) ([jherdman](https://github.com/jherdman))
41
+
42
+ ## [v8.0.6](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v8.0.6) (2024-01-24)
43
+
44
+ [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v8.0.5...v8.0.6)
45
+
46
+ **Implemented enhancements:**
47
+
48
+ - Returning same job id [\#814](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/814)
49
+ - fix: various minor issues [\#826](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/826) ([mhenrixon](https://github.com/mhenrixon))
50
+
51
+ **Closed issues:**
52
+
53
+ - should respond to `has_valid_sidekiq_options?` [\#822](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/822)
54
+ - Reaper manager registration is subject to race conditions [\#801](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/801)
55
+ - `while_executing` with `on_conflict: :reschedule` Reschedule job after job execution [\#800](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/800)
56
+ - Large retry queue causes reaper to run too slow [\#759](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/759)
57
+
58
+ **Merged pull requests:**
59
+
60
+ - fix: skip unless reaper was registered [\#820](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/820) ([mhenrixon](https://github.com/mhenrixon))
61
+
62
+ ## [v8.0.5](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v8.0.5) (2023-11-11)
63
+
64
+ [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v8.0.4...v8.0.5)
65
+
66
+ **Merged pull requests:**
67
+
68
+ - Bump @babel/traverse from 7.22.8 to 7.23.3 in /myapp [\#819](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/819) ([dependabot[bot]](https://github.com/apps/dependabot))
69
+ - Bump postcss from 8.4.21 to 8.4.31 in /myapp [\#811](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/811) ([dependabot[bot]](https://github.com/apps/dependabot))
70
+ - fix: `while_executing` should not invoke conflict strategy when the job was successfully executed \[v8\] [\#810](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/810) ([cuzik](https://github.com/cuzik))
71
+ - Bump actions/checkout from 3 to 4 [\#808](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/808) ([dependabot[bot]](https://github.com/apps/dependabot))
72
+ - Bump semver from 6.3.0 to 6.3.1 in /myapp [\#798](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/798) ([dependabot[bot]](https://github.com/apps/dependabot))
73
+ - Because `replace` is a client strategy, it should only remove client locks aka queue locks. [\#778](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/778) ([bigzed](https://github.com/bigzed))
74
+
3
75
  ## [v8.0.4](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v8.0.4) (2023-11-11)
4
76
 
5
- [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v7.1.30...v8.0.4)
77
+ [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v7.1.31...v8.0.4)
6
78
 
7
79
  **Implemented enhancements:**
8
80
 
@@ -18,9 +90,6 @@
18
90
  **Closed issues:**
19
91
 
20
92
  - register\_reaper\_process nx: true crash Sidekiq on startup [\#817](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/817)
21
- - Should client middleware also be added to the Sidekiq server config? [\#803](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/803)
22
- - 8.0.1 Time on locks & changelog UI is incorrect/wrong [\#761](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/761)
23
- - Job executed twice when reaper runs [\#738](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/738)
24
93
 
25
94
  **Merged pull requests:**
26
95
 
@@ -29,6 +98,16 @@
29
98
  - Handle strategy fallbacks properly [\#809](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/809) ([matejrisek](https://github.com/matejrisek))
30
99
  - Fix CI status badge [\#802](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/802) ([tagliala](https://github.com/tagliala))
31
100
 
101
+ ## [v7.1.31](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v7.1.31) (2023-08-28)
102
+
103
+ [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v7.1.30...v7.1.31)
104
+
105
+ **Closed issues:**
106
+
107
+ - Should client middleware also be added to the Sidekiq server config? [\#803](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/803)
108
+ - 8.0.1 Time on locks & changelog UI is incorrect/wrong [\#761](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/761)
109
+ - Job executed twice when reaper runs [\#738](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/738)
110
+
32
111
  ## [v7.1.30](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v7.1.30) (2023-07-17)
33
112
 
34
113
  [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v8.0.3...v7.1.30)
@@ -335,7 +414,6 @@
335
414
 
336
415
  - Job finished, but lock is not cleared [\#677](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/677)
337
416
  - sidekiq\_options lock conflicts with sidekiq-lock gem lock option [\#669](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/669)
338
- - Slow evalsha causing timeouts [\#668](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/668)
339
417
  - Inconsistent documentation for config validation [\#647](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/647)
340
418
 
341
419
  **Merged pull requests:**
data/README.md CHANGED
@@ -582,8 +582,6 @@ end
582
582
  #spec/workers/bad_worker_spec.rb
583
583
 
584
584
  require "sidekiq_unique_jobs/testing"
585
- #OR
586
- require "sidekiq_unique_jobs/rspec/matchers"
587
585
 
588
586
  RSpec.describe BadWorker do
589
587
  specify { expect(described_class).to have_valid_sidekiq_options }
@@ -816,7 +814,7 @@ In my benchmarks deleting 1000 orphaned locks with lua performs around 65% faste
816
814
 
817
815
  On the other hand if I increase it to 10 000 orphaned locks per cleanup (`reaper_count: 10_0000`) then redis starts throwing:
818
816
 
819
- > BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE. (Redis::CommandError)
817
+ > BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE. (RedisClient::CommandError)
820
818
 
821
819
  If you want to disable the reaper set it to `:none`, `nil` or `false`. Actually, any value that isn't `:ruby` or `:lua` will disable the reaping.
822
820
 
@@ -9,7 +9,7 @@ module SidekiqUniqueJobs
9
9
  class BatchDelete
10
10
  #
11
11
  # @return [Integer] the default batch size
12
- BATCH_SIZE = 100
12
+ BATCH_SIZE = 500
13
13
 
14
14
  #
15
15
  # @return [Array<String>] Supported key suffixes
@@ -59,7 +59,7 @@ module SidekiqUniqueJobs
59
59
  # NOTE: When debugging, check the last item in the returned array.
60
60
  [
61
61
  total_size.to_i,
62
- result[0].to_i, # next_cursor
62
+ result[0].to_i, # next_cursor
63
63
  result[1].map { |entry| load_json(entry) }.select { |entry| entry.is_a?(Hash) },
64
64
  ]
65
65
  end
@@ -67,10 +67,10 @@ module SidekiqUniqueJobs
67
67
  # @return [Hash<Symbol, SidekiqUniqueJobs::Lock::BaseLock] all available default locks
68
68
  LOCKS =
69
69
  LOCKS_WHEN_BUSY.dup
70
- .merge(LOCKS_WHILE_ENQUEUED.dup)
71
- .merge(LOCKS_WITHOUT_UNLOCK.dup)
72
- .merge(LOCKS_FROM_PUSH_TO_PROCESSED.dup)
73
- .freeze
70
+ .merge(LOCKS_WHILE_ENQUEUED.dup)
71
+ .merge(LOCKS_WITHOUT_UNLOCK.dup)
72
+ .merge(LOCKS_FROM_PUSH_TO_PROCESSED.dup)
73
+ .freeze
74
74
 
75
75
  #
76
76
  # @return [Hash<Symbol, SidekiqUniqueJobs::OnConflict::Strategy] all available default strategies
@@ -15,6 +15,7 @@ module SidekiqUniqueJobs
15
15
  DEAD_VERSION = "uniquejobs:dead"
16
16
  DIGESTS = "uniquejobs:digests"
17
17
  EXPIRING_DIGESTS = "uniquejobs:expiring_digests"
18
+ ORPHANED_DIGESTS = "uniquejobs:orphaned_digests"
18
19
  ERRORS = "errors"
19
20
  JID = "jid"
20
21
  LIMIT = "limit"
@@ -41,19 +41,19 @@ module SidekiqUniqueJobs
41
41
  end
42
42
 
43
43
  def add_lock_timeout(item)
44
- item[LOCK_TIMEOUT] ||= SidekiqUniqueJobs::LockTimeout.calculate(item)
44
+ item[LOCK_TIMEOUT] = SidekiqUniqueJobs::LockTimeout.calculate(item)
45
45
  end
46
46
 
47
47
  def add_lock_args(item)
48
- item[LOCK_ARGS] ||= SidekiqUniqueJobs::LockArgs.call(item)
48
+ item[LOCK_ARGS] = SidekiqUniqueJobs::LockArgs.call(item)
49
49
  end
50
50
 
51
51
  def add_lock_digest(item)
52
- item[LOCK_DIGEST] ||= SidekiqUniqueJobs::LockDigest.call(item)
52
+ item[LOCK_DIGEST] = SidekiqUniqueJobs::LockDigest.call(item)
53
53
  end
54
54
 
55
55
  def add_lock_prefix(item)
56
- item[LOCK_PREFIX] ||= SidekiqUniqueJobs.config.lock_prefix
56
+ item[LOCK_PREFIX] = SidekiqUniqueJobs.config.lock_prefix
57
57
  end
58
58
 
59
59
  def add_lock_type(item)
@@ -109,7 +109,7 @@ module SidekiqUniqueJobs
109
109
  #
110
110
  # Call whatever strategry that has been configured
111
111
  #
112
- # @param [Symbol] origin: the origin `:client` or `:server`
112
+ # @param [Symbol] origin the origin `:client` or `:server`
113
113
  #
114
114
  # @return [void] the return value is irrelevant
115
115
  #
@@ -11,6 +11,8 @@ module SidekiqUniqueJobs
11
11
  #
12
12
  # @author Mikael Henriksson <mikael@mhenrixon.com>
13
13
  class WhileExecuting < BaseLock
14
+ #
15
+ # @return [String] returns :RUN
14
16
  RUN_SUFFIX = ":RUN"
15
17
 
16
18
  include SidekiqUniqueJobs::OptionsWithFallback
@@ -33,9 +33,9 @@ module SidekiqUniqueJobs
33
33
  #
34
34
  # @return [Lock] a newly lock that has been locked
35
35
  #
36
- def self.create(digest, job_id, lock_info = {})
37
- lock = new(digest, time: Timing.now_f)
38
- lock.lock(job_id, lock_info)
36
+ def self.create(digest, job_id, lock_info: {}, time: Timing.now_f, score: nil)
37
+ lock = new(digest, time: time)
38
+ lock.lock(job_id, lock_info, score)
39
39
  lock
40
40
  end
41
41
 
@@ -63,15 +63,16 @@ module SidekiqUniqueJobs
63
63
  #
64
64
  # @return [void]
65
65
  #
66
- def lock(job_id, lock_info = {})
66
+ def lock(job_id, lock_info = {}, score = nil)
67
+ score ||= now_f
67
68
  redis do |conn|
68
69
  conn.multi do |pipeline|
69
70
  pipeline.set(key.digest, job_id)
70
71
  pipeline.hset(key.locked, job_id, now_f)
71
72
  info.set(lock_info, pipeline)
72
- add_digest_to_set(pipeline, lock_info)
73
- pipeline.zadd(key.changelog, now_f, changelog_json(job_id, "queue.lua", "Queued"))
74
- pipeline.zadd(key.changelog, now_f, changelog_json(job_id, "lock.lua", "Locked"))
73
+ add_digest_to_set(pipeline, lock_info, score)
74
+ pipeline.zadd(key.changelog, score, changelog_json(job_id, "queue.lua", "Queued"))
75
+ pipeline.zadd(key.changelog, score, changelog_json(job_id, "lock.lua", "Locked"))
75
76
  end
76
77
  end
77
78
  end
@@ -333,12 +334,14 @@ module SidekiqUniqueJobs
333
334
  #
334
335
  # @return [nil]
335
336
  #
336
- def add_digest_to_set(pipeline, lock_info)
337
+ def add_digest_to_set(pipeline, lock_info, score = nil)
338
+ score ||= now_f
337
339
  digest_string = key.digest
340
+
338
341
  if lock_info["lock"] == :until_expired
339
- pipeline.zadd(key.expiring_digests, now_f + lock_info["ttl"], digest_string)
342
+ pipeline.zadd(key.expiring_digests, score + lock_info["ttl"], digest_string)
340
343
  else
341
- pipeline.zadd(key.digests, now_f, digest_string)
344
+ pipeline.zadd(key.digests, score, digest_string)
342
345
  end
343
346
  end
344
347
  end
@@ -188,7 +188,7 @@ module SidekiqUniqueJobs
188
188
  #
189
189
  # @param [Sidekiq::RedisConnection, ConnectionPool] conn the redis connection
190
190
  # @param [Method] primed_method reference to the method to use for getting a primed token
191
- # @param [nil, Integer, Float] time to wait before timeout
191
+ # @param [nil, Integer, Float] wait time to wait before timeout
192
192
  #
193
193
  # @yieldparam [string] job_id the sidekiq JID
194
194
  # @yieldreturn [void] whatever the calling block returns
@@ -258,8 +258,8 @@ module SidekiqUniqueJobs
258
258
 
259
259
  # NOTE: When debugging, change .value to .value!
260
260
  primed_jid = Concurrent::Promises
261
- .future(conn) { |red_con| pop_queued(red_con, timeout) }
262
- .value
261
+ .future(conn) { |red_con| pop_queued(red_con, timeout) }
262
+ .value
263
263
 
264
264
  handle_primed(primed_jid, &block)
265
265
  end
@@ -75,21 +75,28 @@ if lock_type ~= "until_expired" then
75
75
  redis.call("HDEL", locked, job_id)
76
76
  end
77
77
 
78
+ if redis.call("LLEN", primed) == 0 then
79
+ log_debug("UNLINK", primed)
80
+ redis.call("UNLINK", primed)
81
+ end
82
+
78
83
  local locked_count = redis.call("HLEN", locked)
79
84
 
80
- if locked_count and locked_count < 1 then
85
+ if locked_count < 1 then
81
86
  log_debug("UNLINK", locked)
82
87
  redis.call("UNLINK", locked)
83
88
  end
84
89
 
85
- if redis.call("LLEN", primed) == 0 then
86
- log_debug("UNLINK", primed)
87
- redis.call("UNLINK", primed)
88
- end
89
-
90
- if limit and limit <= 1 and locked_count and locked_count <= 1 then
91
- log_debug("ZREM", digests, digest)
92
- redis.call("ZREM", digests, digest)
90
+ if limit then
91
+ if limit <= 1 and locked_count <= 1 then
92
+ log_debug("ZREM", digests, digest)
93
+ redis.call("ZREM", digests, digest)
94
+ end
95
+ else
96
+ if locked_count <= 1 then
97
+ log_debug("ZREM", digests, digest)
98
+ redis.call("ZREM", digests, digest)
99
+ end
93
100
  end
94
101
 
95
102
  log_debug("LPUSH", queued, "1")
@@ -6,7 +6,7 @@ module SidekiqUniqueJobs
6
6
  #
7
7
  # @author Mikael Henriksson <mikael@mhenrixon.com>
8
8
  class Client
9
- include Sidekiq::ClientMiddleware if defined?(Sidekiq::ClientMiddleware)
9
+ include Sidekiq::ClientMiddleware
10
10
 
11
11
  # prepend "SidekiqUniqueJobs::Middleware"
12
12
  # @!parse prepends SidekiqUniqueJobs::Middleware
@@ -6,7 +6,7 @@ module SidekiqUniqueJobs
6
6
  #
7
7
  # @author Mikael Henriksson <mikael@mhenrixon.com>
8
8
  class Server
9
- include Sidekiq::ServerMiddleware if defined?(Sidekiq::ServerMiddleware)
9
+ include Sidekiq::ServerMiddleware
10
10
 
11
11
  # prepend "SidekiqUniqueJobs::Middleware"
12
12
  # @!parse prepends SidekiqUniqueJobs::Middleware
@@ -19,15 +19,15 @@ module SidekiqUniqueJobs
19
19
  # This method runs before (prepended) the actual middleware implementation.
20
20
  # This is done to reduce duplication
21
21
  #
22
- # @param [Sidekiq::Worker] worker_class
22
+ # @param [Sidekiq::Job] worker_class
23
23
  # @param [Hash] item a sidekiq job hash
24
24
  # @param [String] queue name of the queue
25
25
  # @param [ConnectionPool] redis_pool only used for compatility reasons
26
26
  #
27
- # @return [yield<super>] <description>
27
+ # @return [yield<super>] call the rest of the middleware stack
28
28
  #
29
- # @yieldparam [<type>] if <description>
30
- # @yieldreturn [<type>] <describe what yield should return>
29
+ # @yieldparam [void] if uniquejobs is disable
30
+ # @yieldreturn [void] delegate back to other sidekiq middleware
31
31
  def call(worker_class, item, queue, redis_pool = nil)
32
32
  @item = item
33
33
  @queue = queue
@@ -35,7 +35,7 @@ module SidekiqUniqueJobs
35
35
  self.job_class = worker_class
36
36
  return yield if unique_disabled?
37
37
 
38
- SidekiqUniqueJobs::Job.prepare(item) unless item[LOCK_DIGEST]
38
+ SidekiqUniqueJobs::Job.prepare(item)
39
39
 
40
40
  with_logging_context do
41
41
  super
@@ -39,12 +39,13 @@ module SidekiqUniqueJobs
39
39
  self.task = test_task || default_task
40
40
 
41
41
  with_logging_context do
42
- register_reaper_process
43
- log_info("Starting Reaper")
42
+ if register_reaper_process
43
+ log_info("Starting Reaper")
44
44
 
45
- task.add_observer(Observer.new)
46
- task.execute
47
- task
45
+ task.add_observer(Observer.new)
46
+ task.execute
47
+ task
48
+ end
48
49
  end
49
50
  end
50
51
 
@@ -22,11 +22,12 @@ module SidekiqUniqueJobs
22
22
  #
23
23
  # @return [Hash<Symbol, SidekiqUniqueJobs::Orphans::Reaper] the current implementation of reapers
24
24
  REAPERS = {
25
- lua: SidekiqUniqueJobs::Orphans::LuaReaper,
25
+ lua: SidekiqUniqueJobs::Orphans::RubyReaper,
26
26
  ruby: SidekiqUniqueJobs::Orphans::RubyReaper,
27
27
  none: SidekiqUniqueJobs::Orphans::NullReaper,
28
28
  nil => SidekiqUniqueJobs::Orphans::NullReaper,
29
29
  false => SidekiqUniqueJobs::Orphans::NullReaper,
30
+ true => SidekiqUniqueJobs::Orphans::RubyReaper,
30
31
  }.freeze
31
32
 
32
33
  #
@@ -13,6 +13,9 @@ module SidekiqUniqueJobs
13
13
  class RubyReaper < Reaper
14
14
  include SidekiqUniqueJobs::Timing
15
15
 
16
+ #
17
+ # @return [Integer] a best guess of Sidekiq::Launcher::BEAT_PAUSE
18
+ SIDEKIQ_BEAT_PAUSE = 10
16
19
  #
17
20
  # @return [String] the suffix for :RUN locks
18
21
  RUN_SUFFIX = ":RUN"
@@ -74,14 +77,30 @@ module SidekiqUniqueJobs
74
77
 
75
78
  BatchDelete.call(expired_digests, conn)
76
79
  BatchDelete.call(orphans, conn)
80
+
81
+ # orphans.each_slice(500) do |chunk|
82
+ # conn.pipelined do |pipeline|
83
+ # chunk.each do |digest|
84
+ # next if belongs_to_job?(digest)
85
+
86
+ # pipeline.zadd(ORPHANED_DIGESTS, now_f, digest)
87
+ # end
88
+ # end
89
+ # end
77
90
  end
78
91
 
79
92
  def expired_digests
80
- max_score = (start_time - reaper_timeout).to_f
81
-
82
93
  conn.zrange(EXPIRING_DIGESTS, 0, max_score, "byscore")
83
94
  end
84
95
 
96
+ def orphaned_digests
97
+ conn.zrange(ORPHANED_DIGESTS, 0, max_score, "byscore")
98
+ end
99
+
100
+ def max_score
101
+ (start_time - reaper_timeout - SIDEKIQ_BEAT_PAUSE).to_f
102
+ end
103
+
85
104
  #
86
105
  # Find orphaned digests
87
106
  #
@@ -89,10 +108,10 @@ module SidekiqUniqueJobs
89
108
  # @return [Array<String>] an array of orphaned digests
90
109
  #
91
110
  def orphans # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
92
- page = 0
93
- per = reaper_count * 2
94
111
  orphans = []
95
- results = conn.zrange(digests.key, page * per, (page + 1) * per)
112
+ page = 0
113
+ per = reaper_count * 2
114
+ results = digests.byscore(0, max_score, offset: page * per, count: (page + 1) * per)
96
115
 
97
116
  while results.size.positive?
98
117
  results.each do |digest|
@@ -107,7 +126,7 @@ module SidekiqUniqueJobs
107
126
  break if orphans.size >= reaper_count
108
127
 
109
128
  page += 1
110
- results = conn.zrange(digests.key, page * per, (page + 1) * per)
129
+ results = digests.byscore(0, max_score, offset: page * per, count: (page + 1) * per)
111
130
  end
112
131
 
113
132
  orphans
@@ -218,7 +237,7 @@ module SidekiqUniqueJobs
218
237
  end
219
238
 
220
239
  def considered_active?(time_f)
221
- (Time.now - reaper_timeout).to_f < time_f
240
+ max_score < time_f
222
241
  end
223
242
 
224
243
  #
@@ -48,6 +48,14 @@ module SidekiqUniqueJobs
48
48
  end
49
49
  end
50
50
 
51
+ def byscore(min, max, offset: nil, count: nil)
52
+ redis do |conn|
53
+ return conn.zrange(key, min, max, "byscore") unless offset && count
54
+
55
+ conn.zrange(key, min, max, "byscore", "limit", offset, count)
56
+ end
57
+ end
58
+
51
59
  #
52
60
  # Return the zrak of the member
53
61
  #
@@ -56,7 +56,7 @@ module SidekiqUniqueJobs
56
56
  def do_call(file_name, conn, keys, argv)
57
57
  argv = argv.dup.push(now_f, debug_lua, max_history, file_name, redis_version)
58
58
 
59
- Script.execute(file_name, conn, keys: keys, argv: normalize_argv(argv))
59
+ SidekiqUniqueJobs::Script.execute(file_name, conn, keys: keys, argv: normalize_argv(argv))
60
60
  end
61
61
 
62
62
  #
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ module Script
5
+ # Interface to dealing with .lua files
6
+ #
7
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
8
+ class Client
9
+ include SidekiqUniqueJobs::Script::Timing
10
+
11
+ #
12
+ # @!attribute [r] logger
13
+ # @return [Logger] an instance of a logger
14
+ attr_reader :logger
15
+ #
16
+ # @!attribute [r] file_name
17
+ # @return [String] The name of the file to execute
18
+ attr_reader :config
19
+ #
20
+ # @!attribute [r] scripts
21
+ # @return [Scripts] the collection with loaded scripts
22
+ attr_reader :scripts
23
+
24
+ def initialize(config)
25
+ @config = config
26
+ @logger = config.logger
27
+ @scripts = Scripts.fetch(config.scripts_path)
28
+ end
29
+
30
+ #
31
+ # Execute a lua script with the provided script_name
32
+ #
33
+ # @note this method is recursive if we need to load a lua script
34
+ # that wasn't previously loaded.
35
+ #
36
+ # @param [Symbol] script_name the name of the script to execute
37
+ # @param [Redis] conn the redis connection to use for execution
38
+ # @param [Array<String>] keys script keys
39
+ # @param [Array<Object>] argv script arguments
40
+ #
41
+ # @return value from script
42
+ #
43
+ def execute(script_name, conn, keys: [], argv: [])
44
+ result, elapsed = timed do
45
+ scripts.execute(script_name, conn, keys: keys, argv: argv)
46
+ end
47
+
48
+ logger.debug("Executed #{script_name}.lua in #{elapsed}ms")
49
+ result
50
+ rescue ::RedisClient::CommandError => ex
51
+ handle_error(script_name, conn, ex) do
52
+ execute(script_name, conn, keys: keys, argv: argv)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ #
59
+ # Handle errors to allow retrying errors that need retrying
60
+ #
61
+ # @param [RedisClient::CommandError] ex exception to handle
62
+ #
63
+ # @return [void]
64
+ #
65
+ # @yieldreturn [void] yields back to the caller when NOSCRIPT is raised
66
+ def handle_error(script_name, conn, ex)
67
+ case ex.message
68
+ when /NOSCRIPT/
69
+ handle_noscript(script_name) { return yield }
70
+ when /BUSY/
71
+ handle_busy(conn) { return yield }
72
+ end
73
+
74
+ raise unless LuaError.intercepts?(ex)
75
+
76
+ script = scripts.fetch(script_name, conn)
77
+ raise LuaError.new(ex, script)
78
+ end
79
+
80
+ def handle_noscript(script_name)
81
+ scripts.delete(script_name)
82
+ yield
83
+ end
84
+
85
+ def handle_busy(conn)
86
+ scripts.kill(conn)
87
+ rescue ::RedisClient::CommandError => ex
88
+ logger.warn(ex)
89
+ ensure
90
+ yield
91
+ end
92
+ end
93
+ end
94
+ end