nfo-resque-mongo 1.15.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.
Files changed (55) hide show
  1. data/HISTORY.md +259 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +828 -0
  4. data/Rakefile +73 -0
  5. data/bin/resque +75 -0
  6. data/bin/resque-web +23 -0
  7. data/lib/resque/errors.rb +10 -0
  8. data/lib/resque/failure/base.rb +74 -0
  9. data/lib/resque/failure/hoptoad.rb +139 -0
  10. data/lib/resque/failure/mongo.rb +92 -0
  11. data/lib/resque/failure/multiple.rb +60 -0
  12. data/lib/resque/failure.rb +82 -0
  13. data/lib/resque/helpers.rb +79 -0
  14. data/lib/resque/job.rb +228 -0
  15. data/lib/resque/plugin.rb +51 -0
  16. data/lib/resque/queue_stats.rb +58 -0
  17. data/lib/resque/server/public/idle.png +0 -0
  18. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  19. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  20. data/lib/resque/server/public/poll.png +0 -0
  21. data/lib/resque/server/public/ranger.js +73 -0
  22. data/lib/resque/server/public/reset.css +48 -0
  23. data/lib/resque/server/public/style.css +86 -0
  24. data/lib/resque/server/public/working.png +0 -0
  25. data/lib/resque/server/test_helper.rb +19 -0
  26. data/lib/resque/server/views/error.erb +1 -0
  27. data/lib/resque/server/views/failed.erb +75 -0
  28. data/lib/resque/server/views/key_sets.erb +19 -0
  29. data/lib/resque/server/views/key_string.erb +11 -0
  30. data/lib/resque/server/views/layout.erb +38 -0
  31. data/lib/resque/server/views/next_more.erb +19 -0
  32. data/lib/resque/server/views/overview.erb +4 -0
  33. data/lib/resque/server/views/queues.erb +49 -0
  34. data/lib/resque/server/views/stats.erb +62 -0
  35. data/lib/resque/server/views/workers.erb +109 -0
  36. data/lib/resque/server/views/working.erb +68 -0
  37. data/lib/resque/server.rb +222 -0
  38. data/lib/resque/stat.rb +55 -0
  39. data/lib/resque/tasks.rb +42 -0
  40. data/lib/resque/version.rb +3 -0
  41. data/lib/resque/worker.rb +524 -0
  42. data/lib/resque.rb +384 -0
  43. data/tasks/redis.rake +161 -0
  44. data/tasks/resque.rake +2 -0
  45. data/test/dump.rdb +0 -0
  46. data/test/job_hooks_test.rb +323 -0
  47. data/test/job_plugins_test.rb +230 -0
  48. data/test/plugin_test.rb +116 -0
  49. data/test/queue_stats_test.rb +57 -0
  50. data/test/redis-test.conf +115 -0
  51. data/test/resque-web_test.rb +48 -0
  52. data/test/resque_test.rb +256 -0
  53. data/test/test_helper.rb +151 -0
  54. data/test/worker_test.rb +356 -0
  55. metadata +166 -0
data/lib/resque.rb ADDED
@@ -0,0 +1,384 @@
1
+ require 'mongo'
2
+
3
+ begin
4
+ require 'yajl'
5
+ rescue LoadError
6
+ require 'json'
7
+ end
8
+
9
+ require 'resque/version'
10
+
11
+ require 'resque/errors'
12
+
13
+ require 'resque/failure'
14
+ require 'resque/failure/base'
15
+
16
+ require 'resque/helpers'
17
+ require 'resque/stat'
18
+ require 'resque/job'
19
+ require 'resque/worker'
20
+ require 'resque/plugin'
21
+ require 'resque/queue_stats'
22
+
23
+ module Resque
24
+ include Helpers
25
+ extend self
26
+
27
+ attr_accessor :verbose
28
+ attr_accessor :very_verbose
29
+
30
+ # Accepts 'hostname' or 'hostname:port' or 'hostname:port/db' strings
31
+ # or a Mongo::DB object.
32
+ def mongo=(server)
33
+ @verbose = ENV['LOGGING']||ENV['VERBOSE']
34
+ @very_verbose = ENV['VVERBOSE']
35
+
36
+ @con.close if @con
37
+
38
+ case server
39
+ when String
40
+ match = server.match(/([^:]+):?(\d*)\/?(\w*)/) # http://rubular.com/r/G6O8qe0DJ5
41
+ host = match[1]
42
+ port = match[2].nil? || match[2] == '' ? '27017' : match[2]
43
+ db = match[3].nil? || match[3] == '' ? 'monque' : match[3]
44
+
45
+ log "Initializing connection to #{host}:#{port}"
46
+ @con = Mongo::Connection.new(host, port)
47
+ @db = @con.db(db)
48
+ when Mongo::DB
49
+ @con = server.connection
50
+ @db = server
51
+ else
52
+ raise "I don't know what to do with #{server.inspect}" unless server.is_a?(String) || server.is_a?(Mongo::Connection)
53
+ end
54
+
55
+ @mongo = @db.collection('monque')
56
+ @workers = @db.collection('workers')
57
+ @failures = @db.collection('failures')
58
+ @stats = @db.collection('stats')
59
+ @queues = @db.collection('queues')
60
+ log "Creating/updating indexes"
61
+ add_indexes
62
+ end
63
+
64
+
65
+ # Returns the current Mongo connection. If none has been created, will
66
+ # create a new one.
67
+ def mongo
68
+ return @mongo if @mongo
69
+ self.mongo = ENV['MONGO']||'localhost:27017'
70
+ self.mongo
71
+ end
72
+
73
+ def mongo_workers
74
+ return @workers if @workers
75
+ self.mongo = ENV['MONGO']||'localhost:27017'
76
+ @workers
77
+ end
78
+
79
+ def mongo_failures
80
+ return @failures if @failures
81
+ self.mongo = ENV['MONGO']||'localhost:27017'
82
+ @failures
83
+ end
84
+
85
+ def mongo_stats
86
+ return @stats if @stats
87
+ self.mongo = ENV['MONGO']||'localhost:27017'
88
+ @stats
89
+ end
90
+
91
+ def mongo_queues
92
+ return @queues if @queues
93
+ self.mongo = ENV['MONGO']||'localhost:27017'
94
+ @queues
95
+ end
96
+
97
+ # The `before_first_fork` hook will be run in the **parent** process
98
+ # only once, before forking to run the first job. Be careful- any
99
+ # changes you make will be permanent for the lifespan of the
100
+ # worker.
101
+ #
102
+ # Call with a block to set the hook.
103
+ # Call with no arguments to return the hook.
104
+ def before_first_fork(&block)
105
+ block ? (@before_first_fork = block) : @before_first_fork
106
+ end
107
+
108
+ # Set a proc that will be called in the parent process before the
109
+ # worker forks for the first time.
110
+ def before_first_fork=(before_first_fork)
111
+ @before_first_fork = before_first_fork
112
+ end
113
+
114
+ # The `before_fork` hook will be run in the **parent** process
115
+ # before every job, so be careful- any changes you make will be
116
+ # permanent for the lifespan of the worker.
117
+ #
118
+ # Call with a block to set the hook.
119
+ # Call with no arguments to return the hook.
120
+ def before_fork(&block)
121
+ block ? (@before_fork = block) : @before_fork
122
+ end
123
+
124
+ # Set the before_fork proc.
125
+ def before_fork=(before_fork)
126
+ @before_fork = before_fork
127
+ end
128
+
129
+ # The `after_fork` hook will be run in the child process and is passed
130
+ # the current job. Any changes you make, therefore, will only live as
131
+ # long as the job currently being processed.
132
+ #
133
+ # Call with a block to set the hook.
134
+ # Call with no arguments to return the hook.
135
+ def after_fork(&block)
136
+ block ? (@after_fork = block) : @after_fork
137
+ end
138
+
139
+ # Set the after_fork proc.
140
+ def after_fork=(after_fork)
141
+ @after_fork = after_fork
142
+ end
143
+
144
+ def to_s
145
+ "Resque Client connected to #{@con.primary[0]}:#{@con.primary[1]}/#{@db.name}/#{@mongo.name}"
146
+ end
147
+
148
+
149
+ def add_indexes
150
+ @mongo.create_index([[:queue,1],[:date, 1]])
151
+ @mongo.create_index :queue
152
+ @workers.create_index :worker
153
+ @stats.create_index :stat
154
+ @queues.create_index(:queue,:unique => 1)
155
+ @failures.create_index :queue
156
+ end
157
+
158
+ # If 'inline' is true Resque will call #perform method inline
159
+ # without queuing it into Redis and without any Resque callbacks.
160
+ # The 'inline' is false Resque jobs will be put in queue regularly.
161
+ def inline?
162
+ @inline
163
+ end
164
+ alias_method :inline, :inline?
165
+
166
+ def inline=(inline)
167
+ @inline = inline
168
+ end
169
+
170
+ #
171
+ # queue manipulation
172
+ #
173
+
174
+ # Pushes a job onto a queue. Queue name should be a string and the
175
+ # item should be any JSON-able Ruby object.
176
+ def push(queue, item)
177
+ watch_queue(queue)
178
+ mongo << { :queue => queue.to_s, :item => item , :date => Time.now }
179
+ QueueStats.add_job(queue)
180
+ end
181
+
182
+ # Pops a job off a queue. Queue name should be a string.
183
+ #
184
+ # Returns a Ruby object.
185
+ def pop(queue)
186
+ doc = mongo.find_and_modify( :query => { :queue => queue.to_s },
187
+ :sort => [[:date, 1]],
188
+ :remove => true )
189
+ QueueStats.remove_job(queue)
190
+ doc['item']
191
+ rescue Mongo::OperationFailure => e
192
+ return nil if e.message =~ /No matching object/
193
+ raise e
194
+ end
195
+
196
+ # Returns an integer representing the size of a queue.
197
+ # Queue name should be a string.
198
+ def size(queue)
199
+ queue_stats = QueueStats.new(queue)
200
+ queue_stats.size
201
+ end
202
+
203
+ # Returns an array of items currently queued. Queue name should be
204
+ # a string.
205
+ #
206
+ # start and count should be integer and can be used for pagination.
207
+ # start is the item to begin, count is how many items to return.
208
+ #
209
+ # To get the 3rd page of a 30 item, paginatied list one would use:
210
+ # Resque.peek('my_list', 59, 30)
211
+ def peek(queue, start = 0, count = 1)
212
+ start, count = [start, count].map { |n| Integer(n) }
213
+ res = mongo.find(:queue => queue).sort([:date, 1]).skip(start).limit(count).to_a
214
+ res.collect! { |doc| doc['item'] }
215
+ count == 1 ? res.first : res
216
+ end
217
+
218
+ # Returns an array of all known Resque queues as strings,
219
+ # filtered by the given names or prefixes.
220
+ def queues(names = nil)
221
+ QueueStats.list(names)
222
+ end
223
+
224
+ # Given a queue name, completely deletes the queue.
225
+ def remove_queue(queue)
226
+ log "removing #{queue}"
227
+ mongo.remove({:queue => queue.to_s})
228
+ QueueStats.remove(queue)
229
+ end
230
+
231
+ # Used internally to keep track of which queues we've created.
232
+ # Don't call this directly.
233
+ def watch_queue(queue)
234
+ # redis.sadd(:queues, queue.to_s)
235
+ end
236
+
237
+
238
+ #
239
+ # job shortcuts
240
+ #
241
+
242
+ # This method can be used to conveniently add a job to a queue.
243
+ # It assumes the class you're passing it is a real Ruby class (not
244
+ # a string or reference) which either:
245
+ #
246
+ # a) has a @queue ivar set
247
+ # b) responds to `queue`
248
+ #
249
+ # If either of those conditions are met, it will use the value obtained
250
+ # from performing one of the above operations to determine the queue.
251
+ #
252
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
253
+ #
254
+ # This method is considered part of the `stable` API.
255
+ def enqueue(klass, *args)
256
+ Job.create(queue_from_class(klass), klass, *args)
257
+
258
+ Plugin.after_enqueue_hooks(klass).each do |hook|
259
+ klass.send(hook, *args)
260
+ end
261
+ end
262
+
263
+ # This method can be used to conveniently remove a job from a queue.
264
+ # It assumes the class you're passing it is a real Ruby class (not
265
+ # a string or reference) which either:
266
+ #
267
+ # a) has a @queue ivar set
268
+ # b) responds to `queue`
269
+ #
270
+ # If either of those conditions are met, it will use the value obtained
271
+ # from performing one of the above operations to determine the queue.
272
+ #
273
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
274
+ #
275
+ # If no args are given, this method will dequeue *all* jobs matching
276
+ # the provided class. See `Resque::Job.destroy` for more
277
+ # information.
278
+ #
279
+ # Returns the number of jobs destroyed.
280
+ #
281
+ # Example:
282
+ #
283
+ # # Removes all jobs of class `UpdateNetworkGraph`
284
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph)
285
+ #
286
+ # # Removes all jobs of class `UpdateNetworkGraph` with matching args.
287
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325')
288
+ #
289
+ # This method is considered part of the `stable` API.
290
+ def dequeue(klass, *args)
291
+ Job.destroy(queue_from_class(klass), klass, *args)
292
+ end
293
+
294
+ # Given a class, try to extrapolate an appropriate queue based on a
295
+ # class instance variable or `queue` method.
296
+ def queue_from_class(klass)
297
+ klass.instance_variable_get(:@queue) ||
298
+ (klass.respond_to?(:queue) and klass.queue)
299
+ end
300
+
301
+ # This method will return a `Resque::Job` object or a non-true value
302
+ # depending on whether a job can be obtained. You should pass it the
303
+ # precise name of a queue: case matters.
304
+ #
305
+ # This method is considered part of the `stable` API.
306
+ def reserve(queue)
307
+ Job.reserve(queue)
308
+ end
309
+
310
+ # Validates if the given klass could be a valid Resque job
311
+ #
312
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
313
+ #
314
+ # If given klass is nil this method will raise a `Resque::NoClassError`
315
+ def validate(klass, queue = nil)
316
+ queue ||= queue_from_class(klass)
317
+
318
+ if !queue
319
+ raise NoQueueError.new("Jobs must be placed onto a queue.")
320
+ end
321
+
322
+ if klass.to_s.empty?
323
+ raise NoClassError.new("Jobs must be given a class.")
324
+ end
325
+ end
326
+
327
+
328
+ #
329
+ # worker shortcuts
330
+ #
331
+
332
+ # A shortcut to Worker.all
333
+ def workers
334
+ Worker.all
335
+ end
336
+
337
+ # A shortcut to Worker.working
338
+ def working
339
+ Worker.working
340
+ end
341
+
342
+ # A shortcut to unregister_worker
343
+ # useful for command line tool
344
+ def remove_worker(worker_id)
345
+ worker = Resque::Worker.find(worker_id)
346
+ worker.unregister_worker
347
+ end
348
+
349
+ #
350
+ # stats
351
+ #
352
+
353
+ # Returns a hash, similar to redis-rb's #info, of interesting stats.
354
+ def info
355
+ return {
356
+ :pending => queues.inject(0) { |m,k| m + size(k) },
357
+ :processed => Stat[:processed],
358
+ :queues => queues.size,
359
+ :workers => workers.size.to_i,
360
+ :working => working.size,
361
+ :failed => Stat[:failed],
362
+ :servers => "#{@con.primary[0]}:#{@con.primary[1]}/#{@db.name}/#{@mongo.name}",
363
+ :environment => ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development',
364
+ }
365
+ end
366
+
367
+ # Returns an array of all known Resque keys in Redis. Redis' KEYS operation
368
+ # is O(N) for the keyspace, so be careful - this can be slow for big databases.
369
+ def keys
370
+ queues
371
+ end
372
+
373
+
374
+ # Log a message to STDOUT if we are verbose or very_verbose.
375
+ def log(message)
376
+ if verbose
377
+ puts "*** #{message}"
378
+ elsif very_verbose
379
+ time = Time.now.strftime('%I:%M:%S %Y-%m-%d')
380
+ puts "** [#{time}] #$$: #{message}"
381
+ end
382
+ end
383
+
384
+ end
data/tasks/redis.rake ADDED
@@ -0,0 +1,161 @@
1
+ # Inspired by rabbitmq.rake the Redbox project at http://github.com/rick/redbox/tree/master
2
+ require 'fileutils'
3
+ require 'open-uri'
4
+ require 'pathname'
5
+
6
+ class RedisRunner
7
+ def self.redis_dir
8
+ @redis_dir ||= if ENV['PREFIX']
9
+ Pathname.new(ENV['PREFIX'])
10
+ else
11
+ Pathname.new(`which redis-server`) + '..' + '..'
12
+ end
13
+ end
14
+
15
+ def self.bin_dir
16
+ redis_dir + 'bin'
17
+ end
18
+
19
+ def self.config
20
+ @config ||= if File.exists?(redis_dir + 'etc/redis.conf')
21
+ redis_dir + 'etc/redis.conf'
22
+ else
23
+ redis_dir + '../etc/redis.conf'
24
+ end
25
+ end
26
+
27
+ def self.dtach_socket
28
+ '/tmp/redis.dtach'
29
+ end
30
+
31
+ # Just check for existance of dtach socket
32
+ def self.running?
33
+ File.exists? dtach_socket
34
+ end
35
+
36
+ def self.start
37
+ puts 'Detach with Ctrl+\ Re-attach with rake redis:attach'
38
+ sleep 1
39
+ command = "#{bin_dir}/dtach -A #{dtach_socket} #{bin_dir}/redis-server #{config}"
40
+ sh command
41
+ end
42
+
43
+ def self.attach
44
+ exec "#{bin_dir}/dtach -a #{dtach_socket}"
45
+ end
46
+
47
+ def self.stop
48
+ sh 'echo "SHUTDOWN" | nc localhost 6379'
49
+ end
50
+ end
51
+
52
+ INSTALL_DIR = ENV['INSTALL_DIR'] || '/tmp/redis'
53
+
54
+ namespace :redis do
55
+ desc 'About redis'
56
+ task :about do
57
+ puts "\nSee http://code.google.com/p/redis/ for information about redis.\n\n"
58
+ end
59
+
60
+ desc 'Start redis'
61
+ task :start do
62
+ RedisRunner.start
63
+ end
64
+
65
+ desc 'Stop redis'
66
+ task :stop do
67
+ RedisRunner.stop
68
+ end
69
+
70
+ desc 'Restart redis'
71
+ task :restart do
72
+ RedisRunner.stop
73
+ RedisRunner.start
74
+ end
75
+
76
+ desc 'Attach to redis dtach socket'
77
+ task :attach do
78
+ RedisRunner.attach
79
+ end
80
+
81
+ desc <<-DOC
82
+ Install the latest verison of Redis from Github (requires git, duh).
83
+ Use INSTALL_DIR env var like "rake redis:install INSTALL_DIR=~/tmp"
84
+ in order to get an alternate location for your install files.
85
+ DOC
86
+
87
+ task :install => [:about, :download, :make] do
88
+ bin_dir = '/usr/bin'
89
+ conf_dir = '/etc'
90
+
91
+ if ENV['PREFIX']
92
+ bin_dir = "#{ENV['PREFIX']}/bin"
93
+ sh "mkdir -p #{bin_dir}" unless File.exists?("#{bin_dir}")
94
+
95
+ conf_dir = "#{ENV['PREFIX']}/etc"
96
+ sh "mkdir -p #{conf_dir}" unless File.exists?("#{conf_dir}")
97
+ end
98
+
99
+ %w(redis-benchmark redis-cli redis-server).each do |bin|
100
+ sh "cp #{INSTALL_DIR}/src/#{bin} #{bin_dir}"
101
+ end
102
+
103
+ puts "Installed redis-benchmark, redis-cli and redis-server to #{bin_dir}"
104
+
105
+ unless File.exists?("#{conf_dir}/redis.conf")
106
+ sh "cp #{INSTALL_DIR}/redis.conf #{conf_dir}/redis.conf"
107
+ puts "Installed redis.conf to #{conf_dir} \n You should look at this file!"
108
+ end
109
+ end
110
+
111
+ task :make do
112
+ sh "cd #{INSTALL_DIR}/src && make clean"
113
+ sh "cd #{INSTALL_DIR}/src && make"
114
+ end
115
+
116
+ desc "Download package"
117
+ task :download do
118
+ sh "rm -rf #{INSTALL_DIR}/" if File.exists?("#{INSTALL_DIR}/.svn")
119
+ sh "git clone git://github.com/antirez/redis.git #{INSTALL_DIR}" unless File.exists?(INSTALL_DIR)
120
+ sh "cd #{INSTALL_DIR} && git pull" if File.exists?("#{INSTALL_DIR}/.git")
121
+ end
122
+ end
123
+
124
+ namespace :dtach do
125
+ desc 'About dtach'
126
+ task :about do
127
+ puts "\nSee http://dtach.sourceforge.net/ for information about dtach.\n\n"
128
+ end
129
+
130
+ desc 'Install dtach 0.8 from source'
131
+ task :install => [:about, :download, :make] do
132
+
133
+ bin_dir = "/usr/bin"
134
+
135
+
136
+ if ENV['PREFIX']
137
+ bin_dir = "#{ENV['PREFIX']}/bin"
138
+ sh "mkdir -p #{bin_dir}" unless File.exists?("#{bin_dir}")
139
+ end
140
+
141
+ sh "cp #{INSTALL_DIR}/dtach-0.8/dtach #{bin_dir}"
142
+ end
143
+
144
+ task :make do
145
+ sh "cd #{INSTALL_DIR}/dtach-0.8/ && ./configure && make"
146
+ end
147
+
148
+ desc "Download package"
149
+ task :download do
150
+ unless File.exists?("#{INSTALL_DIR}/dtach-0.8.tar.gz")
151
+ require 'net/http'
152
+
153
+ url = 'http://downloads.sourceforge.net/project/dtach/dtach/0.8/dtach-0.8.tar.gz'
154
+ open("#{INSTALL_DIR}/dtach-0.8.tar.gz", 'wb') do |file| file.write(open(url).read) end
155
+ end
156
+
157
+ unless File.directory?("#{INSTALL_DIR}/dtach-0.8")
158
+ sh "cd #{INSTALL_DIR} && tar xzf dtach-0.8.tar.gz"
159
+ end
160
+ end
161
+ end