sidekiq-unique-jobs 6.0.0.rc6 → 6.0.0.rc7
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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +6 -7
- data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- data/.reek.yml +17 -48
- data/.rubocop.yml +3 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +2 -0
- data/README.md +65 -23
- data/assets/unique_digests_1.png +0 -0
- data/assets/unique_digests_2.png +0 -0
- data/examples/another_unique_job.rb +4 -2
- data/examples/custom_queue_job_with_filter_method.rb +1 -1
- data/examples/custom_queue_job_with_filter_proc.rb +1 -1
- data/examples/expiring_job.rb +1 -1
- data/examples/inline_worker.rb +1 -1
- data/examples/just_a_worker.rb +1 -1
- data/examples/long_running_job.rb +4 -2
- data/examples/main_job.rb +3 -2
- data/examples/my_unique_job.rb +4 -5
- data/examples/my_unique_job_with_filter_method.rb +3 -3
- data/examples/my_unique_job_with_filter_proc.rb +3 -3
- data/examples/notify_worker.rb +2 -2
- data/examples/simple_worker.rb +2 -2
- data/examples/unique_across_workers_job.rb +1 -1
- data/examples/unique_job_on_conflict_raise.rb +14 -0
- data/examples/unique_job_on_conflict_reject.rb +14 -0
- data/examples/unique_job_on_conflict_reschedule.rb +14 -0
- data/examples/unique_job_with_conditional_parameter.rb +3 -3
- data/examples/unique_job_with_filter_method.rb +5 -2
- data/examples/unique_job_with_nil_unique_args.rb +3 -3
- data/examples/unique_job_with_no_unique_args_method.rb +3 -3
- data/examples/unique_job_withthout_unique_args_parameter.rb +3 -3
- data/examples/unique_on_all_queues_job.rb +1 -1
- data/examples/until_and_while_executing_job.rb +4 -1
- data/examples/until_executed_2_job.rb +5 -5
- data/examples/until_executed_job.rb +5 -5
- data/examples/until_executing_job.rb +1 -1
- data/examples/until_expired_job.rb +1 -1
- data/examples/until_global_expired_job.rb +1 -1
- data/examples/while_executing_job.rb +2 -2
- data/examples/while_executing_reject_job.rb +2 -2
- data/examples/without_argument_job.rb +1 -1
- data/lib/sidekiq_unique_jobs.rb +30 -0
- data/lib/sidekiq_unique_jobs/client/middleware.rb +12 -1
- data/lib/sidekiq_unique_jobs/connection.rb +5 -1
- data/lib/sidekiq_unique_jobs/constants.rb +3 -0
- data/lib/sidekiq_unique_jobs/digests.rb +111 -0
- data/lib/sidekiq_unique_jobs/exceptions.rb +15 -16
- data/lib/sidekiq_unique_jobs/lock/base_lock.rb +44 -3
- data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +13 -3
- data/lib/sidekiq_unique_jobs/lock/until_executed.rb +8 -1
- data/lib/sidekiq_unique_jobs/lock/until_executing.rb +8 -1
- data/lib/sidekiq_unique_jobs/lock/until_expired.rb +14 -2
- data/lib/sidekiq_unique_jobs/lock/while_executing.rb +19 -5
- data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +16 -63
- data/lib/sidekiq_unique_jobs/locksmith.rb +36 -13
- data/lib/sidekiq_unique_jobs/logging.rb +24 -1
- data/lib/sidekiq_unique_jobs/normalizer.rb +6 -0
- data/lib/sidekiq_unique_jobs/on_conflict.rb +24 -0
- data/lib/sidekiq_unique_jobs/on_conflict/log.rb +20 -0
- data/lib/sidekiq_unique_jobs/on_conflict/null_strategy.rb +16 -0
- data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +17 -0
- data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +72 -0
- data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +24 -0
- data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +28 -0
- data/lib/sidekiq_unique_jobs/options_with_fallback.rb +19 -4
- data/lib/sidekiq_unique_jobs/scripts.rb +31 -0
- data/lib/sidekiq_unique_jobs/server/middleware.rb +10 -0
- data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +15 -1
- data/lib/sidekiq_unique_jobs/timeout/calculator.rb +17 -4
- data/lib/sidekiq_unique_jobs/unique_args.rb +47 -5
- data/lib/sidekiq_unique_jobs/unlockable.rb +10 -0
- data/lib/sidekiq_unique_jobs/util.rb +12 -7
- data/lib/sidekiq_unique_jobs/version.rb +1 -1
- data/lib/sidekiq_unique_jobs/web.rb +51 -0
- data/lib/sidekiq_unique_jobs/web/helpers.rb +37 -0
- data/lib/sidekiq_unique_jobs/web/views/unique_digest.erb +28 -0
- data/lib/sidekiq_unique_jobs/web/views/unique_digests.erb +42 -0
- data/redis/create.lua +4 -2
- data/redis/delete.lua +3 -1
- data/redis/delete_by_digest.lua +22 -0
- data/redis/signal.lua +3 -1
- data/sidekiq-unique-jobs.gemspec +2 -0
- metadata +49 -3
- data/lib/sidekiq_unique_jobs/lock/while_executing_requeue.rb +0 -21
@@ -4,11 +4,20 @@ require 'sidekiq_unique_jobs/server/middleware'
|
|
4
4
|
|
5
5
|
module SidekiqUniqueJobs
|
6
6
|
module Client
|
7
|
+
# The unique sidekiq middleware for the client push
|
8
|
+
#
|
9
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
7
10
|
class Middleware
|
8
11
|
include SidekiqUniqueJobs::Logging
|
9
12
|
include OptionsWithFallback
|
10
13
|
|
11
|
-
#
|
14
|
+
# Calls this client middleware
|
15
|
+
# Used from Sidekiq.process_single
|
16
|
+
# @param [String] worker_class name of the sidekiq worker class
|
17
|
+
# @param [Hash] item a sidekiq job hash
|
18
|
+
# @param [String] queue name of the queue
|
19
|
+
# @param [Sidekiq::RedisConnection, ConnectionPool] redis_pool the redis connection
|
20
|
+
# @yield when uniqueness is disable or lock successful
|
12
21
|
def call(worker_class, item, queue, redis_pool = nil)
|
13
22
|
@worker_class = worker_class
|
14
23
|
@item = item
|
@@ -20,6 +29,8 @@ module SidekiqUniqueJobs
|
|
20
29
|
|
21
30
|
private
|
22
31
|
|
32
|
+
# The sidekiq job hash
|
33
|
+
# @return [Hash] the Sidekiq job hash
|
23
34
|
attr_reader :item
|
24
35
|
|
25
36
|
def success?
|
@@ -1,12 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SidekiqUniqueJobs
|
4
|
+
# Shared module for dealing with redis connections
|
5
|
+
#
|
6
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
4
7
|
module Connection
|
5
8
|
def self.included(base)
|
6
9
|
base.send(:extend, self)
|
7
10
|
end
|
8
11
|
|
9
|
-
#
|
12
|
+
# Creates a connection to redis
|
13
|
+
# @return [Sidekiq::RedisConnection, ConnectionPool] a connection to redis
|
10
14
|
def redis(redis_pool = nil)
|
11
15
|
if redis_pool
|
12
16
|
redis_pool.with { |conn| yield conn }
|
@@ -14,6 +14,9 @@ module SidekiqUniqueJobs
|
|
14
14
|
UNIQUE_ARGS_KEY ||= 'unique_args'
|
15
15
|
UNIQUE_DIGEST_KEY ||= 'unique_digest'
|
16
16
|
UNIQUE_KEY ||= 'unique'
|
17
|
+
UNIQUE_SET ||= 'unique:keys'
|
18
|
+
LOCK_KEY ||= 'lock'
|
19
|
+
ON_CONFLICT_KEY ||= 'on_conflict'
|
17
20
|
UNIQUE_ON_ALL_QUEUES_KEY ||= 'unique_on_all_queues' # TODO: Remove in v6.1
|
18
21
|
UNIQUE_PREFIX_KEY ||= 'unique_prefix'
|
19
22
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SidekiqUniqueJobs
|
4
|
+
# Utility module to help manage unique digests in redis.
|
5
|
+
#
|
6
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
7
|
+
module Digests
|
8
|
+
DEFAULT_COUNT = 1_000
|
9
|
+
SCAN_PATTERN = '*'
|
10
|
+
CHUNK_SIZE = 100
|
11
|
+
|
12
|
+
include SidekiqUniqueJobs::Logging
|
13
|
+
include SidekiqUniqueJobs::Connection
|
14
|
+
extend self # rubocop:disable Style/ModuleFunction
|
15
|
+
|
16
|
+
# Return unique digests matching pattern
|
17
|
+
#
|
18
|
+
# @param [String] pattern a pattern to match with
|
19
|
+
# @param [Integer] count the maximum number to match
|
20
|
+
# @return [Array<String>] with unique digests
|
21
|
+
def all(pattern: SCAN_PATTERN, count: DEFAULT_COUNT)
|
22
|
+
redis { |conn| conn.sscan_each(UNIQUE_SET, match: pattern, count: count).to_a }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Get a total count of unique digests
|
26
|
+
#
|
27
|
+
# @return [Integer] number of digests
|
28
|
+
def count
|
29
|
+
redis { |conn| conn.scard(UNIQUE_SET) }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Deletes unique digest either by a digest or pattern
|
33
|
+
#
|
34
|
+
# @param [String] digest the full digest to delete
|
35
|
+
# @param [String] pattern a key pattern to match with
|
36
|
+
# @param [Integer] count the maximum number
|
37
|
+
# @raise [ArgumentError] when both pattern and digest are nil
|
38
|
+
# @return [Array<String>] with unique digests
|
39
|
+
def del(digest: nil, pattern: nil, count: DEFAULT_COUNT)
|
40
|
+
return delete_by_pattern(pattern, count: count) if pattern
|
41
|
+
return delete_by_digest(digest) if digest
|
42
|
+
|
43
|
+
raise ArgumentError, 'either digest or pattern need to be provided'
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Deletes unique digests by pattern
|
49
|
+
#
|
50
|
+
# @param [String] pattern a key pattern to match with
|
51
|
+
# @param [Integer] count the maximum number
|
52
|
+
# @return [Array<String>] with unique digests
|
53
|
+
def delete_by_pattern(pattern, count: DEFAULT_COUNT)
|
54
|
+
result, elapsed = timed do
|
55
|
+
digests = all(pattern: pattern, count: count)
|
56
|
+
batch_delete(digests)
|
57
|
+
digests.size
|
58
|
+
end
|
59
|
+
|
60
|
+
log_info("#{__method__}(#{pattern}, count: #{count}) completed in #{elapsed}ms")
|
61
|
+
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get a total count of unique digests
|
66
|
+
#
|
67
|
+
# @param [String] digest a key pattern to match with
|
68
|
+
def delete_by_digest(digest)
|
69
|
+
result, elapsed = timed do
|
70
|
+
Scripts.call(:delete_by_digest, nil, keys: [UNIQUE_SET, digest])
|
71
|
+
count
|
72
|
+
end
|
73
|
+
|
74
|
+
log_info("#{__method__}(#{digest}) completed in #{elapsed}ms")
|
75
|
+
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
def batch_delete(digests) # rubocop:disable Metrics/MethodLength
|
80
|
+
redis do |conn|
|
81
|
+
digests.each_slice(CHUNK_SIZE) do |chunk|
|
82
|
+
conn.pipelined do
|
83
|
+
chunk.each do |digest|
|
84
|
+
conn.del digest
|
85
|
+
conn.srem(UNIQUE_SET, digest)
|
86
|
+
conn.del("#{digest}:EXISTS")
|
87
|
+
conn.del("#{digest}:GRABBED")
|
88
|
+
conn.del("#{digest}:VERSION")
|
89
|
+
conn.del("#{digest}:AVAILABLE")
|
90
|
+
conn.del("#{digest}:RUN:EXISTS")
|
91
|
+
conn.del("#{digest}:RUN:GRABBED")
|
92
|
+
conn.del("#{digest}:RUN:VERSION")
|
93
|
+
conn.del("#{digest}:RUN:AVAILABLE")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def timed
|
101
|
+
start = current_time
|
102
|
+
result = yield
|
103
|
+
elapsed = (current_time - start).round(2)
|
104
|
+
[result, elapsed]
|
105
|
+
end
|
106
|
+
|
107
|
+
def current_time
|
108
|
+
Time.now
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -1,30 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SidekiqUniqueJobs
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
class
|
4
|
+
# Error raised when a Lua script fails to execute
|
5
|
+
#
|
6
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
7
|
+
class Conflict < StandardError
|
8
|
+
def initialize(item)
|
9
|
+
super("Item with the key: #{item[UNIQUE_DIGEST_KEY]} is already scheduled or processing")
|
10
|
+
end
|
8
11
|
end
|
9
12
|
|
13
|
+
# Error raised from {OnConflict::Raise}
|
14
|
+
#
|
15
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
10
16
|
class ScriptError < StandardError
|
17
|
+
# @param [Symbol] file_name the name of the lua script
|
18
|
+
# @param [Redis::CommandError] source_exception exception to handle
|
11
19
|
def initialize(file_name:, source_exception:)
|
12
20
|
super("Problem compiling #{file_name}. Message: #{source_exception.message}")
|
13
21
|
end
|
14
22
|
end
|
15
23
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
class JidMissing < ArgumentError
|
20
|
-
end
|
21
|
-
|
22
|
-
class MaxLockTimeMissing < ArgumentError
|
23
|
-
end
|
24
|
-
|
25
|
-
class UnexpectedValue < StandardError
|
26
|
-
end
|
27
|
-
|
24
|
+
# Error raised from {OptionsWithFallback#lock_class}
|
25
|
+
#
|
26
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
28
27
|
class UnknownLock < StandardError
|
29
28
|
end
|
30
29
|
end
|
@@ -2,43 +2,80 @@
|
|
2
2
|
|
3
3
|
module SidekiqUniqueJobs
|
4
4
|
class Lock
|
5
|
+
# Abstract base class for locks
|
6
|
+
#
|
7
|
+
# @abstract
|
8
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
5
9
|
class BaseLock
|
6
10
|
include SidekiqUniqueJobs::Logging
|
7
11
|
|
12
|
+
# @param [Hash] item the Sidekiq job hash
|
13
|
+
# @param [Proc] callback the callback to use after unlock
|
14
|
+
# @param [Sidekiq::RedisConnection, ConnectionPool] redis_pool the redis connection
|
8
15
|
def initialize(item, callback, redis_pool = nil)
|
9
16
|
@item = prepare_item(item)
|
10
17
|
@callback = callback
|
11
18
|
@redis_pool = redis_pool
|
12
19
|
end
|
13
20
|
|
21
|
+
# Handles locking of sidekiq jobs.
|
22
|
+
# Will call a conflict strategy if lock can't be achieved.
|
23
|
+
# @return [String] the sidekiq job id
|
14
24
|
def lock
|
15
|
-
locksmith.lock(item[LOCK_TIMEOUT_KEY])
|
25
|
+
if (token = locksmith.lock(item[LOCK_TIMEOUT_KEY]))
|
26
|
+
token
|
27
|
+
else
|
28
|
+
strategy.call
|
29
|
+
end
|
16
30
|
end
|
17
31
|
|
32
|
+
# Execute the job in the Sidekiq server processor
|
33
|
+
# @raise [NotImplementedError] needs to be implemented in child class
|
18
34
|
def execute
|
19
35
|
raise NotImplementedError, "##{__method__} needs to be implemented in #{self.class}"
|
20
36
|
end
|
21
37
|
|
38
|
+
# Unlocks the job from redis
|
39
|
+
# @return [String] sidekiq job id when successful
|
40
|
+
# @return [false] when unsuccessful
|
22
41
|
def unlock
|
23
42
|
locksmith.signal(item[JID_KEY]) # Only signal to release the lock
|
24
43
|
end
|
25
44
|
|
45
|
+
# Deletes the job from redis if it is locked.
|
26
46
|
def delete
|
27
47
|
locksmith.delete # Soft delete (don't forcefully remove when expiration is set)
|
28
48
|
end
|
29
49
|
|
50
|
+
# Forcefully deletes the job from redis.
|
51
|
+
# This is good for jobs when a previous lock was not unlocked
|
30
52
|
def delete!
|
31
53
|
locksmith.delete! # Force delete the lock
|
32
54
|
end
|
33
55
|
|
56
|
+
# Checks if the item has achieved a lock
|
57
|
+
# @return [true] when this jid has locked the job
|
58
|
+
# @return [false] when this jid has not locked the job
|
34
59
|
def locked?
|
35
60
|
locksmith.locked?(item[JID_KEY])
|
36
61
|
end
|
37
62
|
|
38
63
|
private
|
39
64
|
|
40
|
-
|
65
|
+
# The sidekiq job hash
|
66
|
+
# @return [Hash] the Sidekiq job hash
|
67
|
+
attr_reader :item
|
41
68
|
|
69
|
+
# The sidekiq redis pool
|
70
|
+
# @return [Sidekiq::RedisConnection, ConnectionPool, NilClass] the redis connection
|
71
|
+
attr_reader :redis_pool
|
72
|
+
|
73
|
+
# The sidekiq job hash
|
74
|
+
# @return [Proc] the callback to use after unlock
|
75
|
+
attr_reader :callback
|
76
|
+
|
77
|
+
# The interface to the locking mechanism
|
78
|
+
# @return [SidekiqUniqueJobs::Locksmith]
|
42
79
|
def locksmith
|
43
80
|
@locksmith ||= SidekiqUniqueJobs::Locksmith.new(item, redis_pool)
|
44
81
|
end
|
@@ -75,9 +112,13 @@ module SidekiqUniqueJobs
|
|
75
112
|
def callback_safely
|
76
113
|
callback&.call
|
77
114
|
rescue StandardError
|
78
|
-
log_warn("The
|
115
|
+
log_warn("The unique_key: #{item[UNIQUE_DIGEST_KEY]} has been unlocked but the #after_unlock callback failed!")
|
79
116
|
raise
|
80
117
|
end
|
118
|
+
|
119
|
+
def strategy
|
120
|
+
@strategy ||= OnConflict.find_strategy(item[ON_CONFLICT_KEY]).new(item)
|
121
|
+
end
|
81
122
|
end
|
82
123
|
end
|
83
124
|
end
|
@@ -2,14 +2,24 @@
|
|
2
2
|
|
3
3
|
module SidekiqUniqueJobs
|
4
4
|
class Lock
|
5
|
+
# Locks jobs while the job is executing in the server process
|
6
|
+
# - Locks on perform_in or perform_async (see {UntilExecuting})
|
7
|
+
# - Unlocks before yielding to the worker's perform method (see {UntilExecuting})
|
8
|
+
# - Locks before yielding to the worker's perform method (see {WhileExecuting})
|
9
|
+
# - Unlocks after yielding to the worker's perform method (see {WhileExecuting})
|
10
|
+
#
|
11
|
+
# See {#lock} for more information about the client.
|
12
|
+
# See {#execute} for more information about the server
|
13
|
+
#
|
14
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
5
15
|
class UntilAndWhileExecuting < BaseLock
|
16
|
+
# Executes in the Sidekiq server process
|
17
|
+
# @yield to the worker class perform method
|
6
18
|
def execute
|
7
19
|
return unless locked?
|
8
20
|
unlock
|
9
21
|
|
10
|
-
runtime_lock.execute
|
11
|
-
yield if block_given?
|
12
|
-
end
|
22
|
+
runtime_lock.execute { yield }
|
13
23
|
end
|
14
24
|
|
15
25
|
def runtime_lock
|
@@ -2,12 +2,19 @@
|
|
2
2
|
|
3
3
|
module SidekiqUniqueJobs
|
4
4
|
class Lock
|
5
|
+
# Locks jobs until the server is done executing the job
|
6
|
+
# - Locks on perform_in or perform_async
|
7
|
+
# - Unlocks after yielding to the worker's perform method
|
8
|
+
#
|
9
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
5
10
|
class UntilExecuted < BaseLock
|
6
11
|
OK ||= 'OK'
|
7
12
|
|
13
|
+
# Executes in the Sidekiq server process
|
14
|
+
# @yield to the worker class perform method
|
8
15
|
def execute
|
9
16
|
return unless locked?
|
10
|
-
with_cleanup { yield
|
17
|
+
with_cleanup { yield }
|
11
18
|
end
|
12
19
|
end
|
13
20
|
end
|
@@ -2,10 +2,17 @@
|
|
2
2
|
|
3
3
|
module SidekiqUniqueJobs
|
4
4
|
class Lock
|
5
|
+
# Locks jobs until {#execute} starts
|
6
|
+
# - Locks on perform_in or perform_async
|
7
|
+
# - Unlocks after yielding to the worker's perform method
|
8
|
+
#
|
9
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
5
10
|
class UntilExecuting < BaseLock
|
11
|
+
# Executes in the Sidekiq server process
|
12
|
+
# @yield to the worker class perform method
|
6
13
|
def execute
|
7
14
|
unlock_with_callback
|
8
|
-
yield
|
15
|
+
yield
|
9
16
|
end
|
10
17
|
end
|
11
18
|
end
|
@@ -2,15 +2,27 @@
|
|
2
2
|
|
3
3
|
module SidekiqUniqueJobs
|
4
4
|
class Lock
|
5
|
+
# Locks jobs until the lock has expired
|
6
|
+
# - Locks on perform_in or perform_async
|
7
|
+
# - Unlocks when the expiration is hit
|
8
|
+
#
|
9
|
+
# See {#lock} for more information about the client.
|
10
|
+
# See {#execute} for more information about the server
|
11
|
+
#
|
12
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
5
13
|
class UntilExpired < BaseLock
|
14
|
+
# Prevents these locks from being unlocked
|
15
|
+
# @return [true] always returns true
|
6
16
|
def unlock
|
7
17
|
true
|
8
18
|
end
|
9
19
|
|
20
|
+
# Executes in the Sidekiq server process
|
21
|
+
# @yield to the worker class perform method
|
10
22
|
def execute
|
11
23
|
return unless locked?
|
12
|
-
yield
|
13
|
-
# this lock does not handle after_unlock since we don't know when that would
|
24
|
+
yield
|
25
|
+
# this lock does not handle after_unlock since we don't know when that would happen
|
14
26
|
end
|
15
27
|
end
|
16
28
|
end
|
@@ -2,24 +2,38 @@
|
|
2
2
|
|
3
3
|
module SidekiqUniqueJobs
|
4
4
|
class Lock
|
5
|
+
# Locks jobs while the job is executing in the server process
|
6
|
+
# - Locks before yielding to the worker's perform method
|
7
|
+
# - Unlocks after yielding to the worker's perform method
|
8
|
+
#
|
9
|
+
# See {#lock} for more information about the client.
|
10
|
+
# See {#execute} for more information about the server
|
11
|
+
#
|
12
|
+
# @author Mikael Henriksson <mikael@zoolutions.se>
|
5
13
|
class WhileExecuting < BaseLock
|
6
14
|
RUN_SUFFIX ||= ':RUN'
|
7
15
|
|
16
|
+
# @param [Hash] item the Sidekiq job hash
|
17
|
+
# @param [Proc] callback callback to call after unlock
|
18
|
+
# @param [Sidekiq::RedisConnection, ConnectionPool] redis_pool the redis connection
|
8
19
|
def initialize(item, callback, redis_pool = nil)
|
9
20
|
super(item, callback, redis_pool)
|
10
21
|
append_unique_key_suffix
|
11
22
|
end
|
12
23
|
|
13
|
-
#
|
14
|
-
#
|
24
|
+
# Simulate that a client lock was achieved.
|
25
|
+
# These locks should only ever be created in the server process.
|
26
|
+
# @return [true] always returns true
|
15
27
|
def lock
|
16
28
|
true
|
17
29
|
end
|
18
30
|
|
19
|
-
#
|
31
|
+
# Executes in the Sidekiq server process.
|
32
|
+
# These jobs are locked in the server process not from the client
|
33
|
+
# @yield to the worker class perform method
|
20
34
|
def execute
|
21
|
-
return unless locksmith.lock(item[LOCK_TIMEOUT_KEY])
|
22
|
-
with_cleanup { yield
|
35
|
+
return strategy.call unless locksmith.lock(item[LOCK_TIMEOUT_KEY])
|
36
|
+
with_cleanup { yield }
|
23
37
|
end
|
24
38
|
|
25
39
|
private
|