resqueue 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/HISTORY.md +488 -0
- data/LICENSE +20 -0
- data/README.markdown +920 -0
- data/Rakefile +57 -0
- data/bin/resque +81 -0
- data/bin/resque-web +31 -0
- data/lib/resque.rb +578 -0
- data/lib/resque/data_store.rb +326 -0
- data/lib/resque/errors.rb +21 -0
- data/lib/resque/failure.rb +119 -0
- data/lib/resque/failure/airbrake.rb +33 -0
- data/lib/resque/failure/base.rb +73 -0
- data/lib/resque/failure/multiple.rb +68 -0
- data/lib/resque/failure/redis.rb +128 -0
- data/lib/resque/failure/redis_multi_queue.rb +104 -0
- data/lib/resque/helpers.rb +48 -0
- data/lib/resque/job.rb +296 -0
- data/lib/resque/log_formatters/quiet_formatter.rb +7 -0
- data/lib/resque/log_formatters/verbose_formatter.rb +7 -0
- data/lib/resque/log_formatters/very_verbose_formatter.rb +8 -0
- data/lib/resque/logging.rb +18 -0
- data/lib/resque/plugin.rb +78 -0
- data/lib/resque/server.rb +299 -0
- data/lib/resque/server/helpers.rb +64 -0
- data/lib/resque/server/public/favicon.ico +0 -0
- data/lib/resque/server/public/idle.png +0 -0
- data/lib/resque/server/public/jquery-1.12.4.min.js +5 -0
- data/lib/resque/server/public/jquery.relatize_date.js +95 -0
- data/lib/resque/server/public/poll.png +0 -0
- data/lib/resque/server/public/ranger.js +78 -0
- data/lib/resque/server/public/reset.css +44 -0
- data/lib/resque/server/public/style.css +91 -0
- data/lib/resque/server/public/working.png +0 -0
- data/lib/resque/server/test_helper.rb +19 -0
- data/lib/resque/server/views/error.erb +1 -0
- data/lib/resque/server/views/failed.erb +29 -0
- data/lib/resque/server/views/failed_job.erb +50 -0
- data/lib/resque/server/views/failed_queues_overview.erb +24 -0
- data/lib/resque/server/views/key_sets.erb +17 -0
- data/lib/resque/server/views/key_string.erb +11 -0
- data/lib/resque/server/views/layout.erb +44 -0
- data/lib/resque/server/views/next_more.erb +22 -0
- data/lib/resque/server/views/overview.erb +4 -0
- data/lib/resque/server/views/queues.erb +58 -0
- data/lib/resque/server/views/stats.erb +62 -0
- data/lib/resque/server/views/workers.erb +111 -0
- data/lib/resque/server/views/working.erb +72 -0
- data/lib/resque/stat.rb +58 -0
- data/lib/resque/tasks.rb +72 -0
- data/lib/resque/thread_signal.rb +45 -0
- data/lib/resque/vendor/utf8_util.rb +26 -0
- data/lib/resque/vendor/utf8_util/utf8_util_18.rb +91 -0
- data/lib/resque/vendor/utf8_util/utf8_util_19.rb +6 -0
- data/lib/resque/version.rb +3 -0
- data/lib/resque/worker.rb +892 -0
- data/lib/resqueue.rb +4 -0
- data/lib/tasks/redis.rake +161 -0
- data/lib/tasks/resque.rake +2 -0
- metadata +197 -0
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#
|
2
|
+
# Setup
|
3
|
+
#
|
4
|
+
|
5
|
+
load 'lib/tasks/redis.rake'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift 'lib'
|
8
|
+
require 'resque/tasks'
|
9
|
+
|
10
|
+
def command?(command)
|
11
|
+
system("type #{command} > /dev/null 2>&1")
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'rubygems'
|
15
|
+
require 'bundler/setup'
|
16
|
+
require 'bundler/gem_tasks'
|
17
|
+
|
18
|
+
|
19
|
+
#
|
20
|
+
# Tests
|
21
|
+
#
|
22
|
+
|
23
|
+
require 'rake/testtask'
|
24
|
+
|
25
|
+
task :default => :test
|
26
|
+
|
27
|
+
Rake::TestTask.new do |test|
|
28
|
+
test.verbose = true
|
29
|
+
test.libs << "test"
|
30
|
+
test.libs << "lib"
|
31
|
+
test.test_files = FileList['test/**/*_test.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
if command? :kicker
|
35
|
+
desc "Launch Kicker (like autotest)"
|
36
|
+
task :kicker do
|
37
|
+
puts "Kicking... (ctrl+c to cancel)"
|
38
|
+
exec "kicker -e rake test lib examples"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
#
|
44
|
+
# Install
|
45
|
+
#
|
46
|
+
|
47
|
+
task :install => [ 'redis:install', 'dtach:install' ]
|
48
|
+
|
49
|
+
|
50
|
+
#
|
51
|
+
# Documentation
|
52
|
+
#
|
53
|
+
|
54
|
+
begin
|
55
|
+
require 'sdoc_helpers'
|
56
|
+
rescue LoadError
|
57
|
+
end
|
data/bin/resque
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
begin
|
5
|
+
require 'redis-namespace'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'redis-namespace'
|
9
|
+
end
|
10
|
+
require 'resque'
|
11
|
+
require 'optparse'
|
12
|
+
|
13
|
+
parser = OptionParser.new do |opts|
|
14
|
+
opts.banner = "Usage: resque [options] COMMAND"
|
15
|
+
|
16
|
+
opts.separator ""
|
17
|
+
opts.separator "Options:"
|
18
|
+
|
19
|
+
opts.on("-r", "--redis [HOST:PORT]", "Redis connection string") do |host|
|
20
|
+
Resque.redis = host
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-N", "--namespace [NAMESPACE]", "Redis namespace") do |namespace|
|
24
|
+
Resque.redis.namespace = namespace
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("-h", "--help", "Show this message") do
|
28
|
+
puts opts
|
29
|
+
exit
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.separator ""
|
33
|
+
opts.separator "Commands:"
|
34
|
+
opts.separator " remove WORKER Removes a worker"
|
35
|
+
opts.separator " kill WORKER Kills a worker"
|
36
|
+
opts.separator " list Lists known workers"
|
37
|
+
end
|
38
|
+
|
39
|
+
def kill(worker)
|
40
|
+
abort "** resque kill WORKER_ID" if worker.nil?
|
41
|
+
pid = worker.split(':')[1].to_i
|
42
|
+
|
43
|
+
begin
|
44
|
+
Process.kill("KILL", pid)
|
45
|
+
puts "** killed #{worker}"
|
46
|
+
rescue Errno::ESRCH
|
47
|
+
puts "** worker #{worker} not running"
|
48
|
+
end
|
49
|
+
|
50
|
+
remove worker
|
51
|
+
end
|
52
|
+
|
53
|
+
def remove(worker)
|
54
|
+
abort "** resque remove WORKER_ID" if worker.nil?
|
55
|
+
|
56
|
+
Resque.remove_worker(worker)
|
57
|
+
puts "** removed #{worker}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def list
|
61
|
+
if Resque.workers.any?
|
62
|
+
Resque.workers.each do |worker|
|
63
|
+
puts "#{worker} (#{worker.state})"
|
64
|
+
end
|
65
|
+
else
|
66
|
+
puts "None"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
parser.parse!
|
71
|
+
|
72
|
+
case ARGV[0]
|
73
|
+
when 'kill'
|
74
|
+
kill ARGV[1]
|
75
|
+
when 'remove'
|
76
|
+
remove ARGV[1]
|
77
|
+
when 'list'
|
78
|
+
list
|
79
|
+
else
|
80
|
+
puts parser.help
|
81
|
+
end
|
data/bin/resque-web
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
begin
|
5
|
+
require 'vegas'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'vegas'
|
9
|
+
end
|
10
|
+
require 'resque/server'
|
11
|
+
|
12
|
+
|
13
|
+
Vegas::Runner.new(Resque::Server, 'resque-web', {
|
14
|
+
:before_run => lambda {|v|
|
15
|
+
path = (ENV['RESQUECONFIG'] || v.args.first)
|
16
|
+
load path.to_s.strip if path
|
17
|
+
}
|
18
|
+
}) do |runner, opts, app|
|
19
|
+
opts.on('-N NAMESPACE', "--namespace NAMESPACE", "set the Redis namespace") {|namespace|
|
20
|
+
runner.logger.info "Using Redis namespace '#{namespace}'"
|
21
|
+
Resque.redis.namespace = namespace
|
22
|
+
}
|
23
|
+
opts.on('-r redis-connection', "--redis redis-connection", "set the Redis connection string") {|redis_conf|
|
24
|
+
runner.logger.info "Using Redis connection '#{redis_conf}'"
|
25
|
+
Resque.redis = redis_conf
|
26
|
+
}
|
27
|
+
opts.on('-a url-prefix', "--append url-prefix", "set reverse_proxy friendly prefix to links") {|url_prefix|
|
28
|
+
runner.logger.info "Using URL Prefix '#{url_prefix}'"
|
29
|
+
Resque::Server.url_prefix = url_prefix
|
30
|
+
}
|
31
|
+
end
|
data/lib/resque.rb
ADDED
@@ -0,0 +1,578 @@
|
|
1
|
+
require 'mono_logger'
|
2
|
+
require 'redis/namespace'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require 'resque/version'
|
6
|
+
|
7
|
+
require 'resque/errors'
|
8
|
+
|
9
|
+
require 'resque/failure'
|
10
|
+
require 'resque/failure/base'
|
11
|
+
|
12
|
+
require 'resque/helpers'
|
13
|
+
require 'resque/stat'
|
14
|
+
require 'resque/logging'
|
15
|
+
require 'resque/log_formatters/quiet_formatter'
|
16
|
+
require 'resque/log_formatters/verbose_formatter'
|
17
|
+
require 'resque/log_formatters/very_verbose_formatter'
|
18
|
+
require 'resque/job'
|
19
|
+
require 'resque/worker'
|
20
|
+
require 'resque/plugin'
|
21
|
+
require 'resque/data_store'
|
22
|
+
require 'resque/thread_signal'
|
23
|
+
|
24
|
+
require 'resque/vendor/utf8_util'
|
25
|
+
|
26
|
+
module Resque
|
27
|
+
include Helpers
|
28
|
+
extend self
|
29
|
+
|
30
|
+
# Given a Ruby object, returns a string suitable for storage in a
|
31
|
+
# queue.
|
32
|
+
def encode(object)
|
33
|
+
if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
|
34
|
+
MultiJson.dump object
|
35
|
+
else
|
36
|
+
MultiJson.encode object
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Given a string, returns a Ruby object.
|
41
|
+
def decode(object)
|
42
|
+
return unless object
|
43
|
+
|
44
|
+
begin
|
45
|
+
if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
|
46
|
+
MultiJson.load object
|
47
|
+
else
|
48
|
+
MultiJson.decode object
|
49
|
+
end
|
50
|
+
rescue ::MultiJson::DecodeError => e
|
51
|
+
raise Helpers::DecodeException, e.message, e.backtrace
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Given a word with dashes, returns a camel cased version of it.
|
56
|
+
#
|
57
|
+
# classify('job-name') # => 'JobName'
|
58
|
+
def classify(dashed_word)
|
59
|
+
dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
|
60
|
+
end
|
61
|
+
|
62
|
+
# Tries to find a constant with the name specified in the argument string:
|
63
|
+
#
|
64
|
+
# constantize("Module") # => Module
|
65
|
+
# constantize("Test::Unit") # => Test::Unit
|
66
|
+
#
|
67
|
+
# The name is assumed to be the one of a top-level constant, no matter
|
68
|
+
# whether it starts with "::" or not. No lexical context is taken into
|
69
|
+
# account:
|
70
|
+
#
|
71
|
+
# C = 'outside'
|
72
|
+
# module M
|
73
|
+
# C = 'inside'
|
74
|
+
# C # => 'inside'
|
75
|
+
# constantize("C") # => 'outside', same as ::C
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# NameError is raised when the constant is unknown.
|
79
|
+
def constantize(camel_cased_word)
|
80
|
+
camel_cased_word = camel_cased_word.to_s
|
81
|
+
|
82
|
+
if camel_cased_word.include?('-')
|
83
|
+
camel_cased_word = classify(camel_cased_word)
|
84
|
+
end
|
85
|
+
|
86
|
+
names = camel_cased_word.split('::')
|
87
|
+
names.shift if names.empty? || names.first.empty?
|
88
|
+
|
89
|
+
constant = Object
|
90
|
+
names.each do |name|
|
91
|
+
args = Module.method(:const_get).arity != 1 ? [false] : []
|
92
|
+
|
93
|
+
if constant.const_defined?(name, *args)
|
94
|
+
constant = constant.const_get(name)
|
95
|
+
else
|
96
|
+
constant = constant.const_missing(name)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
constant
|
100
|
+
end
|
101
|
+
|
102
|
+
extend ::Forwardable
|
103
|
+
|
104
|
+
# Accepts:
|
105
|
+
# 1. A 'hostname:port' String
|
106
|
+
# 2. A 'hostname:port:db' String (to select the Redis db)
|
107
|
+
# 3. A 'hostname:port/namespace' String (to set the Redis namespace)
|
108
|
+
# 4. A Redis URL String 'redis://host:port'
|
109
|
+
# 5. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`,
|
110
|
+
# or `Redis::Namespace`.
|
111
|
+
# 6. An Hash of a redis connection {:host => 'localhost', :port => 6379, :db => 0}
|
112
|
+
def redis=(server)
|
113
|
+
case server
|
114
|
+
when String
|
115
|
+
if server =~ /redis\:\/\//
|
116
|
+
redis = Redis.connect(:url => server, :thread_safe => true)
|
117
|
+
else
|
118
|
+
server, namespace = server.split('/', 2)
|
119
|
+
host, port, db = server.split(':')
|
120
|
+
redis = Redis.new(:host => host, :port => port,
|
121
|
+
:thread_safe => true, :db => db)
|
122
|
+
end
|
123
|
+
namespace ||= :resque
|
124
|
+
|
125
|
+
@data_store = Resque::DataStore.new(Redis::Namespace.new(namespace, :redis => redis))
|
126
|
+
when Redis::Namespace
|
127
|
+
@data_store = Resque::DataStore.new(server)
|
128
|
+
when Resque::DataStore
|
129
|
+
@data_store = server
|
130
|
+
when Hash
|
131
|
+
@data_store = Resque::DataStore.new(Redis::Namespace.new(:resque, :redis => Redis.new(server)))
|
132
|
+
else
|
133
|
+
@data_store = Resque::DataStore.new(Redis::Namespace.new(:resque, :redis => server))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns the current Redis connection. If none has been created, will
|
138
|
+
# create a new one.
|
139
|
+
def redis
|
140
|
+
return @data_store if @data_store
|
141
|
+
self.redis = Redis.respond_to?(:connect) ? Redis.connect : "localhost:6379"
|
142
|
+
self.redis
|
143
|
+
end
|
144
|
+
alias :data_store :redis
|
145
|
+
|
146
|
+
def redis_id
|
147
|
+
data_store.identifier
|
148
|
+
end
|
149
|
+
|
150
|
+
# Set or retrieve the current logger object
|
151
|
+
attr_accessor :logger
|
152
|
+
|
153
|
+
DEFAULT_HEARTBEAT_INTERVAL = 60
|
154
|
+
DEFAULT_PRUNE_INTERVAL = DEFAULT_HEARTBEAT_INTERVAL * 5
|
155
|
+
|
156
|
+
attr_writer :heartbeat_interval
|
157
|
+
def heartbeat_interval
|
158
|
+
@heartbeat_interval || DEFAULT_HEARTBEAT_INTERVAL
|
159
|
+
end
|
160
|
+
|
161
|
+
attr_writer :prune_interval
|
162
|
+
def prune_interval
|
163
|
+
@prune_interval || DEFAULT_PRUNE_INTERVAL
|
164
|
+
end
|
165
|
+
|
166
|
+
attr_writer :enqueue_front
|
167
|
+
def enqueue_front
|
168
|
+
return @enqueue_front unless @enqueue_front.nil?
|
169
|
+
@enqueue_front = false
|
170
|
+
end
|
171
|
+
|
172
|
+
# The `before_first_fork` hook will be run in the **parent** process
|
173
|
+
# only once, before forking to run the first job. Be careful- any
|
174
|
+
# changes you make will be permanent for the lifespan of the
|
175
|
+
# worker.
|
176
|
+
#
|
177
|
+
# Call with a block to register a hook.
|
178
|
+
# Call with no arguments to return all registered hooks.
|
179
|
+
def before_first_fork(&block)
|
180
|
+
block ? register_hook(:before_first_fork, block) : hooks(:before_first_fork)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Register a before_first_fork proc.
|
184
|
+
def before_first_fork=(block)
|
185
|
+
register_hook(:before_first_fork, block)
|
186
|
+
end
|
187
|
+
|
188
|
+
# The `before_fork` hook will be run in the **parent** process
|
189
|
+
# before every job, so be careful- any changes you make will be
|
190
|
+
# permanent for the lifespan of the worker.
|
191
|
+
#
|
192
|
+
# Call with a block to register a hook.
|
193
|
+
# Call with no arguments to return all registered hooks.
|
194
|
+
def before_fork(&block)
|
195
|
+
block ? register_hook(:before_fork, block) : hooks(:before_fork)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Register a before_fork proc.
|
199
|
+
def before_fork=(block)
|
200
|
+
register_hook(:before_fork, block)
|
201
|
+
end
|
202
|
+
|
203
|
+
# The `after_fork` hook will be run in the child process and is passed
|
204
|
+
# the current job. Any changes you make, therefore, will only live as
|
205
|
+
# long as the job currently being processed.
|
206
|
+
#
|
207
|
+
# Call with a block to register a hook.
|
208
|
+
# Call with no arguments to return all registered hooks.
|
209
|
+
def after_fork(&block)
|
210
|
+
block ? register_hook(:after_fork, block) : hooks(:after_fork)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Register an after_fork proc.
|
214
|
+
def after_fork=(block)
|
215
|
+
register_hook(:after_fork, block)
|
216
|
+
end
|
217
|
+
|
218
|
+
# The `before_pause` hook will be run in the parent process before the
|
219
|
+
# worker has paused processing (via #pause_processing or SIGUSR2).
|
220
|
+
def before_pause(&block)
|
221
|
+
block ? register_hook(:before_pause, block) : hooks(:before_pause)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Register a before_pause proc.
|
225
|
+
def before_pause=(block)
|
226
|
+
register_hook(:before_pause, block)
|
227
|
+
end
|
228
|
+
|
229
|
+
# The `after_pause` hook will be run in the parent process after the
|
230
|
+
# worker has paused (via SIGCONT).
|
231
|
+
def after_pause(&block)
|
232
|
+
block ? register_hook(:after_pause, block) : hooks(:after_pause)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Register an after_pause proc.
|
236
|
+
def after_pause=(block)
|
237
|
+
register_hook(:after_pause, block)
|
238
|
+
end
|
239
|
+
|
240
|
+
def to_s
|
241
|
+
"Resque Client connected to #{redis_id}"
|
242
|
+
end
|
243
|
+
|
244
|
+
attr_accessor :inline
|
245
|
+
|
246
|
+
# If 'inline' is true Resque will call #perform method inline
|
247
|
+
# without queuing it into Redis and without any Resque callbacks.
|
248
|
+
# The 'inline' is false Resque jobs will be put in queue regularly.
|
249
|
+
alias :inline? :inline
|
250
|
+
|
251
|
+
#
|
252
|
+
# queue manipulation
|
253
|
+
#
|
254
|
+
|
255
|
+
# Pushes a job onto a queue. Queue name should be a string and the
|
256
|
+
# item should be any JSON-able Ruby object.
|
257
|
+
#
|
258
|
+
# Resque works generally expect the `item` to be a hash with the following
|
259
|
+
# keys:
|
260
|
+
#
|
261
|
+
# class - The String name of the job to run.
|
262
|
+
# args - An Array of arguments to pass the job. Usually passed
|
263
|
+
# via `class.to_class.perform(*args)`.
|
264
|
+
#
|
265
|
+
# Example
|
266
|
+
#
|
267
|
+
# Resque.push('archive', :class => 'Archive', :args => [ 35, 'tar' ])
|
268
|
+
#
|
269
|
+
# Returns nothing
|
270
|
+
def push(queue, item)
|
271
|
+
data_store.push_to_queue(queue,encode(item))
|
272
|
+
end
|
273
|
+
|
274
|
+
# Pops a job off a queue. Queue name should be a string.
|
275
|
+
#
|
276
|
+
# Returns a Ruby object.
|
277
|
+
def pop(queue)
|
278
|
+
decode(data_store.pop_from_queue(queue))
|
279
|
+
end
|
280
|
+
|
281
|
+
# Returns an integer representing the size of a queue.
|
282
|
+
# Queue name should be a string.
|
283
|
+
def size(queue)
|
284
|
+
data_store.queue_size(queue)
|
285
|
+
end
|
286
|
+
|
287
|
+
# Returns an array of items currently queued. Queue name should be
|
288
|
+
# a string.
|
289
|
+
#
|
290
|
+
# start and count should be integer and can be used for pagination.
|
291
|
+
# start is the item to begin, count is how many items to return.
|
292
|
+
#
|
293
|
+
# To get the 3rd page of a 30 item, paginatied list one would use:
|
294
|
+
# Resque.peek('my_list', 59, 30)
|
295
|
+
def peek(queue, start = 0, count = 1)
|
296
|
+
results = data_store.peek_in_queue(queue,start,count)
|
297
|
+
if count == 1
|
298
|
+
decode(results)
|
299
|
+
else
|
300
|
+
results.map { |result| decode(result) }
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Does the dirty work of fetching a range of items from a Redis list
|
305
|
+
# and converting them into Ruby objects.
|
306
|
+
def list_range(key, start = 0, count = 1)
|
307
|
+
results = data_store.list_range(key, start, count)
|
308
|
+
if count == 1
|
309
|
+
decode(results)
|
310
|
+
else
|
311
|
+
results.map { |result| decode(result) }
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# Returns an array of all known Resque queues as strings.
|
316
|
+
def queues
|
317
|
+
data_store.queue_names
|
318
|
+
end
|
319
|
+
|
320
|
+
# Given a queue name, completely deletes the queue.
|
321
|
+
def remove_queue(queue)
|
322
|
+
data_store.remove_queue(queue)
|
323
|
+
end
|
324
|
+
|
325
|
+
# Used internally to keep track of which queues we've created.
|
326
|
+
# Don't call this directly.
|
327
|
+
def watch_queue(queue)
|
328
|
+
data_store.watch_queue(queue)
|
329
|
+
end
|
330
|
+
|
331
|
+
|
332
|
+
#
|
333
|
+
# job shortcuts
|
334
|
+
#
|
335
|
+
|
336
|
+
# This method can be used to conveniently add a job to a queue.
|
337
|
+
# It assumes the class you're passing it is a real Ruby class (not
|
338
|
+
# a string or reference) which either:
|
339
|
+
#
|
340
|
+
# a) has a @queue ivar set
|
341
|
+
# b) responds to `queue`
|
342
|
+
#
|
343
|
+
# If either of those conditions are met, it will use the value obtained
|
344
|
+
# from performing one of the above operations to determine the queue.
|
345
|
+
#
|
346
|
+
# If no queue can be inferred this method will raise a `Resque::NoQueueError`
|
347
|
+
#
|
348
|
+
# Returns true if the job was queued, nil if the job was rejected by a
|
349
|
+
# before_enqueue hook.
|
350
|
+
#
|
351
|
+
# This method is considered part of the `stable` API.
|
352
|
+
def enqueue(klass, *args)
|
353
|
+
enqueue_to(queue_from_class(klass), klass, *args)
|
354
|
+
end
|
355
|
+
|
356
|
+
# Just like `enqueue` but allows you to specify the queue you want to
|
357
|
+
# use. Runs hooks.
|
358
|
+
#
|
359
|
+
# `queue` should be the String name of the queue you're targeting.
|
360
|
+
#
|
361
|
+
# Returns true if the job was queued, nil if the job was rejected by a
|
362
|
+
# before_enqueue hook.
|
363
|
+
#
|
364
|
+
# This method is considered part of the `stable` API.
|
365
|
+
def enqueue_to(queue, klass, *args)
|
366
|
+
# Perform before_enqueue hooks. Don't perform enqueue if any hook returns false
|
367
|
+
before_hooks = Plugin.before_enqueue_hooks(klass).collect do |hook|
|
368
|
+
klass.send(hook, *args)
|
369
|
+
end
|
370
|
+
return nil if before_hooks.any? { |result| result == false }
|
371
|
+
|
372
|
+
Job.create(queue, klass, *args)
|
373
|
+
|
374
|
+
Plugin.after_enqueue_hooks(klass).each do |hook|
|
375
|
+
klass.send(hook, *args)
|
376
|
+
end
|
377
|
+
|
378
|
+
return true
|
379
|
+
end
|
380
|
+
|
381
|
+
# This method can be used to conveniently remove a job from a queue.
|
382
|
+
# It assumes the class you're passing it is a real Ruby class (not
|
383
|
+
# a string or reference) which either:
|
384
|
+
#
|
385
|
+
# a) has a @queue ivar set
|
386
|
+
# b) responds to `queue`
|
387
|
+
#
|
388
|
+
# If either of those conditions are met, it will use the value obtained
|
389
|
+
# from performing one of the above operations to determine the queue.
|
390
|
+
#
|
391
|
+
# If no queue can be inferred this method will raise a `Resque::NoQueueError`
|
392
|
+
#
|
393
|
+
# If no args are given, this method will dequeue *all* jobs matching
|
394
|
+
# the provided class. See `Resque::Job.destroy` for more
|
395
|
+
# information.
|
396
|
+
#
|
397
|
+
# Returns the number of jobs destroyed.
|
398
|
+
#
|
399
|
+
# Example:
|
400
|
+
#
|
401
|
+
# # Removes all jobs of class `UpdateNetworkGraph`
|
402
|
+
# Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph)
|
403
|
+
#
|
404
|
+
# # Removes all jobs of class `UpdateNetworkGraph` with matching args.
|
405
|
+
# Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325')
|
406
|
+
#
|
407
|
+
# This method is considered part of the `stable` API.
|
408
|
+
def dequeue(klass, *args)
|
409
|
+
# Perform before_dequeue hooks. Don't perform dequeue if any hook returns false
|
410
|
+
before_hooks = Plugin.before_dequeue_hooks(klass).collect do |hook|
|
411
|
+
klass.send(hook, *args)
|
412
|
+
end
|
413
|
+
return if before_hooks.any? { |result| result == false }
|
414
|
+
|
415
|
+
destroyed = Job.destroy(queue_from_class(klass), klass, *args)
|
416
|
+
|
417
|
+
Plugin.after_dequeue_hooks(klass).each do |hook|
|
418
|
+
klass.send(hook, *args)
|
419
|
+
end
|
420
|
+
|
421
|
+
destroyed
|
422
|
+
end
|
423
|
+
|
424
|
+
# Given a class, try to extrapolate an appropriate queue based on a
|
425
|
+
# class instance variable or `queue` method.
|
426
|
+
def queue_from_class(klass)
|
427
|
+
klass.instance_variable_get(:@queue) ||
|
428
|
+
(klass.respond_to?(:queue) and klass.queue)
|
429
|
+
end
|
430
|
+
|
431
|
+
# This method will return a `Resque::Job` object or a non-true value
|
432
|
+
# depending on whether a job can be obtained. You should pass it the
|
433
|
+
# precise name of a queue: case matters.
|
434
|
+
#
|
435
|
+
# This method is considered part of the `stable` API.
|
436
|
+
def reserve(queue)
|
437
|
+
Job.reserve(queue)
|
438
|
+
end
|
439
|
+
|
440
|
+
# Validates if the given klass could be a valid Resque job
|
441
|
+
#
|
442
|
+
# If no queue can be inferred this method will raise a `Resque::NoQueueError`
|
443
|
+
#
|
444
|
+
# If given klass is nil this method will raise a `Resque::NoClassError`
|
445
|
+
def validate(klass, queue = nil)
|
446
|
+
queue ||= queue_from_class(klass)
|
447
|
+
|
448
|
+
if !queue
|
449
|
+
raise NoQueueError.new("Jobs must be placed onto a queue. No queue could be inferred for class #{klass}")
|
450
|
+
end
|
451
|
+
|
452
|
+
if klass.to_s.empty?
|
453
|
+
raise NoClassError.new("Jobs must be given a class.")
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
|
458
|
+
#
|
459
|
+
# worker shortcuts
|
460
|
+
#
|
461
|
+
|
462
|
+
# A shortcut to Worker.all
|
463
|
+
def workers
|
464
|
+
Worker.all
|
465
|
+
end
|
466
|
+
|
467
|
+
# A shortcut to Worker.working
|
468
|
+
def working
|
469
|
+
Worker.working
|
470
|
+
end
|
471
|
+
|
472
|
+
# A shortcut to unregister_worker
|
473
|
+
# useful for command line tool
|
474
|
+
def remove_worker(worker_id)
|
475
|
+
worker = Resque::Worker.find(worker_id)
|
476
|
+
worker.unregister_worker
|
477
|
+
end
|
478
|
+
|
479
|
+
#
|
480
|
+
# stats
|
481
|
+
#
|
482
|
+
|
483
|
+
# Returns a hash, similar to redis-rb's #info, of interesting stats.
|
484
|
+
def info
|
485
|
+
return {
|
486
|
+
:pending => queue_sizes.inject(0) { |sum, (queue_name, queue_size)| sum + queue_size },
|
487
|
+
:processed => Stat[:processed],
|
488
|
+
:queues => queues.size,
|
489
|
+
:workers => workers.size.to_i,
|
490
|
+
:working => working.size,
|
491
|
+
:failed => data_store.num_failed,
|
492
|
+
:servers => [redis_id],
|
493
|
+
:environment => ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
494
|
+
}
|
495
|
+
end
|
496
|
+
|
497
|
+
# Returns an array of all known Resque keys in Redis. Redis' KEYS operation
|
498
|
+
# is O(N) for the keyspace, so be careful - this can be slow for big databases.
|
499
|
+
def keys
|
500
|
+
data_store.all_resque_keys
|
501
|
+
end
|
502
|
+
|
503
|
+
# Returns a hash, mapping queue names to queue sizes
|
504
|
+
def queue_sizes
|
505
|
+
queue_names = queues
|
506
|
+
|
507
|
+
sizes = redis.pipelined do
|
508
|
+
queue_names.each do |name|
|
509
|
+
redis.llen("queue:#{name}")
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
Hash[queue_names.zip(sizes)]
|
514
|
+
end
|
515
|
+
|
516
|
+
# Returns a hash, mapping queue names to (up to `sample_size`) samples of jobs in that queue
|
517
|
+
def sample_queues(sample_size = 1000)
|
518
|
+
queue_names = queues
|
519
|
+
|
520
|
+
samples = redis.pipelined do
|
521
|
+
queue_names.each do |name|
|
522
|
+
key = "queue:#{name}"
|
523
|
+
redis.llen(key)
|
524
|
+
redis.lrange(key, 0, sample_size - 1)
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
hash = {}
|
529
|
+
|
530
|
+
queue_names.zip(samples.each_slice(2).to_a) do |queue_name, (queue_size, serialized_samples)|
|
531
|
+
samples = serialized_samples.map do |serialized_sample|
|
532
|
+
Job.decode(serialized_sample)
|
533
|
+
end
|
534
|
+
|
535
|
+
hash[queue_name] = {
|
536
|
+
:size => queue_size,
|
537
|
+
:samples => samples
|
538
|
+
}
|
539
|
+
end
|
540
|
+
|
541
|
+
hash
|
542
|
+
end
|
543
|
+
|
544
|
+
private
|
545
|
+
|
546
|
+
# Register a new proc as a hook. If the block is nil this is the
|
547
|
+
# equivalent of removing all hooks of the given name.
|
548
|
+
#
|
549
|
+
# `name` is the hook that the block should be registered with.
|
550
|
+
def register_hook(name, block)
|
551
|
+
return clear_hooks(name) if block.nil?
|
552
|
+
|
553
|
+
@hooks ||= {}
|
554
|
+
@hooks[name] ||= []
|
555
|
+
|
556
|
+
block = Array(block)
|
557
|
+
@hooks[name].concat(block)
|
558
|
+
end
|
559
|
+
|
560
|
+
# Clear all hooks given a hook name.
|
561
|
+
def clear_hooks(name)
|
562
|
+
@hooks && @hooks[name] = []
|
563
|
+
end
|
564
|
+
|
565
|
+
# Retrieve all hooks
|
566
|
+
def hooks
|
567
|
+
@hooks || {}
|
568
|
+
end
|
569
|
+
|
570
|
+
# Retrieve all hooks of a given name.
|
571
|
+
def hooks(name)
|
572
|
+
(@hooks && @hooks[name]) || []
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
# Log to STDOUT by default
|
577
|
+
Resque.logger = MonoLogger.new(STDOUT)
|
578
|
+
Resque.logger.formatter = Resque::QuietFormatter.new
|