resque-loner 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,91 @@
1
+ # This is an old helpers module pulled in from Resque 1.25.1
2
+ # which will be deprecated in future Resque versions. Bringing in
3
+ # this module allows us to get rid of the deprecation message
4
+ # (Resque::Helpers will be gone with no replacement in Resque 2.0.0.)
5
+ require 'multi_json'
6
+
7
+ # OkJson won't work because it doesn't serialize symbols
8
+ # in the same way yajl and json do.
9
+ if MultiJson.engine.to_s == 'MultiJson::Engines::OkJson'
10
+ fail 'Please install the yajl-ruby or json gem'
11
+ end
12
+
13
+ module Resque
14
+ module Plugins
15
+ module Loner
16
+ # Methods used by various classes in Resque.
17
+ module LegacyHelpers
18
+ class DecodeException < StandardError; end
19
+
20
+ # Direct access to the Redis instance.
21
+ def redis
22
+ Resque.redis
23
+ end
24
+
25
+ # Given a Ruby object, returns a string suitable for storage in a
26
+ # queue.
27
+ def encode(object)
28
+ ::MultiJson.encode(object)
29
+ end
30
+
31
+ # Given a string, returns a Ruby object.
32
+ def decode(object)
33
+ return unless object
34
+
35
+ begin
36
+ ::MultiJson.decode(object)
37
+ rescue ::MultiJson::DecodeError => e
38
+ raise DecodeException, e.message, e.backtrace
39
+ end
40
+ end
41
+
42
+ # Given a word with dashes, returns a camel cased version of it.
43
+ #
44
+ # classify('job-name') # => 'JobName'
45
+ def classify(dashed_word)
46
+ dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
47
+ end
48
+
49
+ # Tries to find a constant with the name specified in the argument string:
50
+ #
51
+ # constantize("Module") # => Module
52
+ # constantize("Test::Unit") # => Test::Unit
53
+ #
54
+ # The name is assumed to be the one of a top-level constant, no matter
55
+ # whether it starts with "::" or not. No lexical context is taken into
56
+ # account:
57
+ #
58
+ # C = 'outside'
59
+ # module M
60
+ # C = 'inside'
61
+ # C # => 'inside'
62
+ # constantize("C") # => 'outside', same as ::C
63
+ # end
64
+ #
65
+ # NameError is raised when the constant is unknown.
66
+ def constantize(camel_cased_word)
67
+ camel_cased_word = camel_cased_word.to_s
68
+
69
+ if camel_cased_word.include?('-')
70
+ camel_cased_word = classify(camel_cased_word)
71
+ end
72
+
73
+ names = camel_cased_word.split('::')
74
+ names.shift if names.empty? || names.first.empty?
75
+
76
+ constant = Object
77
+ names.each do |name|
78
+ args = Module.method(:const_get).arity != 1 ? [false] : []
79
+
80
+ if constant.const_defined?(name, *args)
81
+ constant = constant.const_get(name)
82
+ else
83
+ constant = constant.const_missing(name)
84
+ end
85
+ end
86
+ constant
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,4 +1,5 @@
1
1
  require 'digest/md5'
2
+ require 'resque-loner/helpers'
2
3
 
3
4
  #
4
5
  # If you want your job to be unique, include this module in it. If you wish,
@@ -7,37 +8,34 @@ require 'digest/md5'
7
8
  module Resque
8
9
  module Plugins
9
10
  module UniqueJob
10
-
11
11
  def self.included(base)
12
- base.extend ClassMethods
12
+ base.extend ClassMethods
13
13
  base.class_eval do
14
- base.send(:extend, Resque::Helpers)
14
+ base.send(:extend, Resque::Plugins::Loner::LegacyHelpers)
15
15
  end
16
16
  end # self.included
17
17
 
18
18
  module ClassMethods
19
-
20
-
21
19
  #
22
20
  # Payload is what Resque stored for this job along with the job's class name.
23
21
  # On a Resque with no plugins installed, this is a hash containing :class and :args
24
22
  #
25
23
  def redis_key(payload)
26
24
  payload = decode(encode(payload)) # This is the cycle the data goes when being enqueued/dequeued
27
- job = payload[:class] || payload["class"]
28
- args = (payload[:args] || payload["args"])
25
+ job = payload[:class] || payload['class']
26
+ args = (payload[:args] || payload['args'])
29
27
  args.map! do |arg|
30
28
  arg.is_a?(Hash) ? arg.sort : arg
31
29
  end
32
30
 
33
- digest = Digest::MD5.hexdigest encode(:class => job, :args => args)
31
+ digest = Digest::MD5.hexdigest(encode(class: job, args: args))
34
32
  digest
35
33
  end
36
34
 
37
35
  #
38
36
  # The default ttl of a locking key is -1, i.e. forever. If for some reason you only
39
- # want the lock to be in place after a certain amount of time, just set a ttl for
40
- # for your job. For example:
37
+ # want the lock to be in place after a certain amount of time, just set a ttl (in
38
+ # seconds) for your job. For example:
41
39
  #
42
40
  # class FooJob
43
41
  # include Resque::Plugins::UniqueJob
@@ -49,9 +47,21 @@ module Resque
49
47
  @loner_ttl || -1
50
48
  end
51
49
 
50
+ #
51
+ # The default ttl of a persisting key is 0, i.e. immediately deleted.
52
+ # You can set loner_lock_after_execution_period if you want to block the execution
53
+ # of the job for a certain amount of time (in seconds). For example:
54
+ #
55
+ # class FooJob
56
+ # include Resque::Plugins::UniqueJob
57
+ # @loner_lock_after_execution_period = 40
58
+ # end
59
+ # end
60
+ #
61
+ def loner_lock_after_execution_period
62
+ @loner_lock_after_execution_period || 0
63
+ end
52
64
  end # ClassMethods
53
-
54
-
55
65
  end
56
66
  end
57
67
  end
@@ -60,13 +70,12 @@ module Resque
60
70
  module Plugins
61
71
  module Loner
62
72
  class UniqueJob
63
-
64
73
  include Resque::Plugins::UniqueJob
65
74
 
66
75
  def self.inherited(host)
67
76
  super(host)
68
77
  return if @__unique_job_warned
69
- warn "Inherit Resque::Plugins::Loner::UniqueJob is deprecated. Include Resque::Plugins::UniqueJob module instead."
78
+ warn 'Inherit Resque::Plugins::Loner::UniqueJob is deprecated. Include Resque::Plugins::UniqueJob module instead.'
70
79
  @__unique_job_warned = true
71
80
  end
72
81
  end
@@ -1,7 +1,7 @@
1
1
  module Resque
2
2
  module Plugins
3
3
  module Loner
4
- VERSION = "1.2.1"
4
+ VERSION = '1.3.0'
5
5
  end
6
6
  end
7
7
  end
@@ -5,46 +5,55 @@ $:.unshift lib unless $:.include?(lib)
5
5
  require 'resque-loner/version'
6
6
 
7
7
  Gem::Specification.new do |s|
8
- s.name = 'resque-loner'
9
- s.version = Resque::Plugins::Loner::VERSION
10
- s.platform = Gem::Platform::RUBY
11
- s.authors = ['Jannis Hermanns']
12
- s.email = ['jannis@moviepilot.com']
13
- s.homepage = 'http://github.com/jayniz/resque-loner'
14
- s.summary = 'Adds unique jobs to resque'
15
- s.has_rdoc = false
8
+ s.name = 'resque-loner'
9
+ s.version = Resque::Plugins::Loner::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ['Jannis Hermanns']
12
+ s.email = ['jannis@moviepilot.com']
13
+ s.homepage = 'http://github.com/jayniz/resque-loner'
14
+ s.summary = 'Adds unique jobs to resque'
15
+ s.has_rdoc = false
16
+ s.license = 'MIT'
16
17
 
17
18
  s.rubyforge_project = 'resque-loner'
18
19
 
19
20
  s.add_dependency 'resque', '~>1.0'
20
- {
21
- 'rake' => '> 0.8.7',
22
- 'rack-test' => '~> 0.5.7',
23
- 'rspec' => '~> 2.5.0',
24
- 'mock_redis' => '~> 0.2.0',
25
- 'yajl-ruby' => '~> 0.8.2'
26
- }.each do |lib, version|
27
- s.add_development_dependency lib, version
28
- end
29
21
 
30
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
31
- s.files = `git ls-files`.split("\n")
32
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
33
- s.require_paths = ["lib"]
22
+ %w(
23
+ airbrake
24
+ i18n
25
+ mocha
26
+ mock_redis
27
+ rack-test
28
+ rake
29
+ rspec
30
+ rubocop
31
+ simplecov
32
+ yajl-ruby
33
+ ).each do |gemname|
34
+ s.add_development_dependency gemname
35
+ end
34
36
 
35
- s.description = <<desc
36
- Makes sure that for special jobs, there can be only one job with the same workload in one queue.
37
+ s.executables = `git ls-files -z -- bin/*`.split("\0").map do
38
+ |f| File.basename(f)
39
+ end
40
+ s.files = `git ls-files -z`.split("\0")
41
+ s.test_files = `git ls-files -z -- {test,spec,features}/*`.split("\0")
42
+ s.require_paths = ['lib']
37
43
 
38
- Example:
39
- class CacheSweeper
44
+ s.description = <<-EODESC.gsub(/^ {4}/, '')
45
+ Makes sure that for special jobs, there can be only one job with the same
46
+ workload in one queue.
40
47
 
41
- include Resque::Plugins::UniqueJob
48
+ Example:
49
+ class CacheSweeper
50
+ include Resque::Plugins::UniqueJob
42
51
 
43
- @queue = :cache_sweeps
52
+ @queue = :cache_sweeps
44
53
 
45
- def self.perform(article_id)
46
- # Cache Me If You Can...
47
- end
48
- end
49
- desc
54
+ def self.perform(article_id)
55
+ # Cache Me If You Can...
56
+ end
57
+ end
58
+ EODESC
50
59
  end
@@ -1,6 +1,5 @@
1
1
  require 'spec_helper'
2
2
 
3
-
4
3
  #
5
4
  # Resque-loner specific specs. I'm shooting right through the stack here and just
6
5
  # test the outcomes, because the implementation will change soon and the tests run
@@ -11,10 +10,8 @@ class SomeJob
11
10
  @queue = :some_queue
12
11
  end
13
12
 
14
- class SomeUniqueJob
15
-
13
+ class SomeUniqueJob
16
14
  include Resque::Plugins::UniqueJob
17
-
18
15
  @queue = :other_queue
19
16
  def self.perform(foo); end
20
17
  end
@@ -23,12 +20,11 @@ class FailingUniqueJob
23
20
  include Resque::Plugins::UniqueJob
24
21
  @queue = :other_queue
25
22
  def self.perform(foo)
26
- raise "I beg to differ"
23
+ fail 'I beg to differ'
27
24
  end
28
25
  end
29
26
 
30
27
  class DeprecatedUniqueJob < Resque::Plugins::Loner::UniqueJob
31
-
32
28
  @queue = :other_queue
33
29
  def self.perform(foo); end
34
30
  end
@@ -37,11 +33,17 @@ class UniqueJobWithTtl
37
33
  include Resque::Plugins::UniqueJob
38
34
  @queue = :unique_with_ttl
39
35
  @loner_ttl = 300
36
+ def self.perform(*args); end
37
+ end
40
38
 
39
+ class UniqueJobWithLockAfterExecution
40
+ include Resque::Plugins::UniqueJob
41
+ @queue = :unique_with_loner_lock_after_execution_period
42
+ @loner_lock_after_execution_period = 150
41
43
  def self.perform(*args); end
42
44
  end
43
45
 
44
- describe "Resque" do
46
+ describe 'Resque' do
45
47
 
46
48
  before(:each) do
47
49
  Resque.redis.flushall
@@ -49,107 +51,107 @@ describe "Resque" do
49
51
  Resque.size(:some_queue).should == 0
50
52
  end
51
53
 
52
- describe "Jobs" do
53
- it "can put multiple normal jobs on a queue" do
54
- Resque.enqueue SomeJob, "foo"
55
- Resque.enqueue SomeJob, "foo"
54
+ describe 'Jobs' do
55
+ it 'can put multiple normal jobs on a queue' do
56
+ Resque.enqueue SomeJob, 'foo'
57
+ Resque.enqueue SomeJob, 'foo'
56
58
  Resque.size(:some_queue).should == 2
57
59
  end
58
60
 
59
- it "should allow only one of the same job to sit in a queue" do
60
- Resque.enqueue SomeUniqueJob, "foo"
61
- Resque.enqueue SomeUniqueJob, "foo"
61
+ it 'should allow only one of the same job to sit in a queue' do
62
+ Resque.enqueue SomeUniqueJob, 'foo'
63
+ Resque.enqueue SomeUniqueJob, 'foo'
62
64
  Resque.size(:other_queue).should == 1
63
65
  end
64
66
 
65
- it "should support deprecated Resque::Plugins::Loner::UniqueJob class" do
66
- Resque.enqueue DeprecatedUniqueJob, "foo"
67
- Resque.enqueue DeprecatedUniqueJob, "foo"
67
+ it 'should support deprecated Resque::Plugins::Loner::UniqueJob class' do
68
+ Resque.enqueue DeprecatedUniqueJob, 'foo'
69
+ Resque.enqueue DeprecatedUniqueJob, 'foo'
68
70
  Resque.size(:other_queue).should == 1
69
71
  end
70
72
 
71
- it "should allow the same jobs to be executed one after the other" do
72
- Resque.enqueue SomeUniqueJob, "foo"
73
- Resque.enqueue SomeUniqueJob, "foo"
73
+ it 'should allow the same jobs to be executed one after the other' do
74
+ Resque.enqueue SomeUniqueJob, 'foo'
75
+ Resque.enqueue SomeUniqueJob, 'foo'
74
76
  Resque.size(:other_queue).should == 1
75
77
 
76
78
  Resque.reserve(:other_queue)
77
79
  Resque.size(:other_queue).should == 0
78
80
 
79
- Resque.enqueue SomeUniqueJob, "foo"
80
- Resque.enqueue SomeUniqueJob, "foo"
81
+ Resque.enqueue SomeUniqueJob, 'foo'
82
+ Resque.enqueue SomeUniqueJob, 'foo'
81
83
  Resque.size(:other_queue).should == 1
82
84
  end
83
85
 
84
- it "should be robust regarding hash attributes" do
85
- Resque.enqueue SomeUniqueJob, :bar => 1, :foo => 2
86
- Resque.enqueue SomeUniqueJob, :foo => 2, :bar => 1
86
+ it 'should be robust regarding hash attributes' do
87
+ Resque.enqueue SomeUniqueJob, bar: 1, foo: 2
88
+ Resque.enqueue SomeUniqueJob, foo: 2, bar: 1
87
89
  Resque.size(:other_queue).should == 1
88
90
  end
89
91
 
90
- it "should be robust regarding hash attributes (JSON does not distinguish between string and symbol)" do
91
- Resque.enqueue SomeUniqueJob, :bar => 1, :foo => 1
92
- Resque.enqueue SomeUniqueJob, :bar => 1, "foo" => 1
92
+ it 'should be robust regarding hash attributes (JSON does not distinguish between string and symbol)' do
93
+ Resque.enqueue SomeUniqueJob, bar: 1, foo: 1
94
+ Resque.enqueue SomeUniqueJob, :bar => 1, 'foo' => 1
93
95
  Resque.size(:other_queue).should == 1
94
96
  end
95
97
 
96
- it "should mark jobs as unqueued, when Job.destroy is killing them" do
97
- Resque.enqueue SomeUniqueJob, "foo"
98
- Resque.enqueue SomeUniqueJob, "foo"
98
+ it 'should mark jobs as unqueued, when Job.destroy is killing them' do
99
+ Resque.enqueue SomeUniqueJob, 'foo'
100
+ Resque.enqueue SomeUniqueJob, 'foo'
99
101
  Resque.size(:other_queue).should == 1
100
102
 
101
103
  Resque::Job.destroy(:other_queue, SomeUniqueJob)
102
104
  Resque.size(:other_queue).should == 0
103
105
 
104
- Resque.enqueue SomeUniqueJob, "foo"
105
- Resque.enqueue SomeUniqueJob, "foo"
106
+ Resque.enqueue SomeUniqueJob, 'foo'
107
+ Resque.enqueue SomeUniqueJob, 'foo'
106
108
  Resque.size(:other_queue).should == 1
107
109
  end
108
110
 
109
- it "should mark jobs as unqueued, when they raise an exception during #perform" do
110
- 2.times { Resque.enqueue( FailingUniqueJob, "foo" ) }
111
+ it 'should mark jobs as unqueued, when they raise an exception during #perform' do
112
+ 2.times { Resque.enqueue(FailingUniqueJob, 'foo') }
111
113
  Resque.size(:other_queue).should == 1
112
114
 
113
115
  worker = Resque::Worker.new(:other_queue)
114
116
  worker.work 0
115
117
  Resque.size(:other_queue).should == 0
116
118
 
117
- 2.times { Resque.enqueue( FailingUniqueJob, "foo" ) }
119
+ 2.times { Resque.enqueue(FailingUniqueJob, 'foo') }
118
120
  Resque.size(:other_queue).should == 1
119
121
  end
120
122
 
121
- it "should report if a job is queued or not" do
122
- Resque.enqueue SomeUniqueJob, "foo"
123
- Resque.enqueued?(SomeUniqueJob, "foo").should be_true
124
- Resque.enqueued?(SomeUniqueJob, "bar").should be_false
123
+ it 'should report if a job is queued or not' do
124
+ Resque.enqueue SomeUniqueJob, 'foo'
125
+ Resque.enqueued?(SomeUniqueJob, 'foo').should be_true
126
+ Resque.enqueued?(SomeUniqueJob, 'bar').should be_false
125
127
  end
126
128
 
127
- it "should report if a job is in a special queue or not" do
129
+ it 'should report if a job is in a special queue or not' do
128
130
  default_queue = SomeUniqueJob.instance_variable_get(:@queue)
129
131
  SomeUniqueJob.instance_variable_set(:@queue, :special_queue)
130
132
 
131
- Resque.enqueue SomeUniqueJob, "foo"
132
- Resque.enqueued_in?( :special_queue, SomeUniqueJob, "foo").should be_true
133
+ Resque.enqueue SomeUniqueJob, 'foo'
134
+ Resque.enqueued_in?(:special_queue, SomeUniqueJob, 'foo').should be_true
133
135
 
134
136
  SomeUniqueJob.instance_variable_set(:@queue, default_queue)
135
137
 
136
- Resque.enqueued?( SomeUniqueJob, "foo").should be_false
138
+ Resque.enqueued?(SomeUniqueJob, 'foo').should be_false
137
139
  end
138
140
 
139
- it "should not be able to report if a non-unique job was enqueued" do
141
+ it 'should not be able to report if a non-unique job was enqueued' do
140
142
  Resque.enqueued?(SomeJob).should be_nil
141
143
  end
142
144
 
143
- it "should cleanup all loners when a queue is destroyed" do
144
- Resque.enqueue SomeUniqueJob, "foo"
145
- Resque.enqueue FailingUniqueJob, "foo"
145
+ it 'should cleanup all loners when a queue is destroyed' do
146
+ Resque.enqueue SomeUniqueJob, 'foo'
147
+ Resque.enqueue FailingUniqueJob, 'foo'
146
148
 
147
149
  Resque.remove_queue(:other_queue)
148
150
 
149
- Resque.enqueue(SomeUniqueJob, "foo")
151
+ Resque.enqueue(SomeUniqueJob, 'foo')
150
152
  Resque.size(:other_queue).should == 1
151
153
  end
152
-
154
+
153
155
  it 'should not raise an error when deleting an already empty queue' do
154
156
  expect { Resque.remove_queue(:other_queue) }.to_not raise_error
155
157
  end
@@ -157,9 +159,29 @@ describe "Resque" do
157
159
  it 'should honor loner_ttl in the redis key' do
158
160
  Resque.enqueue UniqueJobWithTtl
159
161
  Resque.enqueued?(UniqueJobWithTtl).should be_true
160
- k=Resque.redis.keys "loners:queue:unique_with_ttl:job:*"
162
+ k = Resque.redis.keys 'loners:queue:unique_with_ttl:job:*'
161
163
  k.length.should == 1
162
164
  Resque.redis.ttl(k[0]).should be_within(2).of(UniqueJobWithTtl.loner_ttl)
163
165
  end
166
+
167
+ it 'should not allow the same job to be enqueued after execution if loner_lock_after_execution_period is set' do
168
+ Resque.enqueue UniqueJobWithLockAfterExecution, 'foo'
169
+ Resque.enqueue UniqueJobWithLockAfterExecution, 'foo'
170
+ Resque.size(:unique_with_loner_lock_after_execution_period).should == 1
171
+
172
+ Resque.reserve(:unique_with_loner_lock_after_execution_period)
173
+ Resque.size(:unique_with_loner_lock_after_execution_period).should == 0
174
+
175
+ Resque.enqueue UniqueJobWithLockAfterExecution, 'foo'
176
+ Resque.size(:unique_with_loner_lock_after_execution_period).should == 0
177
+ end
178
+
179
+ it 'should honor loner_lock_after_execution_period in the redis key' do
180
+ Resque.enqueue UniqueJobWithLockAfterExecution
181
+ Resque.reserve(:unique_with_loner_lock_after_execution_period)
182
+ k = Resque.redis.keys 'loners:queue:unique_with_loner_lock_after_execution_period:job:*'
183
+ k.length.should == 1
184
+ Resque.redis.ttl(k[0]).should be_within(2).of(UniqueJobWithLockAfterExecution.loner_lock_after_execution_period)
185
+ end
164
186
  end
165
187
  end