resque-lonely_job 1.0.2 → 1.1.3
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.
- checksums.yaml +4 -4
- data/README.md +35 -9
- data/lib/resque-lonely_job.rb +7 -0
- data/lib/resque-lonely_job/version.rb +1 -1
- data/resque-lonely_job.gemspec +1 -1
- data/spec/lib/lonely_job_spec.rb +42 -27
- metadata +19 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 385d9088e5dcf459dc861e253a7311171ed99081
|
|
4
|
+
data.tar.gz: c3670738b0a5913dd7b1eb7351f05c8c73028ce2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7967576ae3331cb0fde89d639dd81a93f54cab0d53f8bbd5f7e48815b10ac5363bd8c22171c45d2dbd934710deb0db9d90884a8778c2982798818edc4cfcfa2f
|
|
7
|
+
data.tar.gz: f731d546913937b970112aea7fdde0971e42cbcf7ae9b0c8d4deade8c97c58002930e3d7be1922f5d1d09d7dc1ce054fc2718cbe515f1b58590cc8f4aaf14422
|
data/README.md
CHANGED
|
@@ -2,15 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://travis-ci.org/wallace/resque-lonely\_job)
|
|
4
4
|
|
|
5
|
-
A [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Version 1.x Requires Resque >= 1.20.0 and < 1.25.0.
|
|
9
|
-
|
|
10
|
-
Requires a version of MRI Ruby >= 1.9.3.
|
|
11
|
-
|
|
12
|
-
Ensures that for a given queue, only one worker is working on a job at any given
|
|
13
|
-
time.
|
|
5
|
+
A [semanticaly versioned](http://semver.org/)
|
|
6
|
+
[Resque](https://github.com/resque/resque) plugin which ensures for a given
|
|
7
|
+
queue, that only one worker is working on a job at any given time.
|
|
14
8
|
|
|
15
9
|
Resque::LonelyJob differs from [resque-queue-lock](https://github.com/mashion/resque-queue-lock), [resque-lock](https://github.com/defunkt/resque-lock) and
|
|
16
10
|
[resque-loner](http://github.com/jayniz/resque-loner) in that the same job may
|
|
@@ -26,6 +20,12 @@ ordering but introduces the possibility of starvation.)
|
|
|
26
20
|
Therefore it is recommended that the payload for jobs be stored in a separate
|
|
27
21
|
redis list distinct from the Resque queue (see Example #3).
|
|
28
22
|
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
Version 1.x Requires Resque >= 1.20.0 and < 1.25.0.
|
|
26
|
+
|
|
27
|
+
Requires a version of MRI Ruby >= 1.9.3.
|
|
28
|
+
|
|
29
29
|
## Installation
|
|
30
30
|
|
|
31
31
|
Add this line to your application's Gemfile:
|
|
@@ -153,6 +153,32 @@ It now doesn't matter whether job 1 and job 2 are re-ordered as whichever goes
|
|
|
153
153
|
first will perform an atomic pop on the redis list that contains the data needed
|
|
154
154
|
for its job (data x, data y, data z).
|
|
155
155
|
|
|
156
|
+
#### Example #4 -- Requeue interval
|
|
157
|
+
|
|
158
|
+
The behavior when multiple jobs exist in a queue protected by resque-lonely_job
|
|
159
|
+
is for one job to be worked, while the other is continuously dequeued and
|
|
160
|
+
requeued until the first job is finished. This can result in that worker
|
|
161
|
+
process pegging a CPU/core on a worker server. To guard against this, the
|
|
162
|
+
default behavior is to sleep for 1 second before the requeue, which will allow
|
|
163
|
+
the cpu to perform other work.
|
|
164
|
+
|
|
165
|
+
This can be customized using a ```@requeue_interval``` class instance variable
|
|
166
|
+
in your job like so:
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
require 'resque-lonely_job'
|
|
170
|
+
|
|
171
|
+
class StrictlySerialJob
|
|
172
|
+
extend Resque::Plugins::LonelyJob
|
|
173
|
+
|
|
174
|
+
@queue = :serial_work
|
|
175
|
+
@requeue_interval = 5 # sleep for 5 seconds before requeueing
|
|
176
|
+
|
|
177
|
+
def self.perform
|
|
178
|
+
# some implementation
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
156
182
|
## Contributing
|
|
157
183
|
|
|
158
184
|
1. Fork it
|
data/lib/resque-lonely_job.rb
CHANGED
|
@@ -9,6 +9,10 @@ module Resque
|
|
|
9
9
|
Time.now.to_i + LOCK_TIMEOUT + 1
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def requeue_interval
|
|
13
|
+
self.instance_variable_get(:@requeue_interval) || 1
|
|
14
|
+
end
|
|
15
|
+
|
|
12
16
|
# Overwrite this method to uniquely identify which mutex should be used
|
|
13
17
|
# for a resque worker.
|
|
14
18
|
def redis_key(*args)
|
|
@@ -37,6 +41,9 @@ module Resque
|
|
|
37
41
|
|
|
38
42
|
def before_perform(*args)
|
|
39
43
|
unless can_lock_queue?(*args)
|
|
44
|
+
# Sleep so the CPU's rest
|
|
45
|
+
sleep(requeue_interval)
|
|
46
|
+
|
|
40
47
|
# can't get the lock, so re-enqueue the task
|
|
41
48
|
reenqueue(*args)
|
|
42
49
|
|
data/resque-lonely_job.gemspec
CHANGED
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
|
|
|
18
18
|
gem.add_dependency 'resque', '>= 1.2'
|
|
19
19
|
gem.add_development_dependency 'mock_redis'
|
|
20
20
|
gem.add_development_dependency 'rake'
|
|
21
|
-
gem.add_development_dependency 'rspec'
|
|
21
|
+
gem.add_development_dependency 'rspec', '>= 3.0'
|
|
22
22
|
gem.add_development_dependency 'timecop'
|
|
23
23
|
|
|
24
24
|
gem.description = <<desc
|
data/spec/lib/lonely_job_spec.rb
CHANGED
|
@@ -23,33 +23,44 @@ describe Resque::Plugins::LonelyJob do
|
|
|
23
23
|
Resque.redis.flushall
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
describe ".requeue_interval" do
|
|
27
|
+
it "should default to 5" do
|
|
28
|
+
expect(SerialJob.requeue_interval).to eql(1)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "should be overridable with a class instance var" do
|
|
32
|
+
SerialJob.instance_variable_set(:@requeue_interval, 5)
|
|
33
|
+
expect(SerialJob.requeue_interval).to eql(5)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
26
37
|
describe ".can_lock_queue?" do
|
|
27
38
|
it 'can lock a queue' do
|
|
28
|
-
SerialJob.can_lock_queue?(:serial_work).
|
|
39
|
+
expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
|
|
29
40
|
end
|
|
30
41
|
|
|
31
42
|
it 'cannot lock an already locked queue' do
|
|
32
|
-
SerialJob.can_lock_queue?(:serial_work).
|
|
33
|
-
SerialJob.can_lock_queue?(:serial_work).
|
|
43
|
+
expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
|
|
44
|
+
expect(SerialJob.can_lock_queue?(:serial_work)).to eql(false)
|
|
34
45
|
end
|
|
35
46
|
|
|
36
47
|
it 'cannot lock a queue with active lock' do
|
|
37
|
-
SerialJob.can_lock_queue?(:serial_work).
|
|
48
|
+
expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
|
|
38
49
|
Timecop.travel(Date.today + 1) do
|
|
39
|
-
SerialJob.can_lock_queue?(:serial_work).
|
|
50
|
+
expect(SerialJob.can_lock_queue?(:serial_work)).to eql(false)
|
|
40
51
|
end
|
|
41
52
|
end
|
|
42
53
|
|
|
43
54
|
it 'can relock a queue with expired lock' do
|
|
44
|
-
SerialJob.can_lock_queue?(:serial_work).
|
|
55
|
+
expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
|
|
45
56
|
|
|
46
57
|
Timecop.travel(Date.today + 10) do
|
|
47
|
-
SerialJob.can_lock_queue?(:serial_work).
|
|
58
|
+
expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
|
|
48
59
|
end
|
|
49
60
|
end
|
|
50
61
|
|
|
51
62
|
it 'solves race condition with getset' do
|
|
52
|
-
SerialJob.can_lock_queue?(:serial_work).
|
|
63
|
+
expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
|
|
53
64
|
|
|
54
65
|
Timecop.travel(Date.today + 10) do
|
|
55
66
|
threads = (1..10).to_a.map {
|
|
@@ -60,21 +71,25 @@ describe Resque::Plugins::LonelyJob do
|
|
|
60
71
|
|
|
61
72
|
# Only one worker should acquire lock
|
|
62
73
|
locks = threads.map {|t| t.join; t[:locked] }
|
|
63
|
-
locks.count(true).
|
|
74
|
+
expect(locks.count(true)).to eql(1)
|
|
64
75
|
end
|
|
65
76
|
end
|
|
66
77
|
end
|
|
67
78
|
|
|
68
79
|
describe ".perform" do
|
|
80
|
+
before do
|
|
81
|
+
SerialJob.instance_variable_set(:@requeue_interval, 0)
|
|
82
|
+
end
|
|
83
|
+
|
|
69
84
|
describe "using the default redis key" do
|
|
70
85
|
it 'should lock and unlock the queue' do
|
|
71
86
|
job = Resque::Job.new(:serial_work, { 'class' => 'SerialJob', 'args' => %w[account_one job_one] })
|
|
72
87
|
|
|
73
88
|
# job is the first SerialJob to run so it can lock the queue and perform
|
|
74
|
-
SerialJob.
|
|
89
|
+
expect(SerialJob).to receive(:can_lock_queue?).and_return(true)
|
|
75
90
|
|
|
76
91
|
# but it should also clean up after itself
|
|
77
|
-
SerialJob.
|
|
92
|
+
expect(SerialJob).to receive(:unlock_queue)
|
|
78
93
|
|
|
79
94
|
job.perform
|
|
80
95
|
end
|
|
@@ -83,17 +98,17 @@ describe Resque::Plugins::LonelyJob do
|
|
|
83
98
|
job = Resque::Job.new(:serial_work, { 'class' => 'SerialJob', 'args' => %w[account_one job_one] })
|
|
84
99
|
|
|
85
100
|
# job is the first SerialJob to run so it can lock the queue and perform
|
|
86
|
-
SerialJob.
|
|
101
|
+
expect(SerialJob).to receive(:can_lock_queue?).and_return(true)
|
|
87
102
|
|
|
88
103
|
# but we have a catastrophic job failure
|
|
89
|
-
SerialJob.
|
|
104
|
+
expect(SerialJob).to receive(:perform).and_raise(Exception)
|
|
90
105
|
|
|
91
106
|
# and still it should clean up after itself
|
|
92
|
-
SerialJob.
|
|
107
|
+
expect(SerialJob).to receive(:unlock_queue)
|
|
93
108
|
|
|
94
109
|
# unfortunately, the job will be lost but resque doesn't guarantee jobs
|
|
95
110
|
# aren't lost
|
|
96
|
-
|
|
111
|
+
expect { job.perform }.to raise_error(Exception)
|
|
97
112
|
end
|
|
98
113
|
|
|
99
114
|
it 'should place self at the end of the queue if unable to acquire the lock' do
|
|
@@ -102,15 +117,15 @@ describe Resque::Plugins::LonelyJob do
|
|
|
102
117
|
Resque::Job.create(:serial_work, 'SerialJob', job1_payload)
|
|
103
118
|
Resque::Job.create(:serial_work, 'SerialJob', job2_payload)
|
|
104
119
|
|
|
105
|
-
SerialJob.
|
|
120
|
+
expect(SerialJob).to receive(:can_lock_queue?).and_return(false)
|
|
106
121
|
|
|
107
122
|
# perform returns false when DontPerform exception is raised in
|
|
108
123
|
# before_perform callback
|
|
109
124
|
job1 = Resque.reserve(:serial_work)
|
|
110
|
-
job1.perform.
|
|
125
|
+
expect(job1.perform).to eql(false)
|
|
111
126
|
|
|
112
127
|
first_queue_element = Resque.reserve(:serial_work)
|
|
113
|
-
first_queue_element.payload["args"].
|
|
128
|
+
expect(first_queue_element.payload["args"]).to eql([job2_payload])
|
|
114
129
|
end
|
|
115
130
|
end
|
|
116
131
|
|
|
@@ -119,10 +134,10 @@ describe Resque::Plugins::LonelyJob do
|
|
|
119
134
|
job = Resque::Job.new(:serial_work, { 'class' => 'SerialJobWithCustomRedisKey', 'args' => %w[account_one job_one] })
|
|
120
135
|
|
|
121
136
|
# job is the first SerialJobWithCustomRedisKey to run so it can lock the queue and perform
|
|
122
|
-
SerialJobWithCustomRedisKey.
|
|
137
|
+
expect(SerialJobWithCustomRedisKey).to receive(:can_lock_queue?).and_return(true)
|
|
123
138
|
|
|
124
139
|
# but it should also clean up after itself
|
|
125
|
-
SerialJobWithCustomRedisKey.
|
|
140
|
+
expect(SerialJobWithCustomRedisKey).to receive(:unlock_queue)
|
|
126
141
|
|
|
127
142
|
job.perform
|
|
128
143
|
end
|
|
@@ -131,17 +146,17 @@ describe Resque::Plugins::LonelyJob do
|
|
|
131
146
|
job = Resque::Job.new(:serial_work, { 'class' => 'SerialJobWithCustomRedisKey', 'args' => %w[account_one job_one] })
|
|
132
147
|
|
|
133
148
|
# job is the first SerialJobWithCustomRedisKey to run so it can lock the queue and perform
|
|
134
|
-
SerialJobWithCustomRedisKey.
|
|
149
|
+
expect(SerialJobWithCustomRedisKey).to receive(:can_lock_queue?).and_return(true)
|
|
135
150
|
|
|
136
151
|
# but we have a catastrophic job failure
|
|
137
|
-
SerialJobWithCustomRedisKey.
|
|
152
|
+
expect(SerialJobWithCustomRedisKey).to receive(:perform).and_raise(Exception)
|
|
138
153
|
|
|
139
154
|
# and still it should clean up after itself
|
|
140
|
-
SerialJobWithCustomRedisKey.
|
|
155
|
+
expect(SerialJobWithCustomRedisKey).to receive(:unlock_queue)
|
|
141
156
|
|
|
142
157
|
# unfortunately, the job will be lost but resque doesn't guarantee jobs
|
|
143
158
|
# aren't lost
|
|
144
|
-
|
|
159
|
+
expect { job.perform }.to raise_error(Exception)
|
|
145
160
|
end
|
|
146
161
|
|
|
147
162
|
it 'should place self at the end of the queue if unable to acquire the lock' do
|
|
@@ -150,15 +165,15 @@ describe Resque::Plugins::LonelyJob do
|
|
|
150
165
|
Resque::Job.create(:serial_work, 'SerialJobWithCustomRedisKey', job1_payload)
|
|
151
166
|
Resque::Job.create(:serial_work, 'SerialJobWithCustomRedisKey', job2_payload)
|
|
152
167
|
|
|
153
|
-
SerialJobWithCustomRedisKey.
|
|
168
|
+
expect(SerialJobWithCustomRedisKey).to receive(:can_lock_queue?).and_return(false)
|
|
154
169
|
|
|
155
170
|
# perform returns false when DontPerform exception is raised in
|
|
156
171
|
# before_perform callback
|
|
157
172
|
job1 = Resque.reserve(:serial_work)
|
|
158
|
-
job1.perform.
|
|
173
|
+
expect(job1.perform).to eql(false)
|
|
159
174
|
|
|
160
175
|
first_queue_element = Resque.reserve(:serial_work)
|
|
161
|
-
first_queue_element.payload["args"].
|
|
176
|
+
expect(first_queue_element.payload["args"]).to eql([job2_payload])
|
|
162
177
|
end
|
|
163
178
|
end
|
|
164
179
|
end
|
metadata
CHANGED
|
@@ -1,83 +1,83 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: resque-lonely_job
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jonathan R. Wallace
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2014-
|
|
11
|
+
date: 2014-08-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: resque
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- -
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '1.2'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- -
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '1.2'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: mock_redis
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- -
|
|
31
|
+
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
33
|
version: '0'
|
|
34
34
|
type: :development
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- -
|
|
38
|
+
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: rake
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- -
|
|
45
|
+
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
47
|
version: '0'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- -
|
|
52
|
+
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: rspec
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
|
-
- -
|
|
59
|
+
- - ">="
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
61
|
+
version: '3.0'
|
|
62
62
|
type: :development
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
|
-
- -
|
|
66
|
+
- - ">="
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
68
|
+
version: '3.0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
70
|
name: timecop
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
|
-
- -
|
|
73
|
+
- - ">="
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
75
|
version: '0'
|
|
76
76
|
type: :development
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
|
-
- -
|
|
80
|
+
- - ">="
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0'
|
|
83
83
|
description: |
|
|
@@ -103,8 +103,8 @@ executables: []
|
|
|
103
103
|
extensions: []
|
|
104
104
|
extra_rdoc_files: []
|
|
105
105
|
files:
|
|
106
|
-
- .gitignore
|
|
107
|
-
- .travis.yml
|
|
106
|
+
- ".gitignore"
|
|
107
|
+
- ".travis.yml"
|
|
108
108
|
- Gemfile
|
|
109
109
|
- LICENSE
|
|
110
110
|
- README.md
|
|
@@ -124,17 +124,17 @@ require_paths:
|
|
|
124
124
|
- lib
|
|
125
125
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
126
126
|
requirements:
|
|
127
|
-
- -
|
|
127
|
+
- - ">="
|
|
128
128
|
- !ruby/object:Gem::Version
|
|
129
129
|
version: '0'
|
|
130
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
131
|
requirements:
|
|
132
|
-
- -
|
|
132
|
+
- - ">="
|
|
133
133
|
- !ruby/object:Gem::Version
|
|
134
134
|
version: '0'
|
|
135
135
|
requirements: []
|
|
136
136
|
rubyforge_project:
|
|
137
|
-
rubygems_version: 2.
|
|
137
|
+
rubygems_version: 2.2.2
|
|
138
138
|
signing_key:
|
|
139
139
|
specification_version: 4
|
|
140
140
|
summary: A resque plugin that ensures that only one job for a given queue will be
|