sidekiq-unique-jobs 7.0.0.beta26 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.

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
  #