1
0
Fork 0
mirror of https://github.com/mperham/sidekiq.git synced 2022-11-09 13:52:34 -05:00

Manage the retrying/deleting of jobs in the Web UI through the 'jid'

rather than 'score' so as to avoid accidentally performing actions on
multiple jobs when only one was intended.
This commit is contained in:
Brandon Hilkert 2012-11-26 11:22:48 -05:00
parent 166edb8f68
commit cbfa33cedc
8 changed files with 149 additions and 138 deletions

View file

@ -101,11 +101,24 @@ module Sidekiq
end end
def at def at
Time.at(@score) Time.at(score)
end end
def delete def delete
@parent.delete(@score) @parent.delete(score)
end
def retry
raise "Retry not available on jobs not in the Retry queue." unless item["failed_at"]
Sidekiq.redis do |conn|
results = conn.zrangebyscore('retry', score, score)
conn.zremrangebyscore('retry', score, score)
results.map do |message|
msg = Sidekiq.load_json(message)
msg['retry_count'] = msg['retry_count'] - 1
conn.rpush("queue:#{msg['queue']}", Sidekiq.dump_json(msg))
end
end
end end
end end

View file

@ -150,14 +150,6 @@ module Sidekiq
redirect "#{root_path}queues/#{params[:name]}" redirect "#{root_path}queues/#{params[:name]}"
end end
get "/retries/:score" do
halt 404 unless params[:score]
@score = params[:score].to_f
@retries = retries_with_score(@score)
redirect "#{root_path}retries" if @retries.empty?
slim :retry
end
get '/retries' do get '/retries' do
@count = (params[:count] || 25).to_i @count = (params[:count] || 25).to_i
(@current_page, @total_size, @retries) = page("retry", params[:page], @count) (@current_page, @total_size, @retries) = page("retry", params[:page], @count)
@ -165,6 +157,43 @@ module Sidekiq
slim :retries slim :retries
end end
get "/retries/:jid" do
halt 404 unless params[:jid]
@retry = Sidekiq::RetrySet.new.select do |retri|
retri.jid == params[:jid]
end.first
redirect "#{root_path}retries" if @retry.nil?
slim :retry
end
post '/retries' do
halt 404 unless params['jid']
if params['delete']
Sidekiq::RetrySet.new.select do |job|
job.jid.in?(params['jid'])
end.map(&:delete)
elsif params['retry']
Sidekiq::RetrySet.new.select do |job|
job.jid.in?(params['jid'])
end.map(&:retry)
end
redirect "#{root_path}retries"
end
post "/retries/:jid" do
halt 404 unless params['jid']
if params['retry']
Sidekiq::RetrySet.new.select do |job|
job.jid == params['jid']
end.first.retry
elsif params['delete']
Sidekiq::RetrySet.new.select do |job|
job.jid == params['jid']
end.first.delete
end
redirect "#{root_path}retries"
end
get '/scheduled' do get '/scheduled' do
@count = (params[:count] || 25).to_i @count = (params[:count] || 25).to_i
(@current_page, @total_size, @scheduled) = page("schedule", params[:page], @count) (@current_page, @total_size, @scheduled) = page("schedule", params[:page], @count)
@ -173,58 +202,14 @@ module Sidekiq
end end
post '/scheduled' do post '/scheduled' do
halt 404 unless params[:score] halt 404 unless params['jid']
halt 404 unless params['delete'] halt 404 unless params['delete']
params[:score].each do |score| Sidekiq::ScheduledSet.new.select do |job|
s = score.to_f job.jid.in?(params['jid'])
process_score('schedule', s, :delete) end.map(&:delete)
end
redirect "#{root_path}scheduled" redirect "#{root_path}scheduled"
end end
post '/retries' do
halt 404 unless params[:score]
params[:score].each do |score|
s = score.to_f
if params['retry']
process_score('retry', s, :retry)
elsif params['delete']
process_score('retry', s, :delete)
end
end
redirect "#{root_path}retries"
end
post "/retries/:score" do
halt 404 unless params[:score]
score = params[:score].to_f
if params['retry']
process_score('retry', score, :retry)
elsif params['delete']
process_score('retry', score, :delete)
end
redirect "#{root_path}retries"
end
def process_score(set, score, operation)
case operation
when :retry
Sidekiq.redis do |conn|
results = conn.zrangebyscore(set, score, score)
conn.zremrangebyscore(set, score, score)
results.map do |message|
msg = Sidekiq.load_json(message)
msg['retry_count'] = msg['retry_count'] - 1
conn.rpush("queue:#{msg['queue']}", Sidekiq.dump_json(msg))
end
end
when :delete
Sidekiq.redis do |conn|
conn.zremrangebyscore(set, score, score)
end
end
end
def self.tabs def self.tabs
@tabs ||= { @tabs ||= {
"Workers" =>'', "Workers" =>'',

View file

@ -9,7 +9,7 @@ class WorkController < ApplicationController
def email def email
UserMailer.delay_for(30.seconds).greetings(Time.now) UserMailer.delay_for(30.seconds).greetings(Time.now)
render :nothing => true render :text => 'enqueued'
end end
def long def long
@ -33,6 +33,6 @@ class WorkController < ApplicationController
p2 = Post.second p2 = Post.second
end end
p.delay.long_method(p2) p.delay.long_method(p2)
render :nothing => true render :text => 'enqueued'
end end
end end

View file

@ -64,9 +64,21 @@ class TestApi < MiniTest::Unit::TestCase
assert_equal 0, r.size assert_equal 0, r.size
end end
it 'can retry a retry' do
add_retry
r = Sidekiq::RetrySet.new
assert_equal 1, r.size
r.first.retry
assert_equal 0, r.size
assert_equal 1, Sidekiq::Queue.new('default').size
job = Sidekiq::Queue.new('default').first
assert_equal 'bob', job.jid
assert_equal 1, job['retry_count']
end
def add_retry def add_retry
at = Time.now.to_f at = Time.now.to_f
payload = Sidekiq.dump_json('class' => 'ApiWorker', 'args' => [1, 'mike'], 'queue' => 'default', 'jid' => 'bob') payload = Sidekiq.dump_json('class' => 'ApiWorker', 'args' => [1, 'mike'], 'queue' => 'default', 'jid' => 'bob', 'retry_count' => 2, 'failed_at' => Time.now.utc)
Sidekiq.redis do |conn| Sidekiq.redis do |conn|
conn.zadd('retry', at.to_s, payload) conn.zadd('retry', at.to_s, payload)
end end

View file

@ -49,7 +49,7 @@ class TestWeb < MiniTest::Unit::TestCase
end end
it 'handles missing retry' do it 'handles missing retry' do
get '/retries/12391982.123' get '/retries/2c4c17969825a384a92f023b'
assert_equal 302, last_response.status assert_equal 302, last_response.status
end end
@ -107,17 +107,6 @@ class TestWeb < MiniTest::Unit::TestCase
assert_match /HardWorker/, last_response.body assert_match /HardWorker/, last_response.body
end end
it 'can delete scheduled' do
msg,score = add_scheduled
Sidekiq.redis do |conn|
assert_equal 1, conn.zcard('schedule')
post '/scheduled', 'score' => [score], 'delete' => 'Delete'
assert_equal 302, last_response.status
assert_equal 'http://example.org/scheduled', last_response.header['Location']
assert_equal 0, conn.zcard('schedule')
end
end
it 'can display retries' do it 'can display retries' do
get '/retries' get '/retries'
assert_equal 200, last_response.status assert_equal 200, last_response.status
@ -133,31 +122,28 @@ class TestWeb < MiniTest::Unit::TestCase
end end
it 'can display a single retry' do it 'can display a single retry' do
get '/retries/12938712.123333' get '/retries/2c4c17969825a384a92f023b'
assert_equal 302, last_response.status assert_equal 302, last_response.status
_, score = add_retry msg = add_retry
get "/retries/#{msg['jid']}"
get "/retries/#{score}"
assert_equal 200, last_response.status assert_equal 200, last_response.status
assert_match /HardWorker/, last_response.body assert_match /HardWorker/, last_response.body
end end
it 'can delete a single retry' do it 'can delete a single retry' do
_, score = add_retry msg = add_retry
post "/retries/#{msg['jid']}", 'delete' => 'Delete'
post "/retries/#{score}", 'delete' => 'Delete'
assert_equal 302, last_response.status assert_equal 302, last_response.status
assert_equal 'http://example.org/retries', last_response.header['Location'] assert_equal 'http://example.org/retries', last_response.header['Location']
get "/retries" get "/retries"
assert_equal 200, last_response.status assert_equal 200, last_response.status
refute_match /#{score}/, last_response.body refute_match /#{msg['args'][2]}/, last_response.body
end end
it 'can retry a single retry now' do it 'can retry a single retry now' do
msg, score = add_retry msg = add_retry
post "/retries/#{msg['jid']}", 'retry' => 'Retry'
post "/retries/#{score}", 'retry' => 'Retry'
assert_equal 302, last_response.status assert_equal 302, last_response.status
assert_equal 'http://example.org/retries', last_response.header['Location'] assert_equal 'http://example.org/retries', last_response.header['Location']
@ -166,6 +152,17 @@ class TestWeb < MiniTest::Unit::TestCase
assert_match /#{msg['args'][2]}/, last_response.body assert_match /#{msg['args'][2]}/, last_response.body
end end
it 'can delete scheduled' do
msg = add_scheduled
Sidekiq.redis do |conn|
assert_equal 1, conn.zcard('schedule')
post '/scheduled', 'jid' => [msg['jid']], 'delete' => 'Delete'
assert_equal 302, last_response.status
assert_equal 'http://example.org/scheduled', last_response.header['Location']
assert_equal 0, conn.zcard('schedule')
end
end
it 'can show user defined tab' do it 'can show user defined tab' do
begin begin
Sidekiq::Web.tabs['Custom Tab'] = '/custom' Sidekiq::Web.tabs['Custom Tab'] = '/custom'
@ -179,14 +176,14 @@ class TestWeb < MiniTest::Unit::TestCase
end end
def add_scheduled def add_scheduled
score = Time.now.to_f
msg = { 'class' => 'HardWorker', msg = { 'class' => 'HardWorker',
'args' => ['bob', 1, Time.now.to_f], 'args' => ['bob', 1, Time.now.to_f],
'at' => Time.now.to_f } 'at' => score }
score = Time.now.to_f
Sidekiq.redis do |conn| Sidekiq.redis do |conn|
conn.zadd('schedule', score, Sidekiq.dump_json(msg)) conn.zadd('schedule', score, Sidekiq.dump_json(msg))
end end
[msg, score] msg
end end
def add_retry def add_retry
@ -196,12 +193,13 @@ class TestWeb < MiniTest::Unit::TestCase
'error_message' => 'Some fake message', 'error_message' => 'Some fake message',
'error_class' => 'RuntimeError', 'error_class' => 'RuntimeError',
'retry_count' => 0, 'retry_count' => 0,
'failed_at' => Time.now.utc, } 'failed_at' => Time.now.utc,
'jid' => "f39af2a05e8f4b24dbc0f1e4"}
score = Time.now.to_f score = Time.now.to_f
Sidekiq.redis do |conn| Sidekiq.redis do |conn|
conn.zadd('retry', score, Sidekiq.dump_json(msg)) conn.zadd('retry', score, Sidekiq.dump_json(msg))
end end
[msg, score] msg
end end
end end
end end

View file

@ -17,18 +17,18 @@ header.row
th Queue th Queue
th Worker th Worker
th Args th Args
- @retries.each do |(msg, score)| - @retries.each do |msg, score|
tr tr
td td
input type='checkbox' name='score[]' value='#{score}' input type='checkbox' name='jid[]' value='#{msg['jid']}'
td td
a href="#{root_path}retries/#{score}"== relative_time(Time.at(score)) a href="#{root_path}retries/#{msg['jid']}"== relative_time(Time.at(score))
td= msg['retry_count'] td= msg['retry_count']
td td
a href="#{root_path}queues/#{msg['queue']}" #{msg['queue']} a href="#{root_path}queues/#{msg['queue']}" #{msg['queue']}
td= msg['class'] td= msg['class']
td= display_args(msg['args']) td= display_args(msg['args'])
input.btn.btn-danger.btn-small.pull-right type="submit" name="delete" value="Delete" input.btn.btn-danger.btn-small.pull-right type="submit" name="delete" value="Delete"
input.btn.btn-primary.btn-small.pull-right type="submit" name="retry" value="Retry Now" input.btn.btn-primary.btn-small.pull-right type="submit" name="retry" value="Retry Now"
- else - else
.alert.alert-success No retries were found .alert.alert-success No retries were found

View file

@ -1,52 +1,55 @@
header header
h3 Job h3 Job
- @retries.each do |msg| table class="retry table table-bordered table-striped"
table class="retry table table-bordered table-striped" tbody
tbody tr
th Queue
td
a href="#{root_path}queues/#{@retry['queue']}" #{@retry['queue']}
tr
th Job Class
td
code= @retry['class']
tr
th Job Arguments
td
code= display_args(@retry['args'], 1000)
tr
th Job ID
td
code= @retry.jid
- if @retry['retry_count'] > 0
tr tr
th Queue th Retry Count
td td= @retry['retry_count']
a href="#{root_path}queues/#{msg['queue']}" #{msg['queue']}
tr tr
th Job Class th Last Retry
td td== relative_time(@retry['retried_at'].is_a?(Numeric) ? Time.at(@retry['retried_at']) : Time.parse(@retry['retried_at']))
code= msg['class'] - else
tr tr
th Job Arguments th Originally Failed
td td== relative_time(@retry['failed_at'].is_a?(Numeric) ? Time.at(@retry['failed_at']) : Time.parse(@retry['failed_at']))
code= display_args(msg['args'], 1000) tr
- if msg['retry_count'] > 0 th Next Retry
tr td== relative_time(Time.at(@retry.score))
th Retry Count
td= msg['retry_count']
tr
th Last Retry
td== relative_time(msg['retried_at'].is_a?(Numeric) ? Time.at(msg['retried_at']) : Time.parse(msg['retried_at']))
- else
tr
th Originally Failed
td== relative_time(msg['failed_at'].is_a?(Numeric) ? Time.at(msg['failed_at']) : Time.parse(msg['failed_at']))
tr
th Next Retry
td== relative_time(Time.at(@score))
h3 Error h3 Error
table class="error table table-bordered table-striped" table class="error table table-bordered table-striped"
tbody tbody
tr
th Error Class
td
code= @retry['error_class']
tr
th Error Message
td= @retry['error_message']
- if !@retry['error_backtrace'].nil?
tr tr
th Error Class th Error Backtrace
td td
code= msg['error_class'] code== @retry['error_backtrace'].join("<br/>")
tr form.form-horizontal action="#{root_path}retries/#{@retry.jid}" method="post"
th Error Message a.btn href="#{root_path}retries" ← Back
td= msg['error_message'] input.btn.btn-primary type="submit" name="retry" value="Retry Now"
- if !msg['error_backtrace'].nil? input.btn.btn-danger type="submit" name="delete" value="Delete"
tr
th Error Backtrace
td
code== msg['error_backtrace'].join("<br/>")
form.form-horizontal action="#{root_path}retries/#{@score}" method="post"
a.btn href="#{root_path}retries" ← Back
input.btn.btn-primary type="submit" name="retry" value="Retry Now"
input.btn.btn-danger type="submit" name="delete" value="Delete"

View file

@ -16,10 +16,10 @@ header.row
th width="10%" Queue th width="10%" Queue
th Worker th Worker
th Args th Args
- @scheduled.each do |(msg, score)| - @scheduled.each do |msg, score|
tr tr
td td
input type='checkbox' name='score[]' value='#{score}' input type='checkbox' name='jid[]' value='#{msg['jid']}'
td== relative_time(Time.at(score)) td== relative_time(Time.at(score))
td td
a href="#{root_path}queues/#{msg['queue']}" #{msg['queue']} a href="#{root_path}queues/#{msg['queue']}" #{msg['queue']}