resque-lock-timeout 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|