diff options
Diffstat (limited to 'samples/Ruby/resque.rb')
| -rw-r--r-- | samples/Ruby/resque.rb | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/samples/Ruby/resque.rb b/samples/Ruby/resque.rb new file mode 100644 index 0000000..1e36524 --- /dev/null +++ b/samples/Ruby/resque.rb @@ -0,0 +1,385 @@ +require 'redis/namespace' + +require 'resque/version' + +require 'resque/errors' + +require 'resque/failure' +require 'resque/failure/base' + +require 'resque/helpers' +require 'resque/stat' +require 'resque/job' +require 'resque/worker' +require 'resque/plugin' +require 'resque/queue' +require 'resque/multi_queue' +require 'resque/coder' +require 'resque/multi_json_coder' + +module Resque + include Helpers + extend self + + # Accepts: + # 1. A 'hostname:port' String + # 2. A 'hostname:port:db' String (to select the Redis db) + # 3. A 'hostname:port/namespace' String (to set the Redis namespace) + # 4. A Redis URL String 'redis://host:port' + # 5. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`, + # or `Redis::Namespace`. + def redis=(server) + case server + when String + if server =~ /redis\:\/\// + redis = Redis.connect(:url => server, :thread_safe => true) + else + server, namespace = server.split('/', 2) + host, port, db = server.split(':') + redis = Redis.new(:host => host, :port => port, + :thread_safe => true, :db => db) + end + namespace ||= :resque + + @redis = Redis::Namespace.new(namespace, :redis => redis) + when Redis::Namespace + @redis = server + else + @redis = Redis::Namespace.new(:resque, :redis => server) + end + @queues = Hash.new { |h,name| + h[name] = Resque::Queue.new(name, @redis, coder) + } + end + + # Encapsulation of encode/decode. Overwrite this to use it across Resque. + # This defaults to MultiJson for backwards compatibilty. + def coder + @coder ||= MultiJsonCoder.new + end + attr_writer :coder + + # Returns the current Redis connection. If none has been created, will + # create a new one. + def redis + return @redis if @redis + self.redis = Redis.respond_to?(:connect) ? Redis.connect : "localhost:6379" + self.redis + end + + def redis_id + # support 1.x versions of redis-rb + if redis.respond_to?(:server) + redis.server + elsif redis.respond_to?(:nodes) # distributed + redis.nodes.map { |n| n.id }.join(', ') + else + redis.client.id + end + end + + # The `before_first_fork` hook will be run in the **parent** process + # only once, before forking to run the first job. Be careful- any + # changes you make will be permanent for the lifespan of the + # worker. + # + # Call with a block to set the hook. + # Call with no arguments to return the hook. + def before_first_fork(&block) + block ? (@before_first_fork = block) : @before_first_fork + end + + # Set a proc that will be called in the parent process before the + # worker forks for the first time. + attr_writer :before_first_fork + + # The `before_fork` hook will be run in the **parent** process + # before every job, so be careful- any changes you make will be + # permanent for the lifespan of the worker. + # + # Call with a block to set the hook. + # Call with no arguments to return the hook. + def before_fork(&block) + block ? (@before_fork = block) : @before_fork + end + + # Set the before_fork proc. + attr_writer :before_fork + + # The `after_fork` hook will be run in the child process and is passed + # the current job. Any changes you make, therefore, will only live as + # long as the job currently being processed. + # + # Call with a block to set the hook. + # Call with no arguments to return the hook. + def after_fork(&block) + block ? (@after_fork = block) : @after_fork + end + + # Set the after_fork proc. + attr_writer :after_fork + + def to_s + "Resque Client connected to #{redis_id}" + end + + attr_accessor :inline + + # If 'inline' is true Resque will call #perform method inline + # without queuing it into Redis and without any Resque callbacks. + # The 'inline' is false Resque jobs will be put in queue regularly. + alias :inline? :inline + + # + # queue manipulation + # + + # Pushes a job onto a queue. Queue name should be a string and the + # item should be any JSON-able Ruby object. + # + # Resque works generally expect the `item` to be a hash with the following + # keys: + # + # class - The String name of the job to run. + # args - An Array of arguments to pass the job. Usually passed + # via `class.to_class.perform(*args)`. + # + # Example + # + # Resque.push('archive', :class => 'Archive', :args => [ 35, 'tar' ]) + # + # Returns nothing + def push(queue, item) + queue(queue) << item + end + + # Pops a job off a queue. Queue name should be a string. + # + # Returns a Ruby object. + def pop(queue) + begin + queue(queue).pop(true) + rescue ThreadError + nil + end + end + + # Returns an integer representing the size of a queue. + # Queue name should be a string. + def size(queue) + queue(queue).size + end + + # Returns an array of items currently queued. Queue name should be + # a string. + # + # start and count should be integer and can be used for pagination. + # start is the item to begin, count is how many items to return. + # + # To get the 3rd page of a 30 item, paginatied list one would use: + # Resque.peek('my_list', 59, 30) + def peek(queue, start = 0, count = 1) + queue(queue).slice start, count + end + + # Does the dirty work of fetching a range of items from a Redis list + # and converting them into Ruby objects. + def list_range(key, start = 0, count = 1) + if count == 1 + decode redis.lindex(key, start) + else + Array(redis.lrange(key, start, start+count-1)).map do |item| + decode item + end + end + end + + # Returns an array of all known Resque queues as strings. + def queues + Array(redis.smembers(:queues)) + end + + # Given a queue name, completely deletes the queue. + def remove_queue(queue) + queue(queue).destroy + @queues.delete(queue.to_s) + end + + # Return the Resque::Queue object for a given name + def queue(name) + @queues[name.to_s] + end + + + # + # job shortcuts + # + + # This method can be used to conveniently add a job to a queue. + # It assumes the class you're passing it is a real Ruby class (not + # a string or reference) which either: + # + # a) has a @queue ivar set + # b) responds to `queue` + # + # If either of those conditions are met, it will use the value obtained + # from performing one of the above operations to determine the queue. + # + # If no queue can be inferred this method will raise a `Resque::NoQueueError` + # + # Returns true if the job was queued, nil if the job was rejected by a + # before_enqueue hook. + # + # This method is considered part of the `stable` API. + def enqueue(klass, *args) + enqueue_to(queue_from_class(klass), klass, *args) + end + + # Just like `enqueue` but allows you to specify the queue you want to + # use. Runs hooks. + # + # `queue` should be the String name of the queue you're targeting. + # + # Returns true if the job was queued, nil if the job was rejected by a + # before_enqueue hook. + # + # This method is considered part of the `stable` API. + def enqueue_to(queue, klass, *args) + # Perform before_enqueue hooks. Don't perform enqueue if any hook returns false + before_hooks = Plugin.before_enqueue_hooks(klass).collect do |hook| + klass.send(hook, *args) + end + return nil if before_hooks.any? { |result| result == false } + + Job.create(queue, klass, *args) + + Plugin.after_enqueue_hooks(klass).each do |hook| + klass.send(hook, *args) + end + + return true + end + + # This method can be used to conveniently remove a job from a queue. + # It assumes the class you're passing it is a real Ruby class (not + # a string or reference) which either: + # + # a) has a @queue ivar set + # b) responds to `queue` + # + # If either of those conditions are met, it will use the value obtained + # from performing one of the above operations to determine the queue. + # + # If no queue can be inferred this method will raise a `Resque::NoQueueError` + # + # If no args are given, this method will dequeue *all* jobs matching + # the provided class. See `Resque::Job.destroy` for more + # information. + # + # Returns the number of jobs destroyed. + # + # Example: + # + # # Removes all jobs of class `UpdateNetworkGraph` + # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph) + # + # # Removes all jobs of class `UpdateNetworkGraph` with matching args. + # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325') + # + # This method is considered part of the `stable` API. + def dequeue(klass, *args) + # Perform before_dequeue hooks. Don't perform dequeue if any hook returns false + before_hooks = Plugin.before_dequeue_hooks(klass).collect do |hook| + klass.send(hook, *args) + end + return if before_hooks.any? { |result| result == false } + + Job.destroy(queue_from_class(klass), klass, *args) + + Plugin.after_dequeue_hooks(klass).each do |hook| + klass.send(hook, *args) + end + end + + # Given a class, try to extrapolate an appropriate queue based on a + # class instance variable or `queue` method. + def queue_from_class(klass) + klass.instance_variable_get(:@queue) || + (klass.respond_to?(:queue) and klass.queue) + end + + # This method will return a `Resque::Job` object or a non-true value + # depending on whether a job can be obtained. You should pass it the + # precise name of a queue: case matters. + # + # This method is considered part of the `stable` API. + def reserve(queue) + Job.reserve(queue) + end + + # Validates if the given klass could be a valid Resque job + # + # If no queue can be inferred this method will raise a `Resque::NoQueueError` + # + # If given klass is nil this method will raise a `Resque::NoClassError` + def validate(klass, queue = nil) + queue ||= queue_from_class(klass) + + if !queue + raise NoQueueError.new("Jobs must be placed onto a queue.") + end + + if klass.to_s.empty? + raise NoClassError.new("Jobs must be given a class.") + end + end + + + # + # worker shortcuts + # + + # A shortcut to Worker.all + def workers + Worker.all + end + + # A shortcut to Worker.working + def working + Worker.working + end + + # A shortcut to unregister_worker + # useful for command line tool + def remove_worker(worker_id) + worker = Resque::Worker.find(worker_id) + worker.unregister_worker + end + + # + # stats + # + + # Returns a hash, similar to redis-rb's #info, of interesting stats. + def info + return { + :pending => queues.inject(0) { |m,k| m + size(k) }, + :processed => Stat[:processed], + :queues => queues.size, + :workers => workers.size.to_i, + :working => working.size, + :failed => Stat[:failed], + :servers => [redis_id], + :environment => ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development' + } + end + + # Returns an array of all known Resque keys in Redis. Redis' KEYS operation + # is O(N) for the keyspace, so be careful - this can be slow for big databases. + def keys + redis.keys("*").map do |key| + key.sub("#{redis.namespace}:", '') + end + end +end + |
