resque-lonely_job 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: Let's say you want the serial constraint to apply at a more granular
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 queue
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
@@ -1,7 +1,7 @@
1
1
  module Resque
2
2
  module Plugins
3
3
  module LonelyJob
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
6
6
  end
7
7
  end
@@ -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 self at the end of the queue
29
- Resque.enqueue(self, *args)
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
@@ -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 end of the queue if unable to acquire the lock' do
69
- Resque.size(:serial_work).should == 0
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
- job.perform.should be_false
76
+ job1 = Resque.reserve(:serial_work)
77
+ job1.perform.should be_false
78
78
 
79
- Resque.size(:serial_work).should == 1
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 end of the queue if unable to acquire the lock' do
114
- Resque.size(:serial_work).should == 0
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
- job.perform.should be_false
122
+ job1 = Resque.reserve(:serial_work)
123
+ job1.perform.should be_false
123
124
 
124
- Resque.size(:serial_work).should == 1
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.1
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-21 00:00:00.000000000 Z
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: &70323053547940 !ruby/object:Gem::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: *70323053547940
24
+ version_requirements: *70163618247460
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: mock_redis
27
- requirement: &70323053547380 !ruby/object:Gem::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: *70323053547380
35
+ version_requirements: *70163618260080
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &70323053546980 !ruby/object:Gem::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: *70323053546980
46
+ version_requirements: *70163618269840
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &70323053546520 !ruby/object:Gem::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: *70323053546520
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