resque-fine-grained-locks 1.0.0
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/LICENSE +20 -0
- data/README.md +48 -0
- data/Rakefile +26 -0
- data/lib/resque/plugins/execution_lock.rb +24 -0
- data/lib/resque/plugins/queue_lock.rb +17 -0
- data/test/execution_lock_test.rb +84 -0
- data/test/queue_lock_test.rb +40 -0
- metadata +75 -0
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) Chris Wanstrath, Ray Krueger
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
Software), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Resque Fine-Grained Locks
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
A [Resque][rq] plugin. Requires Resque 1.7.0.
|
|
5
|
+
|
|
6
|
+
If you want only one instance of your job queued at a time, extend it
|
|
7
|
+
with Resque::Plugins::QueueLock. You will be able to enqueue another
|
|
8
|
+
job if one is currently executing.
|
|
9
|
+
|
|
10
|
+
For example:
|
|
11
|
+
|
|
12
|
+
require 'resque/plugins/queue_lock'
|
|
13
|
+
|
|
14
|
+
class UpdateNetworkGraph
|
|
15
|
+
extend Resque::Plugins::QueueLock
|
|
16
|
+
|
|
17
|
+
def self.perform(repo_id)
|
|
18
|
+
heavy_lifting
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
If you want only one instance of your job to execute at a time, extend
|
|
23
|
+
it with Resque::Plugins::ExecutionLock. You can optionally use
|
|
24
|
+
resque-retry if you want to retry locked jobs without them failing.
|
|
25
|
+
|
|
26
|
+
For example:
|
|
27
|
+
|
|
28
|
+
require 'resque/plugins/execution_lock'
|
|
29
|
+
|
|
30
|
+
class UpdateNetworkGraph
|
|
31
|
+
extend Resque::Plugins::Retry
|
|
32
|
+
extend Resque::Plugins::ExecutionLock
|
|
33
|
+
|
|
34
|
+
@retry_exceptions = [
|
|
35
|
+
Resque::Plugins::ExecutionLock::JobIsLocked
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
def self.perform(repo_id)
|
|
39
|
+
heavy_lifting
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
And in your resque initializer (config/resque.rb):
|
|
44
|
+
|
|
45
|
+
require 'resque/failure/multiple'
|
|
46
|
+
|
|
47
|
+
Resque::Failure::MultipleWithRetrySuppression.classes = [Resque::Plugins::ExecutionLock::JobIsLocked]
|
|
48
|
+
Resque::Failure.backend = Resque::Failure::MultipleWithRetrySuppression
|
data/Rakefile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'rake/testtask'
|
|
2
|
+
require 'rdoc/task'
|
|
3
|
+
|
|
4
|
+
def command?(command)
|
|
5
|
+
system("type #{command} > /dev/null")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# Tests
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
task :default => :test
|
|
13
|
+
|
|
14
|
+
if command? :turn
|
|
15
|
+
desc "Run tests"
|
|
16
|
+
task :test do
|
|
17
|
+
suffix = "-n #{ENV['TEST']}" if ENV['TEST']
|
|
18
|
+
sh "turn test/*.rb #{suffix}"
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
Rake::TestTask.new do |t|
|
|
22
|
+
t.libs << 'lib'
|
|
23
|
+
t.pattern = 'test/**/*_test.rb'
|
|
24
|
+
t.verbose = false
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Resque
|
|
2
|
+
module Plugins
|
|
3
|
+
module ExecutionLock
|
|
4
|
+
class JobIsLocked < RuntimeError; end
|
|
5
|
+
|
|
6
|
+
def execution_lock(*args)
|
|
7
|
+
"execution_lock:#{name}-#{args.to_s}"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def before_perform_execution_lock(*args)
|
|
11
|
+
raise JobIsLocked unless Resque.redis.setnx(execution_lock(*args), true)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def around_perform_execution_lock(*args)
|
|
15
|
+
begin
|
|
16
|
+
yield
|
|
17
|
+
ensure
|
|
18
|
+
Resque.redis.del(execution_lock(*args))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Resque
|
|
2
|
+
module Plugins
|
|
3
|
+
module QueueLock
|
|
4
|
+
def queue_lock(*args)
|
|
5
|
+
"queue_lock:#{name}-#{args.to_s}"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def before_enqueue_queue_lock(*args)
|
|
9
|
+
Resque.redis.setnx(queue_lock(*args), true)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def before_perform_queue_lock(*args)
|
|
13
|
+
Resque.redis.del(queue_lock(*args))
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'resque'
|
|
3
|
+
require 'resque/plugins/execution_lock'
|
|
4
|
+
|
|
5
|
+
$execution_lock_do = nil
|
|
6
|
+
$execution_lock_results = nil
|
|
7
|
+
|
|
8
|
+
class ExecutionLockTestJob
|
|
9
|
+
extend Resque::Plugins::ExecutionLock
|
|
10
|
+
@queue = :execution_lock_test
|
|
11
|
+
|
|
12
|
+
def self.perform(success_type)
|
|
13
|
+
if $execution_lock_do
|
|
14
|
+
block, $execution_lock_do = $execution_lock_do, nil
|
|
15
|
+
block.call
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
case success_type.to_sym
|
|
19
|
+
when :success
|
|
20
|
+
$execution_lock_results << :success
|
|
21
|
+
when :fail
|
|
22
|
+
raise RuntimeError, "Purposeful fail!"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class ExecutionLockTest < Test::Unit::TestCase
|
|
28
|
+
def setup
|
|
29
|
+
Resque.redis.del('queue:execution_lock_test')
|
|
30
|
+
Resque.redis.del(ExecutionLockTestJob.execution_lock('success'))
|
|
31
|
+
Resque.redis.del(ExecutionLockTestJob.execution_lock('fail'))
|
|
32
|
+
|
|
33
|
+
$execution_lock_do = nil
|
|
34
|
+
$execution_lock_results = []
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def perform_job(success_type, &block)
|
|
38
|
+
$execution_lock_do = block if block_given?
|
|
39
|
+
payload = {"class" => "ExecutionLockTestJob", "args" => success_type.to_s}
|
|
40
|
+
Resque::Job.new(:execution_lock_test, payload).perform
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_lint
|
|
44
|
+
assert_nothing_raised do
|
|
45
|
+
Resque::Plugin.lint(Resque::Plugins::ExecutionLock)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_multiple_jobs_can_run_sequentially
|
|
50
|
+
perform_job(:success)
|
|
51
|
+
assert_equal [:success], $execution_lock_results
|
|
52
|
+
|
|
53
|
+
perform_job(:success)
|
|
54
|
+
assert_equal [:success, :success], $execution_lock_results
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_subsequent_jobs_can_run_after_a_job_failure
|
|
58
|
+
assert_raise RuntimeError do
|
|
59
|
+
perform_job(:fail)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
perform_job(:success)
|
|
63
|
+
assert_equal [:success], $execution_lock_results
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_multiple_jobs_cannot_run_simultaneously
|
|
67
|
+
perform_job(:success) do
|
|
68
|
+
assert_raise Resque::Plugins::ExecutionLock::JobIsLocked do
|
|
69
|
+
perform_job(:success)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
assert_equal [:success], $execution_lock_results
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_locked_job_does_not_clear_lock
|
|
76
|
+
perform_job(:success) do
|
|
77
|
+
assert_raise Resque::Plugins::ExecutionLock::JobIsLocked do
|
|
78
|
+
perform_job(:success)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
assert Resque.redis.exists(ExecutionLockTestJob.execution_lock('success'))
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'resque'
|
|
3
|
+
require 'resque/plugins/queue_lock'
|
|
4
|
+
|
|
5
|
+
$counter = 0
|
|
6
|
+
|
|
7
|
+
class QueueLockTest < Test::Unit::TestCase
|
|
8
|
+
class Job
|
|
9
|
+
extend Resque::Plugins::QueueLock
|
|
10
|
+
@queue = :lock_test
|
|
11
|
+
|
|
12
|
+
def self.perform
|
|
13
|
+
raise "Woah woah woah, that wasn't supposed to happen"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def setup
|
|
18
|
+
Resque.redis.del('queue:lock_test')
|
|
19
|
+
Resque.redis.del(Job.queue_lock)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_lint
|
|
23
|
+
assert_nothing_raised do
|
|
24
|
+
Resque::Plugin.lint(Resque::Plugins::QueueLock)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_version
|
|
29
|
+
major, minor, patch = Resque::Version.split('.')
|
|
30
|
+
assert_equal 1, major.to_i
|
|
31
|
+
assert minor.to_i >= 17
|
|
32
|
+
assert Resque::Plugin.respond_to?(:before_enqueue_hooks)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_lock
|
|
36
|
+
3.times { Resque.enqueue(Job) }
|
|
37
|
+
|
|
38
|
+
assert_equal 1, Resque.redis.llen('queue:lock_test')
|
|
39
|
+
end
|
|
40
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: resque-fine-grained-locks
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 23
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 1
|
|
8
|
+
- 0
|
|
9
|
+
- 0
|
|
10
|
+
version: 1.0.0
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Efficiency 2.0
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2012-02-02 00:00:00 Z
|
|
19
|
+
dependencies: []
|
|
20
|
+
|
|
21
|
+
description: |+
|
|
22
|
+
A Resque plugin. If you want only one instance of your job
|
|
23
|
+
queued or executing at a time, extend it with Resque::Jobs::QueueLock
|
|
24
|
+
and/or Resque::Jobs::ExecutionLock.
|
|
25
|
+
|
|
26
|
+
email: tech@efficiency20.com
|
|
27
|
+
executables: []
|
|
28
|
+
|
|
29
|
+
extensions: []
|
|
30
|
+
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
|
|
33
|
+
files:
|
|
34
|
+
- README.md
|
|
35
|
+
- Rakefile
|
|
36
|
+
- LICENSE
|
|
37
|
+
- lib/resque/plugins/execution_lock.rb
|
|
38
|
+
- lib/resque/plugins/queue_lock.rb
|
|
39
|
+
- test/execution_lock_test.rb
|
|
40
|
+
- test/queue_lock_test.rb
|
|
41
|
+
homepage: http://github.com/efficiency20/resque-fine-grained-locks
|
|
42
|
+
licenses: []
|
|
43
|
+
|
|
44
|
+
post_install_message:
|
|
45
|
+
rdoc_options: []
|
|
46
|
+
|
|
47
|
+
require_paths:
|
|
48
|
+
- lib
|
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
|
+
none: false
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
hash: 3
|
|
55
|
+
segments:
|
|
56
|
+
- 0
|
|
57
|
+
version: "0"
|
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
|
+
none: false
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
hash: 3
|
|
64
|
+
segments:
|
|
65
|
+
- 0
|
|
66
|
+
version: "0"
|
|
67
|
+
requirements: []
|
|
68
|
+
|
|
69
|
+
rubyforge_project:
|
|
70
|
+
rubygems_version: 1.8.15
|
|
71
|
+
signing_key:
|
|
72
|
+
specification_version: 3
|
|
73
|
+
summary: Resque plugins for ensuring only one instance of your job is queued and/or executing at a time.
|
|
74
|
+
test_files: []
|
|
75
|
+
|