powerhome-resque-status 0.6.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.
- 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
|