resque-delayed 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +112 -0
- data/Rakefile +5 -0
- data/lib/resque-delayed.rb +6 -0
- data/lib/resque-delayed/resque-delayed.rb +69 -0
- data/lib/resque-delayed/resque.rb +11 -0
- data/lib/resque-delayed/tasks.rb +22 -0
- data/lib/resque-delayed/version.rb +5 -0
- data/lib/resque-delayed/worker.rb +120 -0
- data/resque-delayed.gemspec +24 -0
- data/spec/redis-test.conf +8 -0
- data/spec/resque_delayed_spec.rb +89 -0
- data/spec/resque_spec.rb +23 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/support/extensions.rb +21 -0
- data/spec/worker_spec.rb +45 -0
- metadata +158 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) Justin Giancola
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# Resque::Delayed
|
2
|
+
|
3
|
+
Delayed job queueing for Resque.
|
4
|
+
|
5
|
+
Enqueue jobs that will only appear for processing after a specified delay or at a particular time in the future.
|
6
|
+
|
7
|
+
## About
|
8
|
+
|
9
|
+
Useful for jobs that would be awkward to run in crons. For example:
|
10
|
+
|
11
|
+
* expiring stale orders to free up reserved inventory
|
12
|
+
* retrying failed webhook deliveries with progressively increasing delays
|
13
|
+
|
14
|
+
Also useful for jobs that are typically run in crons. For example:
|
15
|
+
|
16
|
+
* sending call-to-action reminder emails a few days after each signup
|
17
|
+
* checking whether pending payments have cleared
|
18
|
+
|
19
|
+
Fine-grained job scheduling avoids the need for monolithic crons that are often slow, resource intensive and single-process. Instead of needing to stagger crons to avoid overlap or parallelize crons that are too slow, jobs can be spread throughout the entire day and amongst multiple worker processes.
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Resque::Delayed is very simple. Call `Resque.enqueue_in` or `Resque.enqueue_at` instead of `Resque.enqueue`
|
24
|
+
For example:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
class User
|
28
|
+
after_create :send_call_to_action_email
|
29
|
+
|
30
|
+
private
|
31
|
+
def send_call_to_action_email
|
32
|
+
Resque.enqueue_in 3.days, CallToActionEmailJob, self.id
|
33
|
+
end
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
**or**
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class RecurringInvoice
|
41
|
+
def generate_invoice
|
42
|
+
# snip...
|
43
|
+
|
44
|
+
Resque.enqueue_at self.next_billing_date, RecurringInvoiceJob, self.id
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
**or**
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class Webhook
|
53
|
+
MAX_RETRIES = 15
|
54
|
+
|
55
|
+
def deliver
|
56
|
+
unless Webhook.post(payload).success?
|
57
|
+
return if retries == Webhook::MAX_RETRIES
|
58
|
+
|
59
|
+
update_attribute :retries, retries + 1
|
60
|
+
|
61
|
+
Resque.enqueue_in (2**retries).minutes, WebhookDeliveryJob, self.id
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
## Setup
|
68
|
+
|
69
|
+
`$ gem install resque-delayed`
|
70
|
+
|
71
|
+
or add
|
72
|
+
|
73
|
+
`gem 'resque-delayed'`
|
74
|
+
|
75
|
+
to your Gemfile and run
|
76
|
+
|
77
|
+
`$ bundle install`
|
78
|
+
|
79
|
+
Resque::Delayed piggybacks on your existing Resque setup so it will use whatever Redis instance Resque has been configured to use.
|
80
|
+
|
81
|
+
The above will provide `Resque.enqueue_in` and `Resque.enqueue_at` to your application but you will also need to run a Resque::Dealyed worker process. The worker is responsible for harvesting future-queued jobs and pushing them onto the appropriate Resque queues at the right time.
|
82
|
+
|
83
|
+
`Resque::Delayed::Worker` is a stripped-down version of `Resque::Worker` so you can use the same configuration options like `INTERVAL`, `PIDFILE`, `LOGGING`, `VERBOSE` and `VVERBOSE`
|
84
|
+
|
85
|
+
Like Resque, Resque::Delayed provides a rake task to run workers. Add `require 'resque-delayed/tasks'` to your `Rakefile` and run
|
86
|
+
|
87
|
+
$ cd app_root
|
88
|
+
$ LOGGING=1 INTERVAL=10 rake resque_delayed:work
|
89
|
+
|
90
|
+
**NOTE: Resque::Delayed workers only take future-queued jobs and push them onto Resque queues when they need to be run. They do *not* actually process jobs so any setup using Resque::Delayed also needs one or more regular Resque workers.**
|
91
|
+
|
92
|
+
## Deployment Considerations
|
93
|
+
|
94
|
+
Resque::Delayed workers are very lean as they do not need to load either your application or your Resque job classes. Even so you will probably want to monitor them in production using something like monit, god, or bluepill. Also, because they are not actually performing any of the job processing work it is unlikely you will need to run more than one.<sup>1</sup>
|
95
|
+
|
96
|
+
<sup>1</sup> a single Resque::Delayed worker on a laptop with unexciting hardware can push a few thousand jobs per second into Resque while new delayed jobs are simultaneously being added.
|
97
|
+
|
98
|
+
## Contributing
|
99
|
+
|
100
|
+
1. [fork](http://help.github.com/fork-a-repo/) this repo
|
101
|
+
1. create a topic branch (`$ git checkout -b my_branch`)
|
102
|
+
1. make your changes along with specs
|
103
|
+
1. push to your branch (`$ git push origin my_branch`)
|
104
|
+
1. send me a [pull request](http://help.github.com/send-pull-requests/)
|
105
|
+
|
106
|
+
## Thanks
|
107
|
+
|
108
|
+
Thanks to [defunkt](https://github.com/defunkt) and all Resque contributors. Resque is a pleasure to use and adapts well to new challenges. Also some code in this project, `Resque::Delayed::Worker` in particular, borrows heavily from the Resque implementation.
|
109
|
+
|
110
|
+
## Copyright
|
111
|
+
|
112
|
+
Copyright (c) Justin Giancola. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module Resque
|
2
|
+
module Delayed
|
3
|
+
class << self
|
4
|
+
@queue = "Resque::Delayed:internal"
|
5
|
+
|
6
|
+
def random_uuid
|
7
|
+
UUIDTools::UUID.random_create.to_s.gsub('-', '')
|
8
|
+
end
|
9
|
+
|
10
|
+
def clear
|
11
|
+
Resque.redis.del @queue
|
12
|
+
end
|
13
|
+
|
14
|
+
def count
|
15
|
+
Resque.redis.zcard @queue
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_at(time, *args)
|
19
|
+
klass, *args = args
|
20
|
+
queue = Resque.queue_from_class klass
|
21
|
+
# validate here so that the Resque::Delayed worker doesn't have
|
22
|
+
# to worry about it before enqueueing
|
23
|
+
Resque.validate(klass, queue)
|
24
|
+
Resque.redis.zadd @queue, time.to_i, encode(queue, klass, *args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_in(offset, *args)
|
28
|
+
create_at Time.now + offset, *args
|
29
|
+
end
|
30
|
+
|
31
|
+
def encode(queue, klass, *args)
|
32
|
+
"#{random_uuid}|#{Resque.encode([queue, klass.to_s, *args])}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def decode(encoded_job)
|
36
|
+
Resque.decode encoded_job.split('|', 2).last
|
37
|
+
end
|
38
|
+
|
39
|
+
def next
|
40
|
+
next_at Time.now
|
41
|
+
end
|
42
|
+
|
43
|
+
def next_at(time)
|
44
|
+
job = peek_at_serialized(time)
|
45
|
+
|
46
|
+
# it is possible that another process will pull this job out of the
|
47
|
+
# queue before this process has a chance. if that happens, return nil
|
48
|
+
# so that we don't end up duplicating the job.
|
49
|
+
return unless job and Resque.redis.zrem(@queue, job)
|
50
|
+
|
51
|
+
Resque::Delayed.decode job
|
52
|
+
end
|
53
|
+
|
54
|
+
def peek
|
55
|
+
peek_at Time.now
|
56
|
+
end
|
57
|
+
|
58
|
+
def peek_at(time)
|
59
|
+
job = peek_at_serialized(time)
|
60
|
+
job and Resque::Delayed.decode(job)
|
61
|
+
end
|
62
|
+
|
63
|
+
def peek_at_serialized(time)
|
64
|
+
Resque.redis.
|
65
|
+
zrangebyscore(@queue, '-inf', time.to_i, :limit => [0, 1]).first
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# require 'resque-delayed/tasks'
|
2
|
+
namespace :resque_delayed do
|
3
|
+
|
4
|
+
desc "Start a Resque::Delayed worker"
|
5
|
+
task :work do
|
6
|
+
require 'resque-delayed'
|
7
|
+
|
8
|
+
begin
|
9
|
+
worker = Resque::Delayed::Worker.new
|
10
|
+
worker.verbose = ENV['LOGGING'] || ENV['VERBOSE']
|
11
|
+
worker.very_verbose = ENV['VVERBOSE']
|
12
|
+
end
|
13
|
+
|
14
|
+
if ENV['PIDFILE']
|
15
|
+
File.open(ENV['PIDFILE'], 'w') { |f| f << worker.pid }
|
16
|
+
end
|
17
|
+
|
18
|
+
worker.log "Starting Resque::Delayed worker #{worker}"
|
19
|
+
|
20
|
+
worker.work(ENV['INTERVAL'] || 5) # interval, will block
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Resque::Delayed
|
2
|
+
# a worker that harvests delayed jobs and queues them
|
3
|
+
#
|
4
|
+
# note: this is a modified version of
|
5
|
+
# https://github.com/defunkt/resque/blob/e01bece0ccfd561909333d51b28813d59777183d/lib/resque/worker.rb
|
6
|
+
# with nearly everything stripped out of it
|
7
|
+
class Worker
|
8
|
+
# Whether the worker should log basic info to STDOUT
|
9
|
+
attr_accessor :verbose
|
10
|
+
|
11
|
+
# Whether the worker should log lots of info to STDOUT
|
12
|
+
attr_accessor :very_verbose
|
13
|
+
|
14
|
+
attr_writer :to_s
|
15
|
+
|
16
|
+
# Can be passed a float representing the polling frequency.
|
17
|
+
# The default is 5 seconds, but for a semi-active site you may
|
18
|
+
# want to use a smaller value.
|
19
|
+
def work(interval = 5.0)
|
20
|
+
interval = Float(interval)
|
21
|
+
$0 = "resque-delayed: harvesting"
|
22
|
+
startup
|
23
|
+
|
24
|
+
loop do
|
25
|
+
break if shutdown?
|
26
|
+
|
27
|
+
# harvest delayed jobs while they are available
|
28
|
+
while job = Resque::Delayed.next do
|
29
|
+
log "got: #{job.inspect}"
|
30
|
+
queue, klass, *args = job
|
31
|
+
Resque::Job.create(queue, klass, *args)
|
32
|
+
end
|
33
|
+
|
34
|
+
break if interval.zero?
|
35
|
+
log! "Sleeping for #{interval} seconds"
|
36
|
+
sleep interval
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Runs all the methods needed when a worker begins its lifecycle.
|
41
|
+
def startup
|
42
|
+
register_signal_handlers
|
43
|
+
|
44
|
+
# Fix buffering so we can `rake resque-delayed:work > resque-delayed.log` and
|
45
|
+
# get output from the worker
|
46
|
+
$stdout.sync = true
|
47
|
+
end
|
48
|
+
|
49
|
+
# Registers the various signal handlers a worker responds to.
|
50
|
+
#
|
51
|
+
# TERM: Shutdown immediately, stop processing jobs.
|
52
|
+
# INT: Shutdown immediately, stop processing jobs.
|
53
|
+
# QUIT: Shutdown after the current job has finished processing.
|
54
|
+
def register_signal_handlers
|
55
|
+
trap('TERM') { shutdown! }
|
56
|
+
trap('INT') { shutdown! }
|
57
|
+
|
58
|
+
begin
|
59
|
+
trap('QUIT') { shutdown }
|
60
|
+
rescue ArgumentError
|
61
|
+
warn "Signals TERM and/or QUIT not supported."
|
62
|
+
end
|
63
|
+
|
64
|
+
log! "Registered signals"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Schedule this worker for shutdown. Will finish processing the
|
68
|
+
# current job.
|
69
|
+
def shutdown
|
70
|
+
log 'Exiting...'
|
71
|
+
@shutdown = true
|
72
|
+
end
|
73
|
+
|
74
|
+
# Kill the child and shutdown immediately.
|
75
|
+
def shutdown!
|
76
|
+
shutdown
|
77
|
+
end
|
78
|
+
|
79
|
+
# Should this worker shutdown as soon as current job is finished?
|
80
|
+
def shutdown?
|
81
|
+
@shutdown
|
82
|
+
end
|
83
|
+
|
84
|
+
def inspect
|
85
|
+
"#<Resque::Delayed worker #{to_s}>"
|
86
|
+
end
|
87
|
+
|
88
|
+
# The string representation is the same as the id for this worker
|
89
|
+
# instance. Can be used with `Worker.find`.
|
90
|
+
def to_s
|
91
|
+
@to_s ||= "#{hostname}:#{Process.pid}:resque-delayed"
|
92
|
+
end
|
93
|
+
alias_method :id, :to_s
|
94
|
+
|
95
|
+
# chomp'd hostname of this machine
|
96
|
+
def hostname
|
97
|
+
@hostname ||= `hostname`.chomp
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns Integer PID of running worker
|
101
|
+
def pid
|
102
|
+
@pid ||= Process.pid
|
103
|
+
end
|
104
|
+
|
105
|
+
# Log a message to STDOUT if we are verbose or very_verbose.
|
106
|
+
def log(message)
|
107
|
+
if verbose
|
108
|
+
puts "*** #{message}"
|
109
|
+
elsif very_verbose
|
110
|
+
time = Time.now.strftime('%H:%M:%S %Y-%m-%d')
|
111
|
+
puts "** [#{time}] #$$: #{message}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Logs a very verbose message to STDOUT.
|
116
|
+
def log!(message)
|
117
|
+
log message if very_verbose
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path('../lib/resque-delayed/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.authors = ["Justin Giancola"]
|
5
|
+
gem.email = ["justin.giancola@gmail.com"]
|
6
|
+
gem.summary = %q{Delayed job queueing for Resque}
|
7
|
+
gem.description = %q{Enqueue jobs that will only appear for processing after a specified delay or at a particular time in the future}
|
8
|
+
gem.homepage = "https://github.com/elucid/resque-delayed"
|
9
|
+
|
10
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
11
|
+
gem.files = `git ls-files`.split("\n")
|
12
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
13
|
+
gem.name = "resque-delayed"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = Resque::Delayed::VERSION
|
16
|
+
|
17
|
+
gem.add_development_dependency "bundler", [">= 1.0.2", "< 1.2.0"]
|
18
|
+
gem.add_development_dependency "rspec", "~> 2.6.0"
|
19
|
+
gem.add_development_dependency "rake", [">= 0.8.7", "< 1.0"]
|
20
|
+
|
21
|
+
gem.add_dependency "redis", "~> 2.2.0"
|
22
|
+
gem.add_dependency "resque", [">= 1.18.0", "< 1.20.0"]
|
23
|
+
gem.add_dependency "uuidtools", "~> 2.1.2"
|
24
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Resque::Delayed do
|
4
|
+
describe 'simple create' do
|
5
|
+
before :each do
|
6
|
+
Resque::Delayed.clear
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should start with zero delayed jobs queued" do
|
10
|
+
Resque::Delayed.count.should be_zero
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should allow queueing at a specified time" do
|
14
|
+
Resque::Delayed.create_at(Time.now + 1.day, SomeJob, 'foo', 'bar', 1234)
|
15
|
+
Resque::Delayed.count.should == 1
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should allow queuing after a specified delay" do
|
19
|
+
Resque::Delayed.create_in(1.day, SomeJob, 'foo', 'bar', 1234)
|
20
|
+
Resque::Delayed.count.should == 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe :availability do
|
25
|
+
before :each do
|
26
|
+
Resque::Delayed.clear
|
27
|
+
@one_day_from_now = Time.now + 1.day
|
28
|
+
Resque::Delayed.create_at(@one_day_from_now, SomeJob, 'foo', 'bar', 1234)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not make items available before create time" do
|
32
|
+
Resque::Delayed.next.should be_nil
|
33
|
+
Resque::Delayed.next_at(Time.now).should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should make items available at create time" do
|
37
|
+
Resque::Delayed.next_at(@one_day_from_now).should_not be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should make items available after create time" do
|
41
|
+
Resque::Delayed.next_at(@one_day_from_now + 1.minute).should_not be_nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe :dequeueing do
|
46
|
+
before do
|
47
|
+
Resque::Delayed.clear
|
48
|
+
@one_hour_ago = Time.now - 1.hour
|
49
|
+
Resque::Delayed.create_at(@one_hour_ago, SomeJob, 'foo', 'bar', 1234)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should deserialize properly" do
|
53
|
+
job = Resque::Delayed.next
|
54
|
+
job.should == ["jobs", "SomeJob", "foo", "bar", 1234]
|
55
|
+
Resque::Delayed.count.should be_zero
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe :updates do
|
60
|
+
before :each do
|
61
|
+
Resque::Delayed.clear
|
62
|
+
@one_hour_ago = Time.now - 1.hour
|
63
|
+
Resque::Delayed.create_at(@one_hour_ago, SomeJob, 'first')
|
64
|
+
Resque::Delayed.create_at(@one_hour_ago + 2.minutes, SomeJob, 'third')
|
65
|
+
Resque::Delayed.create_at(@one_hour_ago + 1.minute, SomeJob, 'second')
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should be setup properly" do
|
69
|
+
Resque::Delayed.peek.should == ["jobs", "SomeJob", "first"]
|
70
|
+
Resque::Delayed.count.should == 3
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should dequeue in the correct order regardless of instert order" do
|
74
|
+
%w(first second third).each do |arg|
|
75
|
+
Resque::Delayed.next.last.should == arg
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should allow queueing multiple instances of the same job" do
|
80
|
+
Resque::Delayed.create_at(@one_hour_ago + 3.minutes, SomeJob, 'first')
|
81
|
+
Resque::Delayed.count.should == 4
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should not alter position of existing jobs on second instance queue" do
|
85
|
+
Resque::Delayed.create_at(@one_hour_ago + 3.minutes, SomeJob, 'first')
|
86
|
+
Resque::Delayed.peek.should == ["jobs", "SomeJob", "first"]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/spec/resque_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Resque do
|
4
|
+
describe 'simple create' do
|
5
|
+
before :each do
|
6
|
+
Resque::Delayed.clear
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should start with zero delayed jobs queued" do
|
10
|
+
Resque::Delayed.count.should be_zero
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should allow queueing at a specified time" do
|
14
|
+
Resque.enqueue_at(Time.now + 1.day, SomeJob, 'foo', 'bar', 1234)
|
15
|
+
Resque::Delayed.count.should == 1
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should allow queuing after a specified delay" do
|
19
|
+
Resque.enqueue_in(1.day, SomeJob, 'foo', 'bar', 1234)
|
20
|
+
Resque::Delayed.count.should == 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# based on https://github.com/defunkt/resque/blob/e01bece0ccfd561909333d51b28813d59777183d/test/test_helper.rb
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
Bundler.setup
|
6
|
+
Bundler.require
|
7
|
+
|
8
|
+
# this is necessary because of
|
9
|
+
# https://github.com/carlhuda/bundler/issues/1096
|
10
|
+
require 'resque'
|
11
|
+
|
12
|
+
# make sure we can run redis
|
13
|
+
if !system("which redis-server")
|
14
|
+
puts '', "** can't find `redis-server` in your path"
|
15
|
+
abort ''
|
16
|
+
end
|
17
|
+
|
18
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
19
|
+
|
20
|
+
# start our own redis when the tests start,
|
21
|
+
# kill it when they end
|
22
|
+
at_exit do
|
23
|
+
pid = File.read("#{dir}/redis-test.pid").chomp
|
24
|
+
puts "Killing test redis server..."
|
25
|
+
Process.kill("KILL", pid.to_i)
|
26
|
+
File.unlink "#{dir}/redis-test.pid"
|
27
|
+
end
|
28
|
+
|
29
|
+
puts "Starting redis for testing at localhost:9736..."
|
30
|
+
`redis-server #{dir}/redis-test.conf`
|
31
|
+
Resque.redis = 'localhost:9736'
|
32
|
+
|
33
|
+
require "#{dir}/support/extensions.rb"
|
34
|
+
|
35
|
+
class SomeJob
|
36
|
+
@queue = :jobs
|
37
|
+
end
|
38
|
+
|
39
|
+
class SomeOtherJob
|
40
|
+
@queue = :other_jobs
|
41
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Fixnum
|
2
|
+
def seconds
|
3
|
+
to_i
|
4
|
+
end
|
5
|
+
alias :second :seconds
|
6
|
+
|
7
|
+
def minutes
|
8
|
+
seconds * 60
|
9
|
+
end
|
10
|
+
alias :minute :minutes
|
11
|
+
|
12
|
+
def hours
|
13
|
+
minutes * 60
|
14
|
+
end
|
15
|
+
alias :hour :hours
|
16
|
+
|
17
|
+
def days
|
18
|
+
hours * 24
|
19
|
+
end
|
20
|
+
alias :day :days
|
21
|
+
end
|
data/spec/worker_spec.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Resque::Delayed::Worker do
|
4
|
+
describe "harvest timing" do
|
5
|
+
before :each do
|
6
|
+
Resque::Delayed.clear
|
7
|
+
@worker = Resque::Delayed::Worker.new
|
8
|
+
end
|
9
|
+
|
10
|
+
after :each do
|
11
|
+
@worker.shutdown!
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should not harvest future jobs" do
|
15
|
+
Resque.enqueue_in(1.hour, SomeJob, 'future')
|
16
|
+
@worker.work(0)
|
17
|
+
Resque::Delayed.count.should == 1
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should harvest past jobs" do
|
21
|
+
Resque.enqueue_at(Time.now - 1.hour, SomeJob, 'past')
|
22
|
+
@worker.work(0)
|
23
|
+
Resque::Delayed.count.should == 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "Resque interface" do
|
28
|
+
before do
|
29
|
+
Resque::Delayed.clear
|
30
|
+
@worker = Resque::Delayed::Worker.new
|
31
|
+
Resque.enqueue_at(Time.now - 1.hour, SomeJob, 'past')
|
32
|
+
Resque.enqueue_at(Time.now - 1.hour, SomeOtherJob, 'past')
|
33
|
+
end
|
34
|
+
|
35
|
+
after do
|
36
|
+
@worker.shutdown!
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should queue delayed jobs in appropriate Resque queues after harvest" do
|
40
|
+
Resque::Job.should_receive(:create).with('jobs', 'SomeJob', 'past')
|
41
|
+
Resque::Job.should_receive(:create).with('other_jobs', 'SomeOtherJob', 'past')
|
42
|
+
@worker.work(0)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: resque-delayed
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Justin Giancola
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-09-07 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: bundler
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.2
|
23
|
+
- - <
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 1.2.0
|
26
|
+
type: :development
|
27
|
+
prerelease: false
|
28
|
+
version_requirements: *id001
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: rspec
|
31
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
32
|
+
none: false
|
33
|
+
requirements:
|
34
|
+
- - ~>
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 2.6.0
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: *id002
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: rake
|
42
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.8.7
|
48
|
+
- - <
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "1.0"
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: *id003
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: redis
|
56
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.2.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: *id004
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: resque
|
67
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 1.18.0
|
73
|
+
- - <
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.20.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: *id005
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: uuidtools
|
81
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ~>
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 2.1.2
|
87
|
+
type: :runtime
|
88
|
+
prerelease: false
|
89
|
+
version_requirements: *id006
|
90
|
+
description: Enqueue jobs that will only appear for processing after a specified delay or at a particular time in the future
|
91
|
+
email:
|
92
|
+
- justin.giancola@gmail.com
|
93
|
+
executables: []
|
94
|
+
|
95
|
+
extensions: []
|
96
|
+
|
97
|
+
extra_rdoc_files: []
|
98
|
+
|
99
|
+
files:
|
100
|
+
- .gitignore
|
101
|
+
- CHANGELOG.md
|
102
|
+
- Gemfile
|
103
|
+
- LICENSE
|
104
|
+
- README.md
|
105
|
+
- Rakefile
|
106
|
+
- lib/resque-delayed.rb
|
107
|
+
- lib/resque-delayed/resque-delayed.rb
|
108
|
+
- lib/resque-delayed/resque.rb
|
109
|
+
- lib/resque-delayed/tasks.rb
|
110
|
+
- lib/resque-delayed/version.rb
|
111
|
+
- lib/resque-delayed/worker.rb
|
112
|
+
- resque-delayed.gemspec
|
113
|
+
- spec/redis-test.conf
|
114
|
+
- spec/resque_delayed_spec.rb
|
115
|
+
- spec/resque_spec.rb
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
- spec/support/extensions.rb
|
118
|
+
- spec/worker_spec.rb
|
119
|
+
homepage: https://github.com/elucid/resque-delayed
|
120
|
+
licenses: []
|
121
|
+
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
hash: -4057691847375928239
|
133
|
+
segments:
|
134
|
+
- 0
|
135
|
+
version: "0"
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
hash: -4057691847375928239
|
142
|
+
segments:
|
143
|
+
- 0
|
144
|
+
version: "0"
|
145
|
+
requirements: []
|
146
|
+
|
147
|
+
rubyforge_project:
|
148
|
+
rubygems_version: 1.8.8
|
149
|
+
signing_key:
|
150
|
+
specification_version: 3
|
151
|
+
summary: Delayed job queueing for Resque
|
152
|
+
test_files:
|
153
|
+
- spec/redis-test.conf
|
154
|
+
- spec/resque_delayed_spec.rb
|
155
|
+
- spec/resque_spec.rb
|
156
|
+
- spec/spec_helper.rb
|
157
|
+
- spec/support/extensions.rb
|
158
|
+
- spec/worker_spec.rb
|