# async-observer - Rails plugin for asynchronous job execution # Copyright (C) 2007 Philotic Inc. # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . require 'beanstalk-client' module Beanstalker; end class Beanstalker::Queue; end class << Beanstalker::Queue DEFAULT_PRI = 512 DEFAULT_FUZZ = 0 DEFAULT_DELAY = 0 DEFAULT_TTR = 120 DEFAULT_TUBE = 'default' attr_accessor :queue, :after_put # This is a fake worker instance for running jobs synchronously. def sync_worker() require 'beanstalker/worker' @sync_worker ||= Beanstalker::Worker.new(binding) end # This runs jobs synchronously; it's used when no queue is configured. def sync_run(obj) body = YAML.dump(obj) job = Beanstalk::Job.new(Beanstalker::FakeConn.new(), 0, body) sync_worker.dispatch(job) sync_worker.do_all_work() return 0, '0.0.0.0' end def put!(obj, pri=DEFAULT_PRI, delay=DEFAULT_DELAY, ttr=DEFAULT_TTR, tube=DEFAULT_TUBE) return sync_run(obj) if pri == :direct || !queue queue.connect() queue.use(tube) info = [queue.yput(obj, pri, delay, ttr), queue.last_server] f = Beanstalker::Queue.after_put f.call(*info) if f return info end SUBMIT_OPTS = [:pri, :fuzz, :delay, :ttr, :tube] def put_call!(obj, sel, opts, args=[]) pri = opts.fetch(:pri, DEFAULT_PRI) fuzz = opts.fetch(:fuzz, DEFAULT_FUZZ) delay = opts.fetch(:delay, DEFAULT_DELAY) ttr = opts.fetch(:ttr, DEFAULT_TTR) tube = opts.fetch(:tube, DEFAULT_TUBE) worker_opts = opts.reject{|k,v| SUBMIT_OPTS.include?(k)} interpolator = opts.fetch(:interpolator, nil) pri = pri + rand(fuzz + 1) if pri != :direct if interpolator code = packed = interpolator else code = gen(obj, sel, args) packed = pkg(code, worker_opts) end RAILS_DEFAULT_LOGGER.info("put #{pri} #{code} to #{tube}") put!(packed, pri, delay, ttr, tube) end def pkg(code, opts) opts.merge(:type => :rails, :code => code) end # Be careful not to pass in a selector that's not valid ruby source. def gen(obj, selector, args) obj.rrepr + '.' + selector.to_s + '(' + gen_args(args) + ')' end def gen_args(args) args.rrepr[1...-1] end end class Beanstalker::FakeConn def delete(x) end def release(x, y, z) end def bury(x, y) end def addr '' end def job_stats(id) { 'id' => id, 'state' => 'reserved', 'age' => 0, 'delay' => 0, 'time-left' => 5000, 'timeouts' => 0, 'releases' => 0, 'buries' => 0, 'kicks' => 0, } end end # This is commented out to workaround (what we think is) a ruby bug in method # lookup. Somehow the methods defined here are being used instead of ones in # ActiveRecord::Base. #class Object # def rrepr() # raise ArgumentError.new('no consistent external repr for ' + self.inspect) # end #end class Symbol def rrepr() inspect end end class Module def rrepr() name end end class NilClass def rrepr() inspect end end class FalseClass def rrepr() inspect end end class TrueClass def rrepr() inspect end end class Numeric def rrepr() inspect end end class String def rrepr() inspect end end class Array def rrepr() '[' + map(&:rrepr).join(', ') + ']' end end class Hash def rrepr() '{' + map{|k,v| k.rrepr + '=>' + v.rrepr}.join(', ') + '}' end end class Range def rrepr() "(#{first.rrepr}#{exclude_end? ? '...' : '..'}#{last.rrepr})" end end class Time def rrepr() "Time.parse('#{self.inspect}')" end end class Date def rrepr() "Date.parse('#{self.inspect}')" end end module Beanstalker::Extensions def rrepr() method = (respond_to? :get_cache) ? 'get_cache' : 'find' "#{self.class.rrepr}.#{method}(#{id.rrepr})" end end