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 CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.3.1 (2011-07-16)
2
+
3
+ * Pass job arguments to `lock_timeout`. (Bob Potter)
4
+ * Added `refresh_lock!` method for long running jobs. (Bob Potter)
5
+
1
6
  ## 0.3.0 (2011-07-16)
2
7
 
3
8
  * Ability to customize redis connection used for storing locks.
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Resque Lock Timeout
2
2
  ===================
3
3
 
4
- A [Resque][rq] plugin. Requires Resque 1.8.0.
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
- def acquire_lock_algorithm!(lock_key)
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
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: resque-lock-timeout
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.3.0
5
+ version: 0.3.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Luke Antins