resqueue 1.0.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/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
|