Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ono committed Nov 12, 2010
0 parents commit bdc4c46
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
.DS_Store
20 changes: 20 additions & 0 deletions LICENSE
@@ -0,0 +1,20 @@
Copyright (c) 2010 Tatsuya Ono

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Empty file added README.markdown
Empty file.
166 changes: 166 additions & 0 deletions lib/resque-cleaner.rb
@@ -0,0 +1,166 @@
module Resque
module Plugins
# ResqueCleaner class provides useful functionalities to retry or clean
# failed jobs.
class ResqueCleaner
# ResqueCleaner fetches all elements from Redis once and checks them
# by linear when filtering. Since there is a performance concern,
# ResqueCleaner only targets the latest x(default 1000) jobs.
# You can change the value. e.g. cleaner.limiter.maximum = 2000
attr_reader :limiter

# Set false if you don't show any message.
attr_accessor :print_message

# Initializes instance
def initialize
@failure = Resque::Failure.backend
@print_message = true
@limiter = Limiter.new
end

# Returns redis instance.
def redis
Resque.redis
end

# Returns failure backend. Only supports redis backend.
def failure
@failure
end

# Outputs summary of failure jobs by date.
def summary_by_date
jobs = @limiter.jobs
summary = {}
jobs.each do |job|
date = job["failed_at"][0,10]
summary[date] ||= 0
summary[date] += 1
end if jobs

if print?
log too_many_message if @limiter.on?
summary.keys.sort.each do |k|
log "%s: %4d" % [k,summary[k]]
end
log "%10s: %4d" % ["total", @limiter.count]
end
summary
end

def summary_by_class
end

# Returns every jobs for which block evaluates to true.
def select(&block)
jobs = @limiter.jobs
@limiter.jobs.select &block if jobs
end

# Clears every jobs for which block evaluates to true.
def clear(&block)

end

# Retries every jobs for which block evaluates to true.
def retry(&block)
end

# Retries and clears every jobs for which block evaluates to true.
def retry_and_clear(&block)
end

# Clears all jobs except the last x jobs
def clear_stale
return unless @limiter.on?
redis.ltrim(:failed, -@limiter.maximum, -1)
end

# Returns
def proc(&block)
FilterProc.new(&block)
end

# Provides typical proc you can filter jobs.
class FilterProc < Proc
def retried
FilterProc.new {|job| self.call(job) && job['retried_at'].blank?}
end

def before(time)
time = Time.parse(time) if time.is_a?(String)
FilterProc.new {|job| self.call(job) && Time.parse(job['failed_at']) <= time}
end

def after(time)
time = Time.parse(time) if time.is_a?(String)
FilterProc.new {|job| self.call(job) && Time.parse(job['failed_at']) >= time}
end

def klass(klass_or_name)
FilterProc.new {|job| self.call(job) && job["payload"]["class"] == klass_or_name.to_s}
end

def queue(queue)
FilterProc.new {|job| self.call(job) && job["queue"] == queue.to_s}
end

def self.new(&block)
if block
super
else
super {|job| true}
end
end
end

# Through the Limiter class, you accesses only the last x(default 1000)
# jobs.
class Limiter
DEFAULT_MAX_JOBS = 1000
attr_accessor :maximum
def initialize(cleaner)
@cleaner = cleaner
@maximum = DEFAULT_MAX_JOBS
end

# Returns true if limiter is ON: number of failed jobs is more than
# maximum value.
def on?
@cleaner.failure.count > @maximum
end

def count
on? ? @maximum : @cleaner.failure.count
end

def jobs
jobs = @cleaner.failure.all( - count, count)
jobs.is_a?(Array) ? jobs : [jobs] if jobs
end
end

# Outputs message. Overrides this method when you want to change a output
# stream.
def log(msg)
puts msg if print?
end

def print?
@print_message
end

def too_many_message
"There are too many failed jobs(count=#{@failure.count}). This only looks into last #{@limiter.maximum} jobs."
end
end
end
end

require 'pp'

h = ResqueFailureHelper.new



119 changes: 119 additions & 0 deletions test/test_helper.rb
@@ -0,0 +1,119 @@
# Mostly copied from Resque in order to have similar test environment.
# https://github.com/defunkt/resque/blob/master/test/test_helper.rb

dir = File.dirname(File.expand_path(__FILE__))
$LOAD_PATH.unshift dir + '/../lib'
$TESTING = true
require 'test/unit'
require 'rubygems'
require 'resque'

begin
require 'leftright'
rescue LoadError
end


#
# make sure we can run redis
#

if !system("which redis-server")
puts '', "** can't find `redis-server` in your path"
puts "** try running `sudo rake install`"
abort ''
end


#
# start our own redis when the tests start,
# kill it when they end
#

at_exit do
next if $!

if defined?(MiniTest)
exit_code = MiniTest::Unit.new.run(ARGV)
else
exit_code = Test::Unit::AutoRunner.run
end

pid = `ps -A -o pid,command | grep [r]edis-test`.split(" ")[0]
puts "Killing test redis server..."
`rm -f #{dir}/dump.rdb`
Process.kill("KILL", pid.to_i)
exit exit_code
end

puts "Starting redis for testing at localhost:9736..."
`redis-server #{dir}/redis-test.conf`
Resque.redis = 'localhost:9736'


##
# test/spec/mini 3
# http://gist.github.com/25455
# chris@ozmm.org
#
def context(*args, &block)
return super unless (name = args.first) && block
require 'test/unit'
klass = Class.new(defined?(ActiveSupport::TestCase) ? ActiveSupport::TestCase : Test::Unit::TestCase) do
def self.test(name, &block)
define_method("test_#{name.gsub(/\W/,'_')}", &block) if block
end
def self.xtest(*args) end
def self.setup(&block) define_method(:setup, &block) end
def self.teardown(&block) define_method(:teardown, &block) end
end
(class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
klass.class_eval &block
end

##
# Helper to perform job classes
#
module PerformJob
def perform_job(klass, *args)
resque_job = Resque::Job.new(:testqueue, 'class' => klass, 'args' => args)
resque_job.perform
end
end

#
# fixture classes
#

class SomeJob
def self.perform(repo_id, path)
end
end

class SomeIvarJob < SomeJob
@queue = :ivar
end

class SomeMethodJob < SomeJob
def self.queue
:method
end
end

class BadJob
def self.perform
raise "Bad job!"
end
end

class GoodJob
def self.perform(name)
"Good job, #{name}"
end
end

class BadJobWithSyntaxError
def self.perform
raise SyntaxError, "Extra Bad job!"
end
end

0 comments on commit bdc4c46

Please sign in to comment.