resque-lock-timeout 0.3.0 → 0.3.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.
- data/HISTORY.md +5 -0
- data/README.md +25 -1
- data/Rakefile +1 -1
- data/lib/resque/plugins/lock_timeout.rb +23 -5
- data/test/lock_test.rb +30 -0
- data/test/test_jobs.rb +31 -0
- metadata +1 -1
data/HISTORY.md
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Resque Lock Timeout
|
2
2
|
===================
|
3
3
|
|
4
|
-
A [Resque][rq] plugin. Requires Resque
|
4
|
+
A [Resque][rq] plugin. Requires Resque >= 01.8.0.
|
5
5
|
|
6
6
|
resque-lock-timeout adds locking, with optional timeout/deadlock handling to
|
7
7
|
resque jobs.
|
@@ -110,6 +110,30 @@ change this you may override `lock_redis`.
|
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
113
|
+
### Setting Timeout At Runtime
|
114
|
+
|
115
|
+
You may define the `lock_timeout` method to adjust the timeout at runtime
|
116
|
+
using job arguments. e.g.
|
117
|
+
|
118
|
+
class UpdateNetworkGraph
|
119
|
+
extend Resque::Plugins::LockTimeout
|
120
|
+
@queue = :network_graph
|
121
|
+
|
122
|
+
def self.lock_timeout(repo_id, timeout_minutes)
|
123
|
+
60 * timeout_minutes
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.perform(repo_id, timeout_minutes = 1)
|
127
|
+
heavy_lifting
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
### Helper Methods
|
132
|
+
|
133
|
+
* `locked?` - checks if the lock is currently held.
|
134
|
+
* `refresh_lock!` - Refresh the lock, useful for jobs that are taking longer
|
135
|
+
then usual but your okay with them holding on to the lock a little longer.
|
136
|
+
|
113
137
|
### Callbacks
|
114
138
|
|
115
139
|
Several callbacks are available to override and implement your own logic, e.g.
|
data/Rakefile
CHANGED
@@ -14,7 +14,7 @@ desc 'Build Yardoc documentation.'
|
|
14
14
|
YARD::Rake::YardocTask.new :yardoc do |t|
|
15
15
|
t.files = ['lib/**/*.rb']
|
16
16
|
t.options = ['--output-dir', "doc/",
|
17
|
-
'--files', 'LICENSE',
|
17
|
+
'--files', 'LICENSE,HISTORY.md',
|
18
18
|
'--readme', 'README.md',
|
19
19
|
'--title', 'resque-lock-timeout documentation']
|
20
20
|
end
|
@@ -59,6 +59,7 @@ module Resque
|
|
59
59
|
# The default looks like this:
|
60
60
|
# `resque-lock-timeout:<class name>:<identifier>`
|
61
61
|
#
|
62
|
+
# @param [Array] args job arguments
|
62
63
|
# @return [String] redis key
|
63
64
|
def redis_lock_key(*args)
|
64
65
|
['lock', name, identifier(*args)].compact.join(":")
|
@@ -67,8 +68,9 @@ module Resque
|
|
67
68
|
# Number of seconds the lock may be held for.
|
68
69
|
# A value of 0 or below will lock without a timeout.
|
69
70
|
#
|
71
|
+
# @param [Array] args job arguments
|
70
72
|
# @return [Fixnum]
|
71
|
-
def lock_timeout
|
73
|
+
def lock_timeout(*args)
|
72
74
|
@lock_timeout ||= 0
|
73
75
|
end
|
74
76
|
|
@@ -105,12 +107,12 @@ module Resque
|
|
105
107
|
acquired = false
|
106
108
|
lock_key = redis_lock_key(*args)
|
107
109
|
|
108
|
-
unless lock_timeout > 0
|
110
|
+
unless lock_timeout(*args) > 0
|
109
111
|
# Acquire without using a timeout.
|
110
112
|
acquired = true if lock_redis.setnx(lock_key, true)
|
111
113
|
else
|
112
114
|
# Acquire using the timeout algorithm.
|
113
|
-
acquired, lock_until = acquire_lock_algorithm!(lock_key)
|
115
|
+
acquired, lock_until = acquire_lock_algorithm!(lock_key, *args)
|
114
116
|
end
|
115
117
|
|
116
118
|
lock_failed(*args) if !acquired
|
@@ -120,9 +122,12 @@ module Resque
|
|
120
122
|
# Attempts to aquire the lock using a timeout / deadlock algorithm.
|
121
123
|
#
|
122
124
|
# Locking algorithm: http://code.google.com/p/redis/wiki/SetnxCommand
|
123
|
-
|
125
|
+
#
|
126
|
+
# @param [String] lock_key redis lock key
|
127
|
+
# @param [Array] args job arguments
|
128
|
+
def acquire_lock_algorithm!(lock_key, *args)
|
124
129
|
now = Time.now.to_i
|
125
|
-
lock_until = now + lock_timeout
|
130
|
+
lock_until = now + lock_timeout(*args)
|
126
131
|
acquired = false
|
127
132
|
|
128
133
|
return [true, lock_until] if lock_redis.setnx(lock_key, lock_until)
|
@@ -143,11 +148,24 @@ module Resque
|
|
143
148
|
end
|
144
149
|
|
145
150
|
# Release the lock.
|
151
|
+
#
|
152
|
+
# @param [Array] args job arguments
|
146
153
|
def release_lock!(*args)
|
147
154
|
lock_redis.del(redis_lock_key(*args))
|
148
155
|
end
|
149
156
|
|
157
|
+
# Refresh the lock.
|
158
|
+
#
|
159
|
+
# @param [Array] args job arguments
|
160
|
+
def refresh_lock!(*args)
|
161
|
+
now = Time.now.to_i
|
162
|
+
lock_until = now + lock_timeout(*args)
|
163
|
+
lock_redis.set(redis_lock_key(*args), lock_until)
|
164
|
+
end
|
165
|
+
|
150
166
|
# Where the magic happens.
|
167
|
+
#
|
168
|
+
# @param [Array] args job arguments
|
151
169
|
def around_perform_lock(*args)
|
152
170
|
# Abort if another job holds the lock.
|
153
171
|
return unless lock_until = acquire_lock!(*args)
|
data/test/lock_test.rb
CHANGED
@@ -126,4 +126,34 @@ class LockTest < Test::Unit::TestCase
|
|
126
126
|
assert_nil lock_redis.get('specific_redis')
|
127
127
|
assert_equal 1, $success, 'job should increment success'
|
128
128
|
end
|
129
|
+
|
130
|
+
def test_lock_timeout_accepts_job_args
|
131
|
+
# setup our time values.
|
132
|
+
now = Time.now.to_i
|
133
|
+
one_hour_ahead = now + 3600
|
134
|
+
twelve_hours_ahead = (now + (3600 * 12))
|
135
|
+
|
136
|
+
# 1 hour ahead.
|
137
|
+
assert VariableTimeoutJob.acquire_lock!(1) >= one_hour_ahead, 'lock should be 1 hour ahead'
|
138
|
+
VariableTimeoutJob.release_lock!
|
139
|
+
|
140
|
+
# 12 hours ahead.
|
141
|
+
assert VariableTimeoutJob.acquire_lock!(12) >= twelve_hours_ahead, 'lock should be 12 hours ahead'
|
142
|
+
VariableTimeoutJob.release_lock!
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_refresh_lock!
|
146
|
+
# grab the lock.
|
147
|
+
RefreshLockJob.acquire_lock!
|
148
|
+
sleep 2
|
149
|
+
|
150
|
+
# grab the initial lock timeout then refresh the lock.
|
151
|
+
initial_lock = Resque.redis.get(RefreshLockJob.redis_lock_key).to_i
|
152
|
+
RefreshLockJob.refresh_lock!
|
153
|
+
|
154
|
+
# lock should now be at least 1 second more then the initial lock.
|
155
|
+
latest_lock = Resque.redis.get(RefreshLockJob.redis_lock_key).to_i
|
156
|
+
diff = latest_lock - initial_lock
|
157
|
+
assert diff >= 1, 'diff between initial lock and refreshed lock should be at least 1 second'
|
158
|
+
end
|
129
159
|
end
|
data/test/test_jobs.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# Slow successful job, does not use timeout algorithm.
|
1
2
|
class SlowJob
|
2
3
|
extend Resque::Plugins::LockTimeout
|
3
4
|
@queue = :test
|
@@ -12,6 +13,7 @@ class SlowJob
|
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
16
|
+
# Fast successful job, does not use timeout algorithm.
|
15
17
|
class FastJob
|
16
18
|
extend Resque::Plugins::LockTimeout
|
17
19
|
@queue = :test
|
@@ -25,6 +27,7 @@ class FastJob
|
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
30
|
+
# Job that fails quickly, does not use timeout algorithm.
|
28
31
|
class FailingFastJob
|
29
32
|
extend Resque::Plugins::LockTimeout
|
30
33
|
@queue = :test
|
@@ -35,6 +38,7 @@ class FailingFastJob
|
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
41
|
+
# Job that enables the timeout algorithm.
|
38
42
|
class SlowWithTimeoutJob
|
39
43
|
extend Resque::Plugins::LockTimeout
|
40
44
|
@queue = :test
|
@@ -46,6 +50,7 @@ class SlowWithTimeoutJob
|
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
53
|
+
# Job that releases its lock AFTER its expired.
|
49
54
|
class ExpireBeforeReleaseJob
|
50
55
|
extend Resque::Plugins::LockTimeout
|
51
56
|
@queue = :test
|
@@ -61,6 +66,7 @@ class ExpireBeforeReleaseJob
|
|
61
66
|
end
|
62
67
|
end
|
63
68
|
|
69
|
+
# Job that uses a spesific redis connection just for storing locks.
|
64
70
|
class SpecificRedisJob
|
65
71
|
extend Resque::Plugins::LockTimeout
|
66
72
|
@queue = :test
|
@@ -81,4 +87,29 @@ class SpecificRedisJob
|
|
81
87
|
$success += 1
|
82
88
|
sleep 0.2
|
83
89
|
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Job that uses a different lock timeout value depending on job args.
|
93
|
+
class VariableTimeoutJob
|
94
|
+
extend Resque::Plugins::LockTimeout
|
95
|
+
@queue = :test
|
96
|
+
|
97
|
+
def self.identifier(*args)
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.lock_timeout(extra_timeout)
|
102
|
+
3600 * extra_timeout
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.perform
|
106
|
+
$success += 1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Job to simulate a long running job that refreshes its hold on the lock.
|
111
|
+
class RefreshLockJob
|
112
|
+
extend Resque::Plugins::LockTimeout
|
113
|
+
@queue = :test
|
114
|
+
@lock_timeout = 60
|
84
115
|
end
|