resque-loner 1.2.1 → 1.3.0

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.
@@ -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