powerhome-resque-status 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +85 -0
- data/LICENSE +20 -0
- data/README.rdoc +185 -0
- data/Rakefile +48 -0
- data/examples/sleep_job.rb +36 -0
- data/init.rb +1 -0
- data/lib/resque-status.rb +1 -0
- data/lib/resque/job_with_status.rb +5 -0
- data/lib/resque/plugins/status.rb +254 -0
- data/lib/resque/plugins/status/hash.rb +283 -0
- data/lib/resque/server/views/status.erb +91 -0
- data/lib/resque/server/views/status_styles.erb +104 -0
- data/lib/resque/server/views/statuses.erb +79 -0
- data/lib/resque/status.rb +8 -0
- data/lib/resque/status_server.rb +88 -0
- data/powerhome-resque-status.gemspec +62 -0
- data/resque-status.gemspec +64 -0
- data/test/redis-test.conf +125 -0
- data/test/test_helper.rb +98 -0
- data/test/test_resque_plugins_status.rb +358 -0
- data/test/test_resque_plugins_status_hash.rb +222 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0d9c0c0ab1c7036c3336d46117db310f73503b76d19ee9117dd4944668abc1a0
|
4
|
+
data.tar.gz: e9df86dd8599410cec3c1ce09e2afdc8785b36011fa921c9f2b11052a2b2a38d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 022f4fedf4fd1ef8ceeb413a0ded6c9e118ae67ca78f0a3c3f044c79a0e950d274a4d5db4178022eeda6b14b04beab7c6f710bb0355502e871b8fefbcf2d70db
|
7
|
+
data.tar.gz: ba037b5c574172c20bd394955ed6a9c822c0cb717d8da027b9b1d0a1330dae9dfd459f380484a9254a956f07bc85b317332575660133b5886e63585a06f7f19c
|
data/.document
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.4.0)
|
5
|
+
builder (3.2.4)
|
6
|
+
descendants_tracker (0.0.4)
|
7
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
8
|
+
faraday (0.9.2)
|
9
|
+
multipart-post (>= 1.2, < 3)
|
10
|
+
git (1.7.0)
|
11
|
+
rchardet (~> 1.8)
|
12
|
+
github_api (0.16.0)
|
13
|
+
addressable (~> 2.4.0)
|
14
|
+
descendants_tracker (~> 0.0.4)
|
15
|
+
faraday (~> 0.8, < 0.10)
|
16
|
+
hashie (>= 3.4)
|
17
|
+
mime-types (>= 1.16, < 3.0)
|
18
|
+
oauth2 (~> 1.0)
|
19
|
+
hashie (4.1.0)
|
20
|
+
highline (2.0.3)
|
21
|
+
jeweler (2.3.9)
|
22
|
+
builder
|
23
|
+
bundler
|
24
|
+
git (>= 1.2.5)
|
25
|
+
github_api (~> 0.16.0)
|
26
|
+
highline (>= 1.6.15)
|
27
|
+
nokogiri (>= 1.5.10)
|
28
|
+
psych
|
29
|
+
rake
|
30
|
+
rdoc
|
31
|
+
semver2
|
32
|
+
jwt (2.2.2)
|
33
|
+
metaclass (0.0.2)
|
34
|
+
mime-types (2.99.3)
|
35
|
+
mini_portile2 (2.4.0)
|
36
|
+
minitest (5.5.1)
|
37
|
+
mocha (0.14.0)
|
38
|
+
metaclass (~> 0.0.1)
|
39
|
+
multi_json (1.15.0)
|
40
|
+
multi_xml (0.6.0)
|
41
|
+
multipart-post (2.1.1)
|
42
|
+
nokogiri (1.10.10)
|
43
|
+
mini_portile2 (~> 2.4.0)
|
44
|
+
oauth2 (1.4.4)
|
45
|
+
faraday (>= 0.8, < 2.0)
|
46
|
+
jwt (>= 1.0, < 3.0)
|
47
|
+
multi_json (~> 1.3)
|
48
|
+
multi_xml (~> 0.5)
|
49
|
+
rack (>= 1.2, < 3)
|
50
|
+
psych (3.2.0)
|
51
|
+
rack (1.6.13)
|
52
|
+
rack-protection (1.2.0)
|
53
|
+
rack
|
54
|
+
rake (13.0.1)
|
55
|
+
rchardet (1.8.0)
|
56
|
+
rdoc (6.2.1)
|
57
|
+
redis (3.0.2)
|
58
|
+
redis-namespace (1.2.1)
|
59
|
+
redis (~> 3.0.0)
|
60
|
+
resque (1.23.0)
|
61
|
+
multi_json (~> 1.0)
|
62
|
+
redis-namespace (~> 1.0)
|
63
|
+
sinatra (>= 0.9.2)
|
64
|
+
vegas (~> 0.1.2)
|
65
|
+
semver2 (3.4.2)
|
66
|
+
sinatra (1.3.3)
|
67
|
+
rack (~> 1.3, >= 1.3.6)
|
68
|
+
rack-protection (~> 1.2)
|
69
|
+
tilt (~> 1.3, >= 1.3.3)
|
70
|
+
thread_safe (0.3.6)
|
71
|
+
tilt (1.3.3)
|
72
|
+
vegas (0.1.11)
|
73
|
+
rack (>= 1.0.0)
|
74
|
+
|
75
|
+
PLATFORMS
|
76
|
+
ruby
|
77
|
+
|
78
|
+
DEPENDENCIES
|
79
|
+
jeweler
|
80
|
+
minitest (~> 5.5)
|
81
|
+
mocha (~> 0.9)
|
82
|
+
resque (~> 1.19)
|
83
|
+
|
84
|
+
BUNDLED WITH
|
85
|
+
2.1.4
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Aaron Quint
|
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.rdoc
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
= resque-status
|
2
|
+
|
3
|
+
resque-status is an extension to the resque queue system that provides simple trackable jobs.
|
4
|
+
|
5
|
+
== About
|
6
|
+
|
7
|
+
resque-status provides a set of simple classes that extend resque's default
|
8
|
+
functionality (with 0% monkey patching) to give apps a way to track specific
|
9
|
+
job instances and their status. It achieves this by giving job instances UUID's
|
10
|
+
and allowing the job instances to report their status from within their iterations.
|
11
|
+
|
12
|
+
== Installation
|
13
|
+
|
14
|
+
resque-status *requires Redis >= 1.1* (though I recommend getting the latest stable version).
|
15
|
+
You can download Redis here: http://code.google.com/p/redis/ or install it
|
16
|
+
using homebrew (brew install redis).
|
17
|
+
|
18
|
+
Install the resque-status gem (which will pull in the dependencies).
|
19
|
+
|
20
|
+
gem install resque-status
|
21
|
+
|
22
|
+
With newer Rails add this to your Gemfile:
|
23
|
+
|
24
|
+
# Gemfile
|
25
|
+
gem 'resque-status'
|
26
|
+
|
27
|
+
Then in an initializer:
|
28
|
+
|
29
|
+
# config/initializers/resque.rb
|
30
|
+
Resque.redis = "your/redis/socket" # default localhost:6379
|
31
|
+
Resque::Plugins::Status::Hash.expire_in = (24 * 60 * 60) # 24hrs in seconds
|
32
|
+
|
33
|
+
== Usage
|
34
|
+
|
35
|
+
The most direct way to use resque-status is to create your jobs using the
|
36
|
+
Resque::Plugins::Status module. An example job would look something like:
|
37
|
+
|
38
|
+
class SleepJob
|
39
|
+
include Resque::Plugins::Status
|
40
|
+
|
41
|
+
def perform
|
42
|
+
total = (options['length'] || 1000).to_i
|
43
|
+
total.times do |i|
|
44
|
+
num = i+1
|
45
|
+
at(num, total, "At #{num} of #{total}")
|
46
|
+
sleep(1)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
One major difference is that instead of implementing <tt>perform</tt> as a
|
52
|
+
class method, we do our job implementation within instances of the job class.
|
53
|
+
|
54
|
+
In order to queue a SleepJob up, we also won't use <tt>Resque.enqueue</tt>, instead
|
55
|
+
we'll use the <tt>create</tt> class method which will wrap <tt>enqueue</tt> and
|
56
|
+
creating a unique id (UUID) for us to track the job with.
|
57
|
+
|
58
|
+
job_id = SleepJob.create(length: 100)
|
59
|
+
|
60
|
+
This will create a UUID enqueue the job and pass the :length option on the SleepJob
|
61
|
+
instance as options['length'] (as you can see above).
|
62
|
+
|
63
|
+
Now that we have a UUID its really easy to get the status:
|
64
|
+
|
65
|
+
status = Resque::Plugins::Status::Hash.get(job_id)
|
66
|
+
|
67
|
+
This returns a Resque::Plugins::Status::Hash object, which is a Hash (with benefits).
|
68
|
+
|
69
|
+
status.pct_complete #=> 0
|
70
|
+
status.status #=> 'queued'
|
71
|
+
status.queued? #=> true
|
72
|
+
status.working? #=> false
|
73
|
+
status.time #=> Time object
|
74
|
+
status.message #=> "Created at ..."
|
75
|
+
|
76
|
+
Once the worker reserves the job, the instance of SleepJob updates the status at
|
77
|
+
each iteration using <tt>at()</tt>
|
78
|
+
|
79
|
+
status = Resque::Plugins::Status::Hash.get(job_id)
|
80
|
+
status.working? #=> true
|
81
|
+
status.num #=> 5
|
82
|
+
status.total #=> 100
|
83
|
+
status.pct_complete #=> 5
|
84
|
+
|
85
|
+
If an error occurs within the job instance, the status is set to 'failed' and then
|
86
|
+
the error is re-raised so that Resque can capture it.
|
87
|
+
|
88
|
+
Its also possible to get a list of current/recent job statuses:
|
89
|
+
|
90
|
+
Resque::Plugins::Status::Hash.statuses #=> [#<Resque::Plugins::Status::Hash>, ...]
|
91
|
+
|
92
|
+
=== Passing back data from the job
|
93
|
+
|
94
|
+
You may want to save data from inside the job to access it from outside the job.
|
95
|
+
|
96
|
+
A common use-case is web-triggered jobs that create files, later available for
|
97
|
+
download by the user.
|
98
|
+
|
99
|
+
A Status is actually just a hash, so inside a job you can do:
|
100
|
+
|
101
|
+
set_status(filename: "myfilename")
|
102
|
+
|
103
|
+
Also, all the status setting methods take any number of hash arguments. So you could do:
|
104
|
+
|
105
|
+
completed('filename' => '/myfilename')
|
106
|
+
|
107
|
+
=== Kill! Kill! Kill!
|
108
|
+
|
109
|
+
Because we're tracking UUIDs per instance, and we're checking in/updating the status
|
110
|
+
on each iteration (using <tt>at</tt> or <tt>tick</tt>) we can kill specific jobs
|
111
|
+
by UUID.
|
112
|
+
|
113
|
+
Resque::Plugins::Status::Hash.kill(job_id)
|
114
|
+
|
115
|
+
The next time the job at job_id calls <tt>at</tt> or <tt>tick</tt>, it will raise a <tt>Killed</tt>
|
116
|
+
error and set the status to killed.
|
117
|
+
|
118
|
+
=== Percent Complete and setting the message
|
119
|
+
|
120
|
+
Use <tt>at</tt> or <tt>tick</tt> to show progress in your job's <tt>perform</tt> function
|
121
|
+
(which is displayed on the resque-web status tab). This will also be where <tt>Killed</tt>
|
122
|
+
is raised if the job is killed.
|
123
|
+
|
124
|
+
at(steps_completed, total_steps, "${steps_completed} of #{total_steps} steps completed!")
|
125
|
+
|
126
|
+
=== Expiration
|
127
|
+
|
128
|
+
Since Redis is RAM based, we probably don't want to keep these statuses around forever
|
129
|
+
(at least until @antirez releases the VM feature). By setting expire_in, all statuses
|
130
|
+
and their related keys will expire in expire_in seconds from the last time theyre updated:
|
131
|
+
|
132
|
+
Resque::Plugins::Status::Hash.expire_in = (60 * 60) # 1 hour
|
133
|
+
=== Testing
|
134
|
+
|
135
|
+
Recent versions of Resque introduced `Resque.inline` which changes the behavior to
|
136
|
+
instead of enqueueing and performing jobs to just executing them inline. In Resque
|
137
|
+
itself this removes the dependency on a Redis, however, `Resque::Status` uses Redis
|
138
|
+
to store information about jobs, so though `inline` "works", you will still need
|
139
|
+
to use or mock a redis connection. You should be able to use a library like
|
140
|
+
https://github.com/causes/mock_redis alongside `inline` if you really want to
|
141
|
+
avoid Redis connections in your test.
|
142
|
+
|
143
|
+
=== resque-web
|
144
|
+
|
145
|
+
Though the main purpose of these trackable jobs is to allow you to surface the status
|
146
|
+
of user created jobs through your apps' own UI, I've added a simple example UI
|
147
|
+
as a plugin to resque-web.
|
148
|
+
|
149
|
+
To use, you need to setup a resque-web config file:
|
150
|
+
|
151
|
+
# ~/resque_conf.rb
|
152
|
+
require 'resque/status_server'
|
153
|
+
|
154
|
+
Then start resque-web with your config:
|
155
|
+
|
156
|
+
resque-web ~/resque_conf.rb
|
157
|
+
|
158
|
+
This should launch resque-web in your browser and you should see a 'Statuses' tab.
|
159
|
+
|
160
|
+
http://img.skitch.com/20100119-k166xyijcjpkk6xtwnw3854a8g.jpg
|
161
|
+
|
162
|
+
== More
|
163
|
+
|
164
|
+
Source: http://github.com/quirkey/resque-status
|
165
|
+
API Docs: http://rdoc.info/projects/quirkey/resque-status
|
166
|
+
Examples: http://github.com/quirkey/resque-status/tree/master/examples
|
167
|
+
Resque: http://github.com/defunkt/resque
|
168
|
+
|
169
|
+
== Thanks
|
170
|
+
|
171
|
+
Resque is awesome, @defunkt needs a shout-out.
|
172
|
+
|
173
|
+
== Note on Patches/Pull Requests
|
174
|
+
|
175
|
+
* Fork the project.
|
176
|
+
* Make your feature addition or bug fix.
|
177
|
+
* Add tests for it. This is important so I don't break it in a
|
178
|
+
future version unintentionally.
|
179
|
+
* Commit, do not mess with rakefile, version, or history.
|
180
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
181
|
+
* Send me a pull request. Bonus points for topic branches.
|
182
|
+
|
183
|
+
== Copyright
|
184
|
+
|
185
|
+
Copyright (c) 2010 Aaron Quint. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
$LOAD_PATH.unshift './lib'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rake'
|
5
|
+
require 'resque-status'
|
6
|
+
require 'resque/tasks'
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'jeweler'
|
10
|
+
Jeweler::Tasks.new do |gem|
|
11
|
+
gem.name = "powerhome-resque-status"
|
12
|
+
gem.version = Resque::Plugins::Status::VERSION
|
13
|
+
gem.summary = %Q{resque-status is an extension to the resque queue system that provides simple trackable jobs.}
|
14
|
+
gem.description = %Q{resque-status is an extension to the resque queue system that provides simple trackable jobs. It provides a Resque::Plugins::Status::Hash class which can set/get the statuses of jobs and a Resque::Plugins::Status class that when included provides easily trackable/killable jobs.}
|
15
|
+
gem.email = "aaron@quirkey.com"
|
16
|
+
gem.homepage = "http://github.com/quirkey/resque-status"
|
17
|
+
gem.rubyforge_project = "quirkey"
|
18
|
+
gem.authors = ["Aaron Quint"]
|
19
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
20
|
+
end
|
21
|
+
Jeweler::RubygemsDotOrgTasks.new
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rake/testtask'
|
27
|
+
Rake::TestTask.new(:test) do |test|
|
28
|
+
test.libs << 'lib' << 'test'
|
29
|
+
test.pattern = 'test/**/test_*.rb'
|
30
|
+
test.verbose = true
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
require 'rcov/rcovtask'
|
35
|
+
Rcov::RcovTask.new do |test|
|
36
|
+
test.libs << 'test'
|
37
|
+
test.pattern = 'test/**/test_*.rb'
|
38
|
+
test.verbose = true
|
39
|
+
end
|
40
|
+
rescue LoadError
|
41
|
+
task :rcov do
|
42
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
task :test
|
47
|
+
|
48
|
+
task :default => :test
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'resque/job_with_status' # in rails you would probably do this in an initializer
|
2
|
+
|
3
|
+
# sleeps for _length_ seconds updating the status every second
|
4
|
+
|
5
|
+
class SleepJob
|
6
|
+
include Resque::Plugins::Status
|
7
|
+
|
8
|
+
def perform
|
9
|
+
total = options.has_key?('length') ? options['length'].to_i : 1000
|
10
|
+
num = 0
|
11
|
+
while num < total
|
12
|
+
at(num, total, "At #{num} of #{total}")
|
13
|
+
sleep(1)
|
14
|
+
num += 1
|
15
|
+
end
|
16
|
+
completed
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
if __FILE__ == $0
|
23
|
+
# Make sure you have a worker running
|
24
|
+
# rake -rexamples/sleep_job.rb resque:work QUEUE=statused
|
25
|
+
|
26
|
+
# running the job
|
27
|
+
puts "Creating the SleepJob"
|
28
|
+
job_id = SleepJob.create :length => 100
|
29
|
+
puts "Got back #{job_id}"
|
30
|
+
|
31
|
+
# check the status until its complete
|
32
|
+
while status = Resque::Plugins::Status::Hash.get(job_id) and !status.completed? && !status.failed?
|
33
|
+
sleep 1
|
34
|
+
puts status.inspect
|
35
|
+
end
|
36
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'resque-status'
|
@@ -0,0 +1 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/resque/status"
|
@@ -0,0 +1,254 @@
|
|
1
|
+
module Resque
|
2
|
+
module Plugins
|
3
|
+
|
4
|
+
# Resque::Plugins::Status is a module your jobs will include.
|
5
|
+
# It provides helper methods for updating the status/etc from within an
|
6
|
+
# instance as well as class methods for creating and queuing the jobs.
|
7
|
+
#
|
8
|
+
# All you have to do to get this functionality is include Resque::Plugins::Status
|
9
|
+
# and then implement a <tt>perform<tt> method.
|
10
|
+
#
|
11
|
+
# For example
|
12
|
+
#
|
13
|
+
# class ExampleJob
|
14
|
+
# include Resque::Plugins::Status
|
15
|
+
#
|
16
|
+
# def perform
|
17
|
+
# num = options['num']
|
18
|
+
# i = 0
|
19
|
+
# while i < num
|
20
|
+
# i += 1
|
21
|
+
# at(i, num)
|
22
|
+
# end
|
23
|
+
# completed("Finished!")
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# This job would iterate num times updating the status as it goes. At the end
|
29
|
+
# we update the status telling anyone listening to this job that its complete.
|
30
|
+
module Status
|
31
|
+
VERSION = '0.6.0'
|
32
|
+
|
33
|
+
STATUS_QUEUED = 'queued'
|
34
|
+
STATUS_WORKING = 'working'
|
35
|
+
STATUS_COMPLETED = 'completed'
|
36
|
+
STATUS_FAILED = 'failed'
|
37
|
+
STATUS_KILLED = 'killed'
|
38
|
+
STATUSES = [
|
39
|
+
STATUS_QUEUED,
|
40
|
+
STATUS_WORKING,
|
41
|
+
STATUS_COMPLETED,
|
42
|
+
STATUS_FAILED,
|
43
|
+
STATUS_KILLED
|
44
|
+
].freeze
|
45
|
+
|
46
|
+
autoload :Hash, 'resque/plugins/status/hash'
|
47
|
+
|
48
|
+
# The error class raised when a job is killed
|
49
|
+
class Killed < RuntimeError; end
|
50
|
+
class NotANumber < RuntimeError; end
|
51
|
+
|
52
|
+
attr_reader :uuid, :options
|
53
|
+
|
54
|
+
def self.included(base)
|
55
|
+
base.extend(ClassMethods)
|
56
|
+
end
|
57
|
+
|
58
|
+
module ClassMethods
|
59
|
+
|
60
|
+
# The default queue is :statused, this can be ovveridden in the specific job
|
61
|
+
# class to put the jobs on a specific worker queue
|
62
|
+
def queue
|
63
|
+
:statused
|
64
|
+
end
|
65
|
+
|
66
|
+
# used when displaying the Job in the resque-web UI and identifiyng the job
|
67
|
+
# type by status. By default this is the name of the job class, but can be
|
68
|
+
# ovveridden in the specific job class to present a more user friendly job
|
69
|
+
# name
|
70
|
+
def name
|
71
|
+
self.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
# Create is the primary method for adding jobs to the queue. This would be
|
75
|
+
# called on the job class to create a job of that type. Any options passed are
|
76
|
+
# passed to the Job instance as a hash of options. It returns the UUID of the
|
77
|
+
# job.
|
78
|
+
#
|
79
|
+
# == Example:
|
80
|
+
#
|
81
|
+
# class ExampleJob
|
82
|
+
# include Resque::Plugins::Status
|
83
|
+
#
|
84
|
+
# def perform
|
85
|
+
# set_status "Hey I'm a job num #{options['num']}"
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# job_id = ExampleJob.create(:num => 100)
|
91
|
+
#
|
92
|
+
def create(options = {})
|
93
|
+
self.enqueue(self, options)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Adds a job of type <tt>klass<tt> to the queue with <tt>options<tt>.
|
97
|
+
#
|
98
|
+
# Returns the UUID of the job if the job was queued, or nil if the job was
|
99
|
+
# rejected by a before_enqueue hook.
|
100
|
+
def enqueue(klass, options = {})
|
101
|
+
self.enqueue_to(Resque.queue_from_class(klass) || queue, klass, options)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Adds a job of type <tt>klass<tt> to a specified queue with <tt>options<tt>.
|
105
|
+
#
|
106
|
+
# Returns the UUID of the job if the job was queued, or nil if the job was
|
107
|
+
# rejected by a before_enqueue hook.
|
108
|
+
def enqueue_to(queue, klass, options = {})
|
109
|
+
uuid = Resque::Plugins::Status::Hash.generate_uuid
|
110
|
+
Resque::Plugins::Status::Hash.create uuid, :options => options
|
111
|
+
|
112
|
+
if Resque.enqueue_to(queue, klass, uuid, options)
|
113
|
+
uuid
|
114
|
+
else
|
115
|
+
Resque::Plugins::Status::Hash.remove(uuid)
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Removes a job of type <tt>klass<tt> from the queue.
|
121
|
+
#
|
122
|
+
# The initially given options are retrieved from the status hash.
|
123
|
+
# (Resque needs the options to find the correct queue entry)
|
124
|
+
def dequeue(klass, uuid)
|
125
|
+
status = Resque::Plugins::Status::Hash.get(uuid)
|
126
|
+
Resque.dequeue(klass, uuid, status.options)
|
127
|
+
end
|
128
|
+
|
129
|
+
# This is the method called by Resque::Worker when processing jobs. It
|
130
|
+
# creates a new instance of the job class and populates it with the uuid and
|
131
|
+
# options.
|
132
|
+
#
|
133
|
+
# You should not override this method, rahter the <tt>perform</tt> instance method.
|
134
|
+
def perform(uuid=nil, options = {})
|
135
|
+
uuid ||= Resque::Plugins::Status::Hash.generate_uuid
|
136
|
+
instance = new(uuid, options)
|
137
|
+
instance.safe_perform!
|
138
|
+
instance
|
139
|
+
end
|
140
|
+
|
141
|
+
# Wrapper API to forward a Resque::Job creation API call into a Resque::Plugins::Status call.
|
142
|
+
# This is needed to be used with resque scheduler
|
143
|
+
# http://github.com/bvandenbos/resque-scheduler
|
144
|
+
def scheduled(queue, klass, *args)
|
145
|
+
self.enqueue_to(queue, self, *args)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Create a new instance with <tt>uuid</tt> and <tt>options</tt>
|
150
|
+
def initialize(uuid, options = {})
|
151
|
+
@uuid = uuid
|
152
|
+
@options = options
|
153
|
+
end
|
154
|
+
|
155
|
+
# Run by the Resque::Worker when processing this job. It wraps the <tt>perform</tt>
|
156
|
+
# method ensuring that the final status of the job is set regardless of error.
|
157
|
+
# If an error occurs within the job's work, it will set the status as failed and
|
158
|
+
# re-raise the error.
|
159
|
+
def safe_perform!
|
160
|
+
set_status({'status' => STATUS_WORKING})
|
161
|
+
perform
|
162
|
+
if status && status.failed?
|
163
|
+
on_failure(status.message) if respond_to?(:on_failure)
|
164
|
+
return
|
165
|
+
elsif status && !status.completed?
|
166
|
+
completed
|
167
|
+
end
|
168
|
+
on_success if respond_to?(:on_success)
|
169
|
+
rescue Killed
|
170
|
+
Resque::Plugins::Status::Hash.killed(uuid)
|
171
|
+
on_killed if respond_to?(:on_killed)
|
172
|
+
rescue => e
|
173
|
+
failed("The task failed because of an error: #{e}")
|
174
|
+
if respond_to?(:on_failure)
|
175
|
+
on_failure(e)
|
176
|
+
else
|
177
|
+
raise e
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Set the jobs status. Can take an array of strings or hashes that are merged
|
182
|
+
# (in order) into a final status hash.
|
183
|
+
def status=(new_status)
|
184
|
+
Resque::Plugins::Status::Hash.set(uuid, *new_status)
|
185
|
+
end
|
186
|
+
|
187
|
+
# get the Resque::Plugins::Status::Hash object for the current uuid
|
188
|
+
def status
|
189
|
+
Resque::Plugins::Status::Hash.get(uuid)
|
190
|
+
end
|
191
|
+
|
192
|
+
def name
|
193
|
+
"#{self.class.name}(#{options.inspect unless options.empty?})"
|
194
|
+
end
|
195
|
+
|
196
|
+
# Checks against the kill list if this specific job instance should be killed
|
197
|
+
# on the next iteration
|
198
|
+
def should_kill?
|
199
|
+
Resque::Plugins::Status::Hash.should_kill?(uuid)
|
200
|
+
end
|
201
|
+
|
202
|
+
# set the status of the job for the current itteration. <tt>num</tt> and
|
203
|
+
# <tt>total</tt> are passed to the status as well as any messages.
|
204
|
+
# This will kill the job if it has been added to the kill list with
|
205
|
+
# <tt>Resque::Plugins::Status::Hash.kill()</tt>
|
206
|
+
def at(num, total, *messages)
|
207
|
+
if total.to_f <= 0.0
|
208
|
+
raise(NotANumber, "Called at() with total=#{total} which is not a number")
|
209
|
+
end
|
210
|
+
tick({
|
211
|
+
'num' => num,
|
212
|
+
'total' => total
|
213
|
+
}, *messages)
|
214
|
+
end
|
215
|
+
|
216
|
+
# sets the status of the job for the current itteration. You should use
|
217
|
+
# the <tt>at</tt> method if you have actual numbers to track the iteration count.
|
218
|
+
# This will kill the job if it has been added to the kill list with
|
219
|
+
# <tt>Resque::Plugins::Status::Hash.kill()</tt>
|
220
|
+
def tick(*messages)
|
221
|
+
kill! if should_kill?
|
222
|
+
set_status({'status' => STATUS_WORKING}, *messages)
|
223
|
+
end
|
224
|
+
|
225
|
+
# set the status to 'failed' passing along any additional messages
|
226
|
+
def failed(*messages)
|
227
|
+
set_status({'status' => STATUS_FAILED}, *messages)
|
228
|
+
end
|
229
|
+
|
230
|
+
# set the status to 'completed' passing along any addional messages
|
231
|
+
def completed(*messages)
|
232
|
+
set_status({
|
233
|
+
'status' => STATUS_COMPLETED,
|
234
|
+
'message' => "Completed at #{Time.now}"
|
235
|
+
}, *messages)
|
236
|
+
end
|
237
|
+
|
238
|
+
# kill the current job, setting the status to 'killed' and raising <tt>Killed</tt>
|
239
|
+
def kill!
|
240
|
+
set_status({
|
241
|
+
'status' => STATUS_KILLED,
|
242
|
+
'message' => "Killed at #{Time.now}"
|
243
|
+
})
|
244
|
+
raise Killed
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
def set_status(*args)
|
249
|
+
self.status = [status, {'name' => self.name}, args].flatten
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|