resque-lonely_job 1.0.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 83362de6a98d68ae3f20fd8e7cd0cb11fff06be7
4
- data.tar.gz: 829b8cc700dbfda2772cd3c15f3df8ab1fd7deed
3
+ metadata.gz: 385d9088e5dcf459dc861e253a7311171ed99081
4
+ data.tar.gz: c3670738b0a5913dd7b1eb7351f05c8c73028ce2
5
5
  SHA512:
6
- metadata.gz: b9bfed33191b4010235ca4eec4b141a33b5cdfd81162823b46b044de4bfe13a9b469e85b1904f836d95ddd5b6ee65831297e18ede0281af896d5122402f1d52c
7
- data.tar.gz: 9d38553d777a2a8fe93b2f421f2cd21dfcd56dd0ed8c8a04cd1357d983aa2c85a7468347f955db00f761a5cbda9c1241302631b61a5eb8768083c724e7ac2a5d
6
+ metadata.gz: 7967576ae3331cb0fde89d639dd81a93f54cab0d53f8bbd5f7e48815b10ac5363bd8c22171c45d2dbd934710deb0db9d90884a8778c2982798818edc4cfcfa2f
7
+ data.tar.gz: f731d546913937b970112aea7fdde0971e42cbcf7ae9b0c8d4deade8c97c58002930e3d7be1922f5d1d09d7dc1ce054fc2718cbe515f1b58590cc8f4aaf14422
data/README.md CHANGED
@@ -2,15 +2,9 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/wallace/resque-lonely_job.png)](https://travis-ci.org/wallace/resque-lonely\_job)
4
4
 
5
- A [Resque](https://github.com/resque/resque) plugin that implements [Semantic
6
- Versioning](http://semver.org/).
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
@@ -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
 
@@ -1,7 +1,7 @@
1
1
  module Resque
2
2
  module Plugins
3
3
  module LonelyJob
4
- VERSION = "1.0.2"
4
+ VERSION = "1.1.3"
5
5
  end
6
6
  end
7
7
  end
@@ -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
@@ -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).should be_true
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).should be_true
33
- SerialJob.can_lock_queue?(:serial_work).should be_false
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).should be_true
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).should be_false
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).should be_true
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).should be_true
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).should be_true
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).should == 1
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.should_receive(:can_lock_queue?).and_return(true)
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.should_receive(:unlock_queue)
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.should_receive(:can_lock_queue?).and_return(true)
101
+ expect(SerialJob).to receive(:can_lock_queue?).and_return(true)
87
102
 
88
103
  # but we have a catastrophic job failure
89
- SerialJob.should_receive(:perform).and_raise(Exception)
104
+ expect(SerialJob).to receive(:perform).and_raise(Exception)
90
105
 
91
106
  # and still it should clean up after itself
92
- SerialJob.should_receive(:unlock_queue)
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
- -> { job.perform }.should raise_error(Exception)
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.should_receive(:can_lock_queue?).and_return(false)
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.should be_false
125
+ expect(job1.perform).to eql(false)
111
126
 
112
127
  first_queue_element = Resque.reserve(:serial_work)
113
- first_queue_element.payload["args"].should == [job2_payload]
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.should_receive(:can_lock_queue?).and_return(true)
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.should_receive(:unlock_queue)
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.should_receive(:can_lock_queue?).and_return(true)
149
+ expect(SerialJobWithCustomRedisKey).to receive(:can_lock_queue?).and_return(true)
135
150
 
136
151
  # but we have a catastrophic job failure
137
- SerialJobWithCustomRedisKey.should_receive(:perform).and_raise(Exception)
152
+ expect(SerialJobWithCustomRedisKey).to receive(:perform).and_raise(Exception)
138
153
 
139
154
  # and still it should clean up after itself
140
- SerialJobWithCustomRedisKey.should_receive(:unlock_queue)
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
- -> { job.perform }.should raise_error(Exception)
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.should_receive(:can_lock_queue?).and_return(false)
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.should be_false
173
+ expect(job1.perform).to eql(false)
159
174
 
160
175
  first_queue_element = Resque.reserve(:serial_work)
161
- first_queue_element.payload["args"].should == [job2_payload]
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.0.2
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-02-10 00:00:00.000000000 Z
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.0.14
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