sidekiq-unique-jobs 7.0.0.beta26 → 7.0.1

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.

Potentially problematic release.


This version of sidekiq-unique-jobs might be problematic. Click here for more details.

Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -11
  3. data/README.md +154 -36
  4. data/lib/sidekiq-unique-jobs.rb +0 -2
  5. data/lib/sidekiq_unique_jobs.rb +1 -0
  6. data/lib/sidekiq_unique_jobs/changelog.rb +11 -4
  7. data/lib/sidekiq_unique_jobs/config.rb +2 -2
  8. data/lib/sidekiq_unique_jobs/constants.rb +4 -0
  9. data/lib/sidekiq_unique_jobs/digests.rb +1 -1
  10. data/lib/sidekiq_unique_jobs/job.rb +1 -1
  11. data/lib/sidekiq_unique_jobs/lock/validator.rb +2 -1
  12. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +1 -1
  13. data/lib/sidekiq_unique_jobs/lock_args.rb +4 -4
  14. data/lib/sidekiq_unique_jobs/lock_config.rb +2 -0
  15. data/lib/sidekiq_unique_jobs/locksmith.rb +1 -1
  16. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +1 -1
  17. data/lib/sidekiq_unique_jobs/lua/lock.lua +1 -2
  18. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +8 -7
  19. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +9 -2
  20. data/lib/sidekiq_unique_jobs/middleware.rb +0 -57
  21. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +9 -8
  22. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +1 -1
  23. data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +1 -1
  24. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +10 -0
  25. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +9 -2
  26. data/lib/sidekiq_unique_jobs/redis/entity.rb +9 -3
  27. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +27 -0
  28. data/lib/sidekiq_unique_jobs/server.rb +48 -0
  29. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +1 -1
  30. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +1 -1
  31. data/lib/sidekiq_unique_jobs/testing.rb +2 -1
  32. data/lib/sidekiq_unique_jobs/version.rb +1 -1
  33. data/lib/sidekiq_unique_jobs/web.rb +26 -9
  34. data/lib/sidekiq_unique_jobs/web/helpers.rb +24 -3
  35. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +54 -0
  36. data/lib/sidekiq_unique_jobs/web/views/locks.erb +1 -1
  37. metadata +25 -9
  38. data/lib/sidekiq_unique_jobs/profiler.rb +0 -55
@@ -1,5 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq_unique_jobs"
4
-
5
- SidekiqUniqueJobs::Middleware.configure
@@ -71,3 +71,4 @@ require "sidekiq_unique_jobs/config"
71
71
  require "sidekiq_unique_jobs/sidekiq_unique_jobs"
72
72
  require "sidekiq_unique_jobs/update_version"
73
73
  require "sidekiq_unique_jobs/upgrade_locks"
74
+ require "sidekiq_unique_jobs/server"
@@ -7,6 +7,13 @@ module SidekiqUniqueJobs
7
7
  # @author Mikael Henriksson <mikael@mhenrixon.com>
8
8
  #
9
9
  class Changelog < Redis::SortedSet
10
+ #
11
+ # @return [Integer] the number of matches to return by default
12
+ DEFAULT_COUNT = 1_000
13
+ #
14
+ # @return [String] the default pattern to use for matching
15
+ SCAN_PATTERN = "*"
16
+
10
17
  def initialize
11
18
  super(CHANGELOGS)
12
19
  end
@@ -34,10 +41,10 @@ module SidekiqUniqueJobs
34
41
  #
35
42
  # @return [Array<Hash>] an array of entries
36
43
  #
37
- def entries(pattern: "*", count: nil)
44
+ def entries(pattern: SCAN_PATTERN, count: DEFAULT_COUNT)
38
45
  options = {}
39
- options[:match] = pattern if pattern
40
- options[:count] = count if count
46
+ options[:match] = pattern
47
+ options[:count] = count
41
48
 
42
49
  redis do |conn|
43
50
  conn.zscan_each(key, **options).to_a.map { |entry| load_json(entry[0]) }
@@ -53,7 +60,7 @@ module SidekiqUniqueJobs
53
60
  #
54
61
  # @return [Array<Integer, Integer, Array<Hash>] the total size, next cursor and changelog entries
55
62
  #
56
- def page(cursor, pattern: "*", page_size: 100)
63
+ def page(cursor: 0, pattern: "*", page_size: 100)
57
64
  redis do |conn|
58
65
  total_size, result = conn.multi do
59
66
  conn.zcard(key)
@@ -6,7 +6,7 @@ module SidekiqUniqueJobs
6
6
  :lock_timeout,
7
7
  :lock_ttl,
8
8
  :enabled,
9
- :unique_prefix,
9
+ :lock_prefix,
10
10
  :logger,
11
11
  :locks,
12
12
  :strategies,
@@ -127,7 +127,7 @@ module SidekiqUniqueJobs
127
127
  # default_lock_timeout: 0,
128
128
  # default_lock_ttl: nil,
129
129
  # enabled: true,
130
- # unique_prefix: "uniquejobs",
130
+ # lock_prefix: "uniquejobs",
131
131
  # logger: #<Sidekiq::Logger:0x00007f81e096b0e0 @level=1 ...>,
132
132
  # locks: {
133
133
  # around_perform: SidekiqUniqueJobs::Lock::WhileExecuting,
@@ -11,6 +11,7 @@ module SidekiqUniqueJobs
11
11
  AT ||= "at"
12
12
  CHANGELOGS ||= "uniquejobs:changelog"
13
13
  CLASS ||= "class"
14
+ CREATED_AT ||= "created_at"
14
15
  DEAD_VERSION ||= "uniquejobs:dead"
15
16
  DIGESTS ||= "uniquejobs:digests"
16
17
  ERRORS ||= "errors"
@@ -19,6 +20,7 @@ module SidekiqUniqueJobs
19
20
  LIVE_VERSION ||= "uniquejobs:live"
20
21
  LOCK ||= "lock"
21
22
  LOCK_ARGS ||= "lock_args"
23
+ LOCK_ARGS_METHOD ||= "lock_args_method"
22
24
  LOCK_DIGEST ||= "lock_digest"
23
25
  LOCK_EXPIRATION ||= "lock_expiration"
24
26
  LOCK_INFO ||= "lock_info"
@@ -31,6 +33,7 @@ module SidekiqUniqueJobs
31
33
  ON_CLIENT_CONFLICT ||= "on_client_conflict"
32
34
  ON_CONFLICT ||= "on_conflict"
33
35
  ON_SERVER_CONFLICT ||= "on_server_conflict"
36
+ PAYLOAD ||= "payload"
34
37
  PROCESSES ||= "processes"
35
38
  QUEUE ||= "queue"
36
39
  RETRY ||= "retry"
@@ -43,6 +46,7 @@ module SidekiqUniqueJobs
43
46
  UNIQUE_ACROSS_QUEUES ||= "unique_across_queues"
44
47
  UNIQUE_ACROSS_WORKERS ||= "unique_across_workers"
45
48
  UNIQUE_ARGS ||= "unique_args"
49
+ UNIQUE_ARGS_METHOD ||= "unique_args_method"
46
50
  UNIQUE_DIGEST ||= "unique_digest"
47
51
  UNIQUE_PREFIX ||= "unique_prefix"
48
52
  UNIQUE_REAPER ||= "uniquejobs:reaper"
@@ -78,7 +78,7 @@ module SidekiqUniqueJobs
78
78
  def entries(pattern: SCAN_PATTERN, count: DEFAULT_COUNT)
79
79
  options = {}
80
80
  options[:match] = pattern
81
- options[:count] = count if count
81
+ options[:count] = count
82
82
 
83
83
  result = redis { |conn| conn.zscan_each(key, **options).to_a }
84
84
 
@@ -52,7 +52,7 @@ module SidekiqUniqueJobs
52
52
  end
53
53
 
54
54
  def add_lock_prefix(item)
55
- item[LOCK_PREFIX] ||= SidekiqUniqueJobs.config.unique_prefix
55
+ item[LOCK_PREFIX] ||= SidekiqUniqueJobs.config.lock_prefix
56
56
  end
57
57
  end
58
58
  end
@@ -12,7 +12,8 @@ module SidekiqUniqueJobs
12
12
  # @return [Hash] a hash mapping of deprecated keys and their new value
13
13
  DEPRECATED_KEYS = {
14
14
  UNIQUE.to_sym => LOCK.to_sym,
15
- UNIQUE_ARGS.to_sym => LOCK_ARGS.to_sym,
15
+ UNIQUE_ARGS.to_sym => LOCK_ARGS_METHOD.to_sym,
16
+ LOCK_ARGS.to_sym => LOCK_ARGS_METHOD.to_sym,
16
17
  UNIQUE_PREFIX.to_sym => LOCK_PREFIX.to_sym,
17
18
  }.freeze
18
19
 
@@ -43,7 +43,7 @@ module SidekiqUniqueJobs
43
43
  end
44
44
  end
45
45
  ensure
46
- locksmith.unlock
46
+ locksmith.unlock!
47
47
  end
48
48
 
49
49
  private
@@ -97,8 +97,8 @@ module SidekiqUniqueJobs
97
97
  end
98
98
 
99
99
  # The method to use for filtering unique arguments
100
- def lock_args_method # rubocop:disable Metrics/CyclomaticComplexity
101
- @lock_args_method ||= worker_options[LOCK_ARGS] || worker_options[UNIQUE_ARGS]
100
+ def lock_args_method
101
+ @lock_args_method ||= worker_options.slice(LOCK_ARGS_METHOD, UNIQUE_ARGS_METHOD).values.first
102
102
  @lock_args_method ||= :lock_args if worker_method_defined?(:lock_args)
103
103
  @lock_args_method ||= :unique_args if worker_method_defined?(:unique_args)
104
104
  @lock_args_method ||= default_lock_args_method
@@ -106,8 +106,8 @@ module SidekiqUniqueJobs
106
106
 
107
107
  # The global worker options defined in Sidekiq directly
108
108
  def default_lock_args_method
109
- default_worker_options[LOCK_ARGS] ||
110
- default_worker_options[UNIQUE_ARGS]
109
+ default_worker_options[LOCK_ARGS_METHOD] ||
110
+ default_worker_options[UNIQUE_ARGS_METHOD]
111
111
  end
112
112
 
113
113
  #
@@ -98,6 +98,8 @@ module SidekiqUniqueJobs
98
98
  # @return [String]
99
99
  #
100
100
  def errors_as_string
101
+ return if valid?
102
+
101
103
  @errors_as_string ||= begin
102
104
  error_msg = +"\t"
103
105
  error_msg << errors.map { |key, val| "#{key}: :#{val}" }.join("\n\t")
@@ -87,7 +87,7 @@ module SidekiqUniqueJobs
87
87
  #
88
88
  def lock(&block)
89
89
  redis(redis_pool) do |conn|
90
- return lock_async(conn, &block) if block_given?
90
+ return lock_async(conn, &block) if block
91
91
 
92
92
  lock_sync(conn) do
93
93
  return job_id
@@ -25,7 +25,7 @@ local redisversion = tostring(ARGV[5])
25
25
  -------- BEGIN delete_by_digest.lua --------
26
26
  local counter = 0
27
27
  local redis_version = toversion(redisversion)
28
- local del_cmd = "DEL"
28
+ local del_cmd = "DEL"
29
29
 
30
30
  log_debug("BEGIN delete_by_digest:", digest)
31
31
 
@@ -69,8 +69,7 @@ redis.call("LREM", queued, -1, job_id)
69
69
  log_debug("LREM", primed, 1, job_id)
70
70
  redis.call("LREM", primed, 1, job_id)
71
71
 
72
- -- The Sidekiq client should only set pttl for until_expired
73
- -- The Sidekiq server should set pttl for all other jobs
72
+ -- The Sidekiq client sets pttl
74
73
  if pttl and pttl > 0 then
75
74
  log_debug("PEXPIRE", digest, pttl)
76
75
  redis.call("PEXPIRE", digest, pttl)
@@ -7,15 +7,16 @@ local retry_set = KEYS[3]
7
7
  -------- END keys ---------
8
8
 
9
9
  -------- BEGIN argv ---------
10
- local reaper_count = tonumber(ARGV[1])
10
+ local reaper_count = tonumber(ARGV[1])
11
+ local threshold = tonumber(ARGV[2])
11
12
  -------- END argv ---------
12
13
 
13
14
  -------- BEGIN injected arguments --------
14
- local current_time = tonumber(ARGV[2])
15
- local debug_lua = ARGV[3] == "true"
16
- local max_history = tonumber(ARGV[4])
17
- local script_name = ARGV[5] .. ".lua"
18
- local redisversion = ARGV[6]
15
+ local current_time = tonumber(ARGV[3])
16
+ local debug_lua = ARGV[4] == "true"
17
+ local max_history = tonumber(ARGV[5])
18
+ local script_name = ARGV[6] .. ".lua"
19
+ local redisversion = ARGV[7]
19
20
  --------- END injected arguments ---------
20
21
 
21
22
 
@@ -65,7 +66,7 @@ repeat
65
66
  -- TODO: Add check for jobs checked out by process
66
67
  if found ~= true then
67
68
  log_debug("Searching for digest:", digest, "in process sets")
68
- found = find_digest_in_process_set(digest)
69
+ found = find_digest_in_process_set(digest, threshold)
69
70
  end
70
71
 
71
72
  if found ~= true then
@@ -1,4 +1,4 @@
1
- local function find_digest_in_process_set(digest)
1
+ local function find_digest_in_process_set(digest, threshold)
2
2
  local process_cursor = 0
3
3
  local job_cursor = 0
4
4
  local pattern = "*" .. digest .. "*"
@@ -26,11 +26,18 @@ local function find_digest_in_process_set(digest)
26
26
  log_debug("No entries in:", workers_key)
27
27
  else
28
28
  for i = 1, #jobs, 2 do
29
- if string.find(jobs[i +1], digest) then
29
+ local jobstr = jobs[i +1]
30
+ if string.find(jobstr, digest) then
30
31
  log_debug("Found digest", digest, "in:", workers_key)
31
32
  found = true
32
33
  break
33
34
  end
35
+
36
+ local job = cjson.decode(jobstr)
37
+ if job.created_at > threshold then
38
+ found = true
39
+ break
40
+ end
34
41
  end
35
42
  end
36
43
 
@@ -11,63 +11,6 @@ module SidekiqUniqueJobs
11
11
  include SidekiqUniqueJobs::OptionsWithFallback
12
12
  include SidekiqUniqueJobs::JSON
13
13
 
14
- #
15
- # Configure both server and client
16
- #
17
- def self.configure
18
- configure_server
19
- configure_client
20
- end
21
-
22
- #
23
- # Configures the Sidekiq server
24
- #
25
- def self.configure_server # rubocop:disable Metrics/MethodLength
26
- Sidekiq.configure_server do |config|
27
- config.client_middleware do |chain|
28
- if defined?(Apartment::Sidekiq::Middleware::Client)
29
- chain.insert_after Apartment::Sidekiq::Middleware::Client, SidekiqUniqueJobs::Middleware::Client
30
- else
31
- chain.add SidekiqUniqueJobs::Middleware::Client
32
- end
33
- end
34
-
35
- config.server_middleware do |chain|
36
- if defined?(Apartment::Sidekiq::Middleware::Server)
37
- chain.insert_after Apartment::Sidekiq::Middleware::Server, SidekiqUniqueJobs::Middleware::Server
38
- else
39
- chain.add SidekiqUniqueJobs::Middleware::Server
40
- end
41
- end
42
-
43
- config.on(:startup) do
44
- SidekiqUniqueJobs::UpdateVersion.call
45
- SidekiqUniqueJobs::UpgradeLocks.call
46
-
47
- SidekiqUniqueJobs::Orphans::Manager.start
48
- end
49
-
50
- config.on(:shutdown) do
51
- SidekiqUniqueJobs::Orphans::Manager.stop
52
- end
53
- end
54
- end
55
-
56
- #
57
- # Configures the Sidekiq client
58
- #
59
- def self.configure_client
60
- Sidekiq.configure_client do |config|
61
- config.client_middleware do |chain|
62
- if defined?(Apartment::Sidekiq::Middleware::Client)
63
- chain.insert_after Apartment::Sidekiq::Middleware::Client, SidekiqUniqueJobs::Middleware::Client
64
- else
65
- chain.add SidekiqUniqueJobs::Middleware::Client
66
- end
67
- end
68
- end
69
- end
70
-
71
14
  # The sidekiq job hash
72
15
  # @return [Hash] the Sidekiq job hash
73
16
  attr_reader :item
@@ -11,9 +11,9 @@ module SidekiqUniqueJobs
11
11
  # @return [String] rthe sidekiq queue this job belongs to
12
12
  attr_reader :queue
13
13
  #
14
- # @!attribute [r] unique_digest
14
+ # @!attribute [r] lock_digest
15
15
  # @return [String] the unique digest to use for locking
16
- attr_reader :unique_digest
16
+ attr_reader :lock_digest
17
17
 
18
18
  #
19
19
  # Initialize a new Replace strategy
@@ -22,8 +22,8 @@ module SidekiqUniqueJobs
22
22
  #
23
23
  def initialize(item, redis_pool = nil)
24
24
  super(item, redis_pool)
25
- @queue = item[QUEUE]
26
- @unique_digest = item[LOCK_DIGEST]
25
+ @queue = item[QUEUE]
26
+ @lock_digest = item[LOCK_DIGEST]
27
27
  end
28
28
 
29
29
  #
@@ -37,10 +37,11 @@ module SidekiqUniqueJobs
37
37
  def call(&block)
38
38
  return unless (deleted_job = delete_job_by_digest)
39
39
 
40
- log_info("Deleting job: #{deleted_job}")
40
+ log_info("Deleted job: #{deleted_job}")
41
41
  if (del_count = delete_lock)
42
- log_info("Deleted `#{del_count}` keys for #{unique_digest}")
42
+ log_info("Deleted `#{del_count}` keys for #{lock_digest}")
43
43
  end
44
+
44
45
  block&.call
45
46
  end
46
47
 
@@ -54,7 +55,7 @@ module SidekiqUniqueJobs
54
55
  def delete_job_by_digest
55
56
  call_script(:delete_job_by_digest,
56
57
  keys: ["#{QUEUE}:#{queue}", SCHEDULE, RETRY],
57
- argv: [unique_digest])
58
+ argv: [lock_digest])
58
59
  end
59
60
 
60
61
  #
@@ -64,7 +65,7 @@ module SidekiqUniqueJobs
64
65
  # @return [Integer] the number of keys deleted
65
66
  #
66
67
  def delete_lock
67
- digests.delete_by_digest(unique_digest)
68
+ digests.delete_by_digest(lock_digest)
68
69
  end
69
70
 
70
71
  #
@@ -21,7 +21,7 @@ module SidekiqUniqueJobs
21
21
  def call
22
22
  if sidekiq_worker_class?
23
23
  log_info("Rescheduling #{item[LOCK_DIGEST]}")
24
- worker_class&.perform_in(5, *item[ARGS])
24
+ worker_class.perform_in(5, *item[ARGS])
25
25
  else
26
26
  log_warn("Skip rescheduling of #{item[LOCK_DIGEST]} because #{worker_class} is not a Sidekiq::Worker")
27
27
  end
@@ -21,7 +21,7 @@ module SidekiqUniqueJobs
21
21
  :reap_orphans,
22
22
  conn,
23
23
  keys: [DIGESTS, SCHEDULE, RETRY, PROCESSES],
24
- argv: [reaper_count],
24
+ argv: [reaper_count, (Time.now - reaper_timeout).to_f],
25
25
  )
26
26
  end
27
27
  end
@@ -76,6 +76,16 @@ module SidekiqUniqueJobs
76
76
  config.reaper
77
77
  end
78
78
 
79
+ #
80
+ # The configured timeout for the reaper
81
+ #
82
+ #
83
+ # @return [Integer] timeout in seconds
84
+ #
85
+ def reaper_timeout
86
+ config.reaper_timeout
87
+ end
88
+
79
89
  #
80
90
  # The number of locks to reap at a time
81
91
  #
@@ -117,7 +117,7 @@ module SidekiqUniqueJobs
117
117
  end
118
118
  end
119
119
 
120
- def active?(digest) # rubocop:disable Metrics/MethodLength
120
+ def active?(digest) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
121
121
  Sidekiq.redis do |conn|
122
122
  procs = conn.sscan_each("processes").to_a
123
123
  return false if procs.empty?
@@ -132,7 +132,10 @@ module SidekiqUniqueJobs
132
132
  next unless workers.any?
133
133
 
134
134
  workers.each_pair do |_tid, job|
135
- return true if load_json(job)[LOCK_DIGEST] == digest
135
+ item = load_json(job)
136
+
137
+ return true if item.dig(PAYLOAD, LOCK_DIGEST) == digest
138
+ return true if considered_active?(item[CREATED_AT])
136
139
  end
137
140
  end
138
141
 
@@ -140,6 +143,10 @@ module SidekiqUniqueJobs
140
143
  end
141
144
  end
142
145
 
146
+ def considered_active?(time_f)
147
+ (Time.now - reaper_timeout).to_f < time_f
148
+ end
149
+
143
150
  #
144
151
  # Loops through all the redis queues and yields them one by one
145
152
  #