resque-lonely_job 0.0.1 → 0.0.2
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/README.md +40 -6
- data/lib/resque-lonely_job/version.rb +1 -1
- data/lib/resque-lonely_job.rb +10 -2
- data/spec/lib/lonely_job_spec.rb +14 -12
- metadata +10 -10
data/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# Resque::LonelyJob
|
|
2
2
|
|
|
3
|
-
A [Resque](https://github.com/defunkt/resque) plugin. Requires Resque 1.20.0.
|
|
3
|
+
A [Resque](https://github.com/defunkt/resque) plugin. Requires Resque >= 1.20.0.
|
|
4
4
|
|
|
5
5
|
Ensures that for a given queue, only one worker is working on a job at any given
|
|
6
|
-
time.
|
|
6
|
+
time.
|
|
7
7
|
|
|
8
8
|
This differs from [resque-lock](from https://github.com/defunkt/resque-lock) and
|
|
9
9
|
[resque-loner](http://github.com/jayniz/resque-loner) in that the same job may
|
|
10
10
|
be queued multiple times but you're guaranteed that first job queued will run to
|
|
11
|
-
completion before subsequent jobs are run.
|
|
11
|
+
completion before subsequent jobs are run. In other words, job ordering is
|
|
12
|
+
preserved.
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
@@ -26,7 +27,7 @@ Or install it yourself as:
|
|
|
26
27
|
|
|
27
28
|
## Usage
|
|
28
29
|
|
|
29
|
-
Example #1
|
|
30
|
+
#### Example #1
|
|
30
31
|
|
|
31
32
|
require 'resque/plugins/lonely_job'
|
|
32
33
|
|
|
@@ -41,7 +42,9 @@ Example #1:
|
|
|
41
42
|
end
|
|
42
43
|
end
|
|
43
44
|
|
|
44
|
-
Example #2
|
|
45
|
+
#### Example #2
|
|
46
|
+
|
|
47
|
+
Let's say you want the serial constraint to apply at a more granular
|
|
45
48
|
level. Instead of applying at the queue level, you can overwrite the .redis\_key
|
|
46
49
|
method.
|
|
47
50
|
|
|
@@ -61,9 +64,40 @@ method.
|
|
|
61
64
|
|
|
62
65
|
def self.perform(account_id, *args)
|
|
63
66
|
# only one at a time in this block, no parallelism allowed for this
|
|
64
|
-
# particular
|
|
67
|
+
# particular redis_key
|
|
65
68
|
end
|
|
66
69
|
end
|
|
70
|
+
|
|
71
|
+
*NOTE*: Without careful consideration of your problem domain, worker starvation
|
|
72
|
+
and/or unfairness is possible for jobs in this example. Imagine a scenario
|
|
73
|
+
where you have three jobs in the queue with two resque workers:
|
|
74
|
+
|
|
75
|
+
+---------------------------------------------------+
|
|
76
|
+
| :serial_work |
|
|
77
|
+
|---------------------------------------------------|
|
|
78
|
+
| | | | |
|
|
79
|
+
| redis_key: | redis_key: | redis_key: | ... |
|
|
80
|
+
| A | A | B | |
|
|
81
|
+
| | | | |
|
|
82
|
+
| job 1 | job 2 | job 3 | |
|
|
83
|
+
+---------------------------------------------------+
|
|
84
|
+
^
|
|
85
|
+
|
|
|
86
|
+
Possible starvation +-----------+
|
|
87
|
+
for this job and
|
|
88
|
+
subsequent ones
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
When the first worker grabs job 1, it'll acquire the mutex for processing
|
|
92
|
+
redis\_key A. The second worker tries to grab the next job off the queue but
|
|
93
|
+
is unable to acquire the mutex for redis\_key A so it places job 2 back at the
|
|
94
|
+
head of the :serial\_work queue. Until worker 1 completes job 1 and releases
|
|
95
|
+
the mutex for redis\_key A, no work will be done in this queue.
|
|
96
|
+
|
|
97
|
+
This issue may be avoided by employing dynamic queues,
|
|
98
|
+
http://blog.kabisa.nl/2010/03/16/dynamic-queue-assignment-for-resque-jobs/,
|
|
99
|
+
where the queue is a one to one mapping to the redis\_key.
|
|
100
|
+
|
|
67
101
|
## Contributing
|
|
68
102
|
|
|
69
103
|
1. Fork it
|
data/lib/resque-lonely_job.rb
CHANGED
|
@@ -23,10 +23,18 @@ module Resque
|
|
|
23
23
|
Resque.redis.del(redis_key(*args))
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# Unfortunately, there's not a Resque interface for lpush so we have to
|
|
27
|
+
# role our own. This is based on Resque.push but we don't need to
|
|
28
|
+
# call Resque.watch_queue as the queue should already exist if we're
|
|
29
|
+
# unable to get the lock.
|
|
30
|
+
def lpush(*args)
|
|
31
|
+
Resque.redis.lpush("queue:#{Resque.queue_from_class(self)}", Resque.encode(class: self, args: args))
|
|
32
|
+
end
|
|
33
|
+
|
|
26
34
|
def before_perform(*args)
|
|
27
35
|
unless can_lock_queue?(*args)
|
|
28
|
-
# can't get the lock, so place
|
|
29
|
-
|
|
36
|
+
# can't get the lock, so place at the front of the queue
|
|
37
|
+
lpush(*args)
|
|
30
38
|
|
|
31
39
|
# and don't perform
|
|
32
40
|
raise Resque::Job::DontPerform
|
data/spec/lib/lonely_job_spec.rb
CHANGED
|
@@ -65,18 +65,19 @@ describe Resque::Plugins::LonelyJob do
|
|
|
65
65
|
-> { job.perform }.should raise_error(Exception)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
it 'should place self at the
|
|
69
|
-
Resque.
|
|
70
|
-
|
|
71
|
-
job = Resque::Job.new(:serial_work, { 'class' => 'SerialJob', 'args' => %w[account_one job_one] })
|
|
68
|
+
it 'should place self at the beginning of the queue if unable to acquire the lock' do
|
|
69
|
+
job1 = Resque::Job.create(:serial_work, 'SerialJob', %w[account_one job_one])
|
|
70
|
+
job2 = Resque::Job.create(:serial_work, 'SerialJob', %w[account_one job_two])
|
|
72
71
|
|
|
73
72
|
SerialJob.should_receive(:can_lock_queue?).and_return(false)
|
|
74
73
|
|
|
75
74
|
# perform returns false when DontPerform exception is raised in
|
|
76
75
|
# before_perform callback
|
|
77
|
-
|
|
76
|
+
job1 = Resque.reserve(:serial_work)
|
|
77
|
+
job1.perform.should be_false
|
|
78
78
|
|
|
79
|
-
Resque.
|
|
79
|
+
first_queue_element = Resque.reserve(:serial_work)
|
|
80
|
+
first_queue_element.should == job1
|
|
80
81
|
end
|
|
81
82
|
end
|
|
82
83
|
|
|
@@ -110,18 +111,19 @@ describe Resque::Plugins::LonelyJob do
|
|
|
110
111
|
-> { job.perform }.should raise_error(Exception)
|
|
111
112
|
end
|
|
112
113
|
|
|
113
|
-
it 'should place self at the
|
|
114
|
-
Resque.
|
|
115
|
-
|
|
116
|
-
job = Resque::Job.new(:serial_work, { 'class' => 'SerialJobWithCustomRedisKey', 'args' => %w[account_one job_one] })
|
|
114
|
+
it 'should place self at the beginning of the queue if unable to acquire the lock' do
|
|
115
|
+
job1 = Resque::Job.create(:serial_work, 'SerialJobWithCustomRedisKey', %w[account_one job_one])
|
|
116
|
+
job2 = Resque::Job.create(:serial_work, 'SerialJobWithCustomRedisKey', %w[account_one job_two])
|
|
117
117
|
|
|
118
118
|
SerialJobWithCustomRedisKey.should_receive(:can_lock_queue?).and_return(false)
|
|
119
119
|
|
|
120
120
|
# perform returns false when DontPerform exception is raised in
|
|
121
121
|
# before_perform callback
|
|
122
|
-
|
|
122
|
+
job1 = Resque.reserve(:serial_work)
|
|
123
|
+
job1.perform.should be_false
|
|
123
124
|
|
|
124
|
-
Resque.
|
|
125
|
+
first_queue_element = Resque.reserve(:serial_work)
|
|
126
|
+
first_queue_element.should == job1
|
|
125
127
|
end
|
|
126
128
|
end
|
|
127
129
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: resque-lonely_job
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.2
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,11 +9,11 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2012-05-
|
|
12
|
+
date: 2012-05-24 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: resque
|
|
16
|
-
requirement: &
|
|
16
|
+
requirement: &70163618247460 !ruby/object:Gem::Requirement
|
|
17
17
|
none: false
|
|
18
18
|
requirements:
|
|
19
19
|
- - ~>
|
|
@@ -21,10 +21,10 @@ dependencies:
|
|
|
21
21
|
version: 1.20.0
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
|
-
version_requirements: *
|
|
24
|
+
version_requirements: *70163618247460
|
|
25
25
|
- !ruby/object:Gem::Dependency
|
|
26
26
|
name: mock_redis
|
|
27
|
-
requirement: &
|
|
27
|
+
requirement: &70163618260080 !ruby/object:Gem::Requirement
|
|
28
28
|
none: false
|
|
29
29
|
requirements:
|
|
30
30
|
- - ~>
|
|
@@ -32,10 +32,10 @@ dependencies:
|
|
|
32
32
|
version: 0.4.1
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
|
-
version_requirements: *
|
|
35
|
+
version_requirements: *70163618260080
|
|
36
36
|
- !ruby/object:Gem::Dependency
|
|
37
37
|
name: rake
|
|
38
|
-
requirement: &
|
|
38
|
+
requirement: &70163618269840 !ruby/object:Gem::Requirement
|
|
39
39
|
none: false
|
|
40
40
|
requirements:
|
|
41
41
|
- - ! '>='
|
|
@@ -43,10 +43,10 @@ dependencies:
|
|
|
43
43
|
version: '0'
|
|
44
44
|
type: :development
|
|
45
45
|
prerelease: false
|
|
46
|
-
version_requirements: *
|
|
46
|
+
version_requirements: *70163618269840
|
|
47
47
|
- !ruby/object:Gem::Dependency
|
|
48
48
|
name: rspec
|
|
49
|
-
requirement: &
|
|
49
|
+
requirement: &70163618268580 !ruby/object:Gem::Requirement
|
|
50
50
|
none: false
|
|
51
51
|
requirements:
|
|
52
52
|
- - ! '>='
|
|
@@ -54,7 +54,7 @@ dependencies:
|
|
|
54
54
|
version: '0'
|
|
55
55
|
type: :development
|
|
56
56
|
prerelease: false
|
|
57
|
-
version_requirements: *
|
|
57
|
+
version_requirements: *70163618268580
|
|
58
58
|
description: ! "Ensures that for a given queue, only one worker is working on a job
|
|
59
59
|
at any given time.\n\nExample:\n\n require 'resque/plugins/lonely_job'\n\n class
|
|
60
60
|
StrictlySerialJob\n extend Resque::Plugins::LonelyJob\n\n @queue = :serial_work\n\n
|