resque-loner 1.0.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/CHANGELOG.markdown +22 -0
- data/Gemfile +8 -0
- data/LICENSE +19 -0
- data/README.markdown +6 -13
- data/Rakefile +83 -0
- data/init.rb +1 -0
- data/lib/resque-ext/job.rb +3 -2
- data/lib/resque-ext/resque.rb +0 -11
- data/lib/resque-loner/helpers.rb +13 -1
- data/lib/resque-loner/unique_job.rb +16 -0
- data/lib/resque-loner/version.rb +1 -1
- data/rails/init.rb +1 -0
- data/resque-loner.gemspec +50 -0
- data/spec/loner_spec.rb +165 -0
- data/spec/redis-test.conf +115 -0
- data/spec/spec_helper.rb +14 -0
- data/tasks/redis.rake +159 -0
- data/tasks/resque.rake +2 -0
- data/test/hoptoad_test.rb +25 -0
- data/test/job_hooks_test.rb +323 -0
- data/test/job_plugins_test.rb +230 -0
- data/test/plugin_test.rb +116 -0
- data/test/redis-test.conf +115 -0
- data/test/resque-web_test.rb +53 -0
- data/test/resque_test.rb +259 -0
- data/test/test_helper.rb +148 -0
- data/test/worker_test.rb +332 -0
- metadata +113 -83
data/.gitignore
ADDED
data/CHANGELOG.markdown
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
1.2.0
|
2
|
+
--------------------------------
|
3
|
+
Thanks @unclebilly for your pull request. Resque-loner now supports
|
4
|
+
a maximum time for which a job should be unique. Just define @loner_ttl
|
5
|
+
in your job (or leave it at -1 to never expire) and after @loner_ttl
|
6
|
+
seconds your job can be enqueued again, even if an older one is still
|
7
|
+
marked as running.
|
8
|
+
|
9
|
+
1.1.0
|
10
|
+
--------------------------------
|
11
|
+
Merged in @ryansch's pull requests to clean up things a bit.
|
12
|
+
This removed the `enqueue_to` and `dequeue_from` methods from
|
13
|
+
resque-loner because it caused troubles with resque's own
|
14
|
+
`enqueue_to`.
|
15
|
+
|
16
|
+
1.0.1
|
17
|
+
--------------------------------
|
18
|
+
Pulled in #8 and #9 so that removing empty queues
|
19
|
+
does not fail
|
20
|
+
|
21
|
+
1.0
|
22
|
+
---------------------------------
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Moviepilot GmbH http://moviepilot.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.markdown
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
Resque-Loner
|
3
2
|
======
|
4
3
|
|
@@ -38,12 +37,13 @@ Your queue is really full, so the job does not get executed right away. But the
|
|
38
37
|
|
39
38
|
At this point you will have two jobs in the queue, the second of which has no effect: You don't have to run it, once the cache has been updated for the first time. This is where resque-loner's UniqueJobs come in. If you define CacheSweeper like this:
|
40
39
|
|
41
|
-
class CacheSweeper
|
42
|
-
|
40
|
+
class CacheSweeper
|
41
|
+
include Resque::Plugins::UniqueJob
|
42
|
+
@queue = :cache_sweeps
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
def self.perform(article_id)
|
45
|
+
# Cache Me If You Can...
|
46
|
+
end
|
47
47
|
end
|
48
48
|
|
49
49
|
Just like that you've assured that on the :cache_sweeps queue, there can only be one CacheSweeper job for each article. Let's see what happens when you try to enqueue a couple of these jobs now:
|
@@ -68,13 +68,6 @@ Since resque-loner keeps track of which jobs are queued in a way that allows for
|
|
68
68
|
>> Resque.enqueued_in? :another_queue, CacheSweeper, 1
|
69
69
|
=> false
|
70
70
|
|
71
|
-
If you want the same type of job in different queues, resque-loner lets you enqueue/dequeue to a queue of your choice:
|
72
|
-
|
73
|
-
>> Resque.enqueue_to :another_queue, CacheSweeper, 1
|
74
|
-
=> "OK"
|
75
|
-
>> Resqueue.dequeue_from :another_queue, CacheSweeper, 1
|
76
|
-
=> 1
|
77
|
-
|
78
71
|
How it works
|
79
72
|
--------
|
80
73
|
|
data/Rakefile
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
#
|
2
|
+
# Setup
|
3
|
+
#
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift 'lib'
|
6
|
+
|
7
|
+
require "rubygems"
|
8
|
+
require "bundler"
|
9
|
+
Bundler.setup
|
10
|
+
|
11
|
+
require 'rspec/core/rake_task'
|
12
|
+
|
13
|
+
load 'tasks/redis.rake'
|
14
|
+
require 'rake/testtask'
|
15
|
+
|
16
|
+
require 'resque/tasks'
|
17
|
+
|
18
|
+
require 'bundler/gem_tasks'
|
19
|
+
|
20
|
+
def command?(command)
|
21
|
+
system("type #{command} > /dev/null 2>&1")
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
#
|
26
|
+
# Tests
|
27
|
+
#
|
28
|
+
|
29
|
+
task :default => :spec
|
30
|
+
|
31
|
+
desc "Run specs for resque-loner"
|
32
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
33
|
+
t.pattern = "spec/**/*_spec.rb"
|
34
|
+
t.rspec_opts = %w(-fd -c)
|
35
|
+
end
|
36
|
+
|
37
|
+
# desc "Run resque's test suite to make sure we did not break anything"
|
38
|
+
# task :test do
|
39
|
+
# rg = command?(:rg)
|
40
|
+
# Dir['test/**/*_test.rb'].each do |f|
|
41
|
+
# rg ? sh("rg #{f}") : ruby(f)
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
|
45
|
+
if command?(:rg)
|
46
|
+
desc "Run the test suite with rg"
|
47
|
+
task :test do
|
48
|
+
Dir['test/**/*_test.rb'].each do |f|
|
49
|
+
sh("rg #{f}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
else
|
53
|
+
Rake::TestTask.new do |test|
|
54
|
+
test.libs << "test"
|
55
|
+
test.test_files = FileList['test/**/*_test.rb']
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if command? :kicker
|
60
|
+
desc "Launch Kicker (like autotest)"
|
61
|
+
task :kicker do
|
62
|
+
puts "Kicking... (ctrl+c to cancel)"
|
63
|
+
exec "kicker -e rake test lib examples"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
#
|
69
|
+
# Install
|
70
|
+
#
|
71
|
+
|
72
|
+
task :install => [ 'redis:install', 'dtach:install' ]
|
73
|
+
|
74
|
+
|
75
|
+
#
|
76
|
+
# Documentation
|
77
|
+
#
|
78
|
+
|
79
|
+
begin
|
80
|
+
require 'sdoc_helpers'
|
81
|
+
rescue LoadError
|
82
|
+
end
|
83
|
+
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rails/init.rb'
|
data/lib/resque-ext/job.rb
CHANGED
@@ -12,6 +12,7 @@ module Resque
|
|
12
12
|
# after Resque::Job.create has called Resque.push
|
13
13
|
#
|
14
14
|
def self.create_with_loner(queue, klass, *args)
|
15
|
+
return create_without_loner(queue, klass, *args) if Resque.inline?
|
15
16
|
item = { :class => klass.to_s, :args => args }
|
16
17
|
return "EXISTED" if Resque::Plugins::Loner::Helpers.loner_queued?(queue, item)
|
17
18
|
job = create_without_loner(queue, klass, *args)
|
@@ -24,7 +25,7 @@ module Resque
|
|
24
25
|
#
|
25
26
|
def self.reserve_with_loner(queue)
|
26
27
|
item = reserve_without_loner(queue)
|
27
|
-
Resque::Plugins::Loner::Helpers.mark_loner_as_unqueued( queue, item ) if item
|
28
|
+
Resque::Plugins::Loner::Helpers.mark_loner_as_unqueued( queue, item ) if item && !Resque.inline?
|
28
29
|
item
|
29
30
|
end
|
30
31
|
|
@@ -35,7 +36,7 @@ module Resque
|
|
35
36
|
# as the original method Resque::Job.destroy. Couldn't make it any dry'er.
|
36
37
|
#
|
37
38
|
def self.destroy_with_loner(queue, klass, *args)
|
38
|
-
Resque::Plugins::Loner::Helpers.job_destroy(queue, klass, *args)
|
39
|
+
Resque::Plugins::Loner::Helpers.job_destroy(queue, klass, *args) unless Resque.inline?
|
39
40
|
destroy_without_loner(queue, klass, *args)
|
40
41
|
end
|
41
42
|
|
data/lib/resque-ext/resque.rb
CHANGED
@@ -1,16 +1,5 @@
|
|
1
1
|
module Resque
|
2
2
|
|
3
|
-
#
|
4
|
-
# Why force one job type into one queue?
|
5
|
-
#
|
6
|
-
def self.enqueue_to( queue, klass, *args )
|
7
|
-
Job.create(queue, klass, *args)
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.dequeue_from( queue, klass, *args)
|
11
|
-
Job.destroy(queue, klass, *args)
|
12
|
-
end
|
13
|
-
|
14
3
|
def self.enqueued?( klass, *args)
|
15
4
|
enqueued_in?(queue_from_class(klass), klass, *args )
|
16
5
|
end
|
data/lib/resque-loner/helpers.rb
CHANGED
@@ -11,7 +11,11 @@ module Resque
|
|
11
11
|
|
12
12
|
def self.mark_loner_as_queued(queue, item)
|
13
13
|
return unless item_is_a_unique_job?(item)
|
14
|
-
|
14
|
+
key = unique_job_queue_key(queue, item)
|
15
|
+
redis.set(key, 1)
|
16
|
+
unless(ttl=item_ttl(item)) == -1 # no need to incur overhead for default value
|
17
|
+
redis.expire(key, ttl)
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
21
|
def self.mark_loner_as_unqueued(queue, job)
|
@@ -34,6 +38,14 @@ module Resque
|
|
34
38
|
end # so resque-loner should not start throwing up when that happens.
|
35
39
|
end
|
36
40
|
|
41
|
+
def self.item_ttl(item)
|
42
|
+
begin
|
43
|
+
constantize(item[:class] || item["class"]).loner_ttl
|
44
|
+
rescue
|
45
|
+
-1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
37
49
|
def self.job_destroy(queue, klass, *args)
|
38
50
|
klass = klass.to_s
|
39
51
|
redis_queue = "queue:#{queue}"
|
@@ -33,6 +33,22 @@ module Resque
|
|
33
33
|
digest = Digest::MD5.hexdigest encode(:class => job, :args => args)
|
34
34
|
digest
|
35
35
|
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# 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:
|
41
|
+
#
|
42
|
+
# class FooJob
|
43
|
+
# include Resque::Plugins::UniqueJob
|
44
|
+
# @loner_ttl = 40
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
def loner_ttl
|
49
|
+
@loner_ttl || -1
|
50
|
+
end
|
51
|
+
|
36
52
|
end # ClassMethods
|
37
53
|
|
38
54
|
|
data/lib/resque-loner/version.rb
CHANGED
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'resque-loner'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib/', __FILE__)
|
3
|
+
$:.unshift lib unless $:.include?(lib)
|
4
|
+
|
5
|
+
require 'resque-loner/version'
|
6
|
+
|
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
|
16
|
+
|
17
|
+
s.rubyforge_project = 'resque-loner'
|
18
|
+
|
19
|
+
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
|
+
|
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"]
|
34
|
+
|
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
|
+
|
38
|
+
Example:
|
39
|
+
class CacheSweeper
|
40
|
+
|
41
|
+
include Resque::Plugins::UniqueJob
|
42
|
+
|
43
|
+
@queue = :cache_sweeps
|
44
|
+
|
45
|
+
def self.perform(article_id)
|
46
|
+
# Cache Me If You Can...
|
47
|
+
end
|
48
|
+
end
|
49
|
+
desc
|
50
|
+
end
|
data/spec/loner_spec.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
#
|
5
|
+
# Resque-loner specific specs. I'm shooting right through the stack here and just
|
6
|
+
# test the outcomes, because the implementation will change soon and the tests run
|
7
|
+
# quite quickly.
|
8
|
+
#
|
9
|
+
|
10
|
+
class SomeJob
|
11
|
+
@queue = :some_queue
|
12
|
+
end
|
13
|
+
|
14
|
+
class SomeUniqueJob
|
15
|
+
|
16
|
+
include Resque::Plugins::UniqueJob
|
17
|
+
|
18
|
+
@queue = :other_queue
|
19
|
+
def self.perform(foo); end
|
20
|
+
end
|
21
|
+
|
22
|
+
class FailingUniqueJob
|
23
|
+
include Resque::Plugins::UniqueJob
|
24
|
+
@queue = :other_queue
|
25
|
+
def self.perform(foo)
|
26
|
+
raise "I beg to differ"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class DeprecatedUniqueJob < Resque::Plugins::Loner::UniqueJob
|
31
|
+
|
32
|
+
@queue = :other_queue
|
33
|
+
def self.perform(foo); end
|
34
|
+
end
|
35
|
+
|
36
|
+
class UniqueJobWithTtl
|
37
|
+
include Resque::Plugins::UniqueJob
|
38
|
+
@queue = :unique_with_ttl
|
39
|
+
@loner_ttl = 300
|
40
|
+
|
41
|
+
def self.perform(*args); end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "Resque" do
|
45
|
+
|
46
|
+
before(:each) do
|
47
|
+
Resque.redis.flushall
|
48
|
+
Resque.size(:other_queue).should == 0
|
49
|
+
Resque.size(:some_queue).should == 0
|
50
|
+
end
|
51
|
+
|
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"
|
56
|
+
Resque.size(:some_queue).should == 2
|
57
|
+
end
|
58
|
+
|
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"
|
62
|
+
Resque.size(:other_queue).should == 1
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should support deprecated Resque::Plugins::Loner::UniqueJob class" do
|
66
|
+
Resque.enqueue DeprecatedUniqueJob, "foo"
|
67
|
+
Resque.enqueue DeprecatedUniqueJob, "foo"
|
68
|
+
Resque.size(:other_queue).should == 1
|
69
|
+
end
|
70
|
+
|
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"
|
74
|
+
Resque.size(:other_queue).should == 1
|
75
|
+
|
76
|
+
Resque.reserve(:other_queue)
|
77
|
+
Resque.size(:other_queue).should == 0
|
78
|
+
|
79
|
+
Resque.enqueue SomeUniqueJob, "foo"
|
80
|
+
Resque.enqueue SomeUniqueJob, "foo"
|
81
|
+
Resque.size(:other_queue).should == 1
|
82
|
+
end
|
83
|
+
|
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
|
87
|
+
Resque.size(:other_queue).should == 1
|
88
|
+
end
|
89
|
+
|
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
|
93
|
+
Resque.size(:other_queue).should == 1
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should mark jobs as unqueued, when Job.destroy is killing them" do
|
97
|
+
Resque.enqueue SomeUniqueJob, "foo"
|
98
|
+
Resque.enqueue SomeUniqueJob, "foo"
|
99
|
+
Resque.size(:other_queue).should == 1
|
100
|
+
|
101
|
+
Resque::Job.destroy(:other_queue, SomeUniqueJob)
|
102
|
+
Resque.size(:other_queue).should == 0
|
103
|
+
|
104
|
+
Resque.enqueue SomeUniqueJob, "foo"
|
105
|
+
Resque.enqueue SomeUniqueJob, "foo"
|
106
|
+
Resque.size(:other_queue).should == 1
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should mark jobs as unqueued, when they raise an exception during #perform" do
|
110
|
+
2.times { Resque.enqueue( FailingUniqueJob, "foo" ) }
|
111
|
+
Resque.size(:other_queue).should == 1
|
112
|
+
|
113
|
+
worker = Resque::Worker.new(:other_queue)
|
114
|
+
worker.work 0
|
115
|
+
Resque.size(:other_queue).should == 0
|
116
|
+
|
117
|
+
2.times { Resque.enqueue( FailingUniqueJob, "foo" ) }
|
118
|
+
Resque.size(:other_queue).should == 1
|
119
|
+
end
|
120
|
+
|
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
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should report if a job is in a special queue or not" do
|
128
|
+
default_queue = SomeUniqueJob.instance_variable_get(:@queue)
|
129
|
+
SomeUniqueJob.instance_variable_set(:@queue, :special_queue)
|
130
|
+
|
131
|
+
Resque.enqueue SomeUniqueJob, "foo"
|
132
|
+
Resque.enqueued_in?( :special_queue, SomeUniqueJob, "foo").should be_true
|
133
|
+
|
134
|
+
SomeUniqueJob.instance_variable_set(:@queue, default_queue)
|
135
|
+
|
136
|
+
Resque.enqueued?( SomeUniqueJob, "foo").should be_false
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should not be able to report if a non-unique job was enqueued" do
|
140
|
+
Resque.enqueued?(SomeJob).should be_nil
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should cleanup all loners when a queue is destroyed" do
|
144
|
+
Resque.enqueue SomeUniqueJob, "foo"
|
145
|
+
Resque.enqueue FailingUniqueJob, "foo"
|
146
|
+
|
147
|
+
Resque.remove_queue(:other_queue)
|
148
|
+
|
149
|
+
Resque.enqueue(SomeUniqueJob, "foo")
|
150
|
+
Resque.size(:other_queue).should == 1
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should not raise an error when deleting an already empty queue' do
|
154
|
+
expect { Resque.remove_queue(:other_queue) }.to_not raise_error
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should honor loner_ttl in the redis key' do
|
158
|
+
Resque.enqueue UniqueJobWithTtl
|
159
|
+
Resque.enqueued?(UniqueJobWithTtl).should be_true
|
160
|
+
k=Resque.redis.keys "loners:queue:unique_with_ttl:job:*"
|
161
|
+
k.length.should == 1
|
162
|
+
Resque.redis.ttl(k[0]).should be_within(2).of(UniqueJobWithTtl.loner_ttl)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|