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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +89 -11
- data/README.md +154 -36
- data/lib/sidekiq-unique-jobs.rb +0 -2
- data/lib/sidekiq_unique_jobs.rb +1 -0
- data/lib/sidekiq_unique_jobs/changelog.rb +11 -4
- data/lib/sidekiq_unique_jobs/config.rb +2 -2
- data/lib/sidekiq_unique_jobs/constants.rb +4 -0
- data/lib/sidekiq_unique_jobs/digests.rb +1 -1
- data/lib/sidekiq_unique_jobs/job.rb +1 -1
- data/lib/sidekiq_unique_jobs/lock/validator.rb +2 -1
- data/lib/sidekiq_unique_jobs/lock/while_executing.rb +1 -1
- data/lib/sidekiq_unique_jobs/lock_args.rb +4 -4
- data/lib/sidekiq_unique_jobs/lock_config.rb +2 -0
- data/lib/sidekiq_unique_jobs/locksmith.rb +1 -1
- data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +1 -1
- data/lib/sidekiq_unique_jobs/lua/lock.lua +1 -2
- data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +8 -7
- data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +9 -2
- data/lib/sidekiq_unique_jobs/middleware.rb +0 -57
- data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +9 -8
- data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +1 -1
- data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +1 -1
- data/lib/sidekiq_unique_jobs/orphans/reaper.rb +10 -0
- data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +9 -2
- data/lib/sidekiq_unique_jobs/redis/entity.rb +9 -3
- data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +27 -0
- data/lib/sidekiq_unique_jobs/server.rb +48 -0
- data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +1 -1
- data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +1 -1
- data/lib/sidekiq_unique_jobs/testing.rb +2 -1
- data/lib/sidekiq_unique_jobs/version.rb +1 -1
- data/lib/sidekiq_unique_jobs/web.rb +26 -9
- data/lib/sidekiq_unique_jobs/web/helpers.rb +24 -3
- data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +54 -0
- data/lib/sidekiq_unique_jobs/web/views/locks.erb +1 -1
- metadata +25 -9
- data/lib/sidekiq_unique_jobs/profiler.rb +0 -55
data/lib/sidekiq-unique-jobs.rb
CHANGED
data/lib/sidekiq_unique_jobs.rb
CHANGED
@@ -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:
|
44
|
+
def entries(pattern: SCAN_PATTERN, count: DEFAULT_COUNT)
|
38
45
|
options = {}
|
39
|
-
options[:match] = pattern
|
40
|
-
options[:count] = 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
|
-
:
|
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
|
-
#
|
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
|
81
|
+
options[:count] = count
|
82
82
|
|
83
83
|
result = redis { |conn| conn.zscan_each(key, **options).to_a }
|
84
84
|
|
@@ -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 =>
|
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
|
|
@@ -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
|
101
|
-
@lock_args_method ||= worker_options
|
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[
|
110
|
-
default_worker_options[
|
109
|
+
default_worker_options[LOCK_ARGS_METHOD] ||
|
110
|
+
default_worker_options[UNIQUE_ARGS_METHOD]
|
111
111
|
end
|
112
112
|
|
113
113
|
#
|
@@ -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
|
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
|
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
|
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[
|
15
|
-
local debug_lua = ARGV[
|
16
|
-
local max_history = tonumber(ARGV[
|
17
|
-
local script_name = ARGV[
|
18
|
-
local redisversion = ARGV[
|
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
|
-
|
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]
|
14
|
+
# @!attribute [r] lock_digest
|
15
15
|
# @return [String] the unique digest to use for locking
|
16
|
-
attr_reader :
|
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
|
26
|
-
@
|
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("
|
40
|
+
log_info("Deleted job: #{deleted_job}")
|
41
41
|
if (del_count = delete_lock)
|
42
|
-
log_info("Deleted `#{del_count}` keys for #{
|
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: [
|
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(
|
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
|
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
|
@@ -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
|
-
|
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
|
#
|