Allow retry to take arguments?

D

Daniel Berger

Hi all,

What would folks think of allowing retry to take two arguments - a
num_retries and delay argument. Then you could do something like this:

begin
connect_to_something
rescue Exception
# Retry up to 3 times, sleeping 30 seconds between attempts
retry(3, 30)
raise
end

This would mostly be a convenience for db/network code, though I'm not
sure what other clever uses one might come up for it.

Good idea? Fraught with danger? Too difficult to implement? Too
specialized?

Inspired by Mark Fowler's Attempt module -
http://search.cpan.org/dist/Attempt/lib/Attempt.pm

Regards,

Dan
 
J

Jim Weirich

Hi all,

What would folks think of allowing retry to take two arguments - a
num_retries and delay argument. Then you could do something like this:

begin
connect_to_something
rescue Exception
# Retry up to 3 times, sleeping 30 seconds between attempts
retry(3, 30)
raise
end

This would mostly be a convenience for db/network code, though I'm not
sure what other clever uses one might come up for it.

Good idea? Fraught with danger? Too difficult to implement? Too
specialized?

Inspired by Mark Fowler's Attempt module -
http://search.cpan.org/dist/Attempt/lib/Attempt.pm

How about:

def attempt(trys, wait_time=0)
yield
rescue
trys -= 1
if trys > 0
sleep(wait_time) if wait_time > 0
retry
end
end

attempt(3, 30) do
connect_to_something
end
 
A

Austin Ziegler

How about:
=20
def attempt(trys, wait_time=3D0)
yield
rescue
trys -=3D 1
if trys > 0
sleep(wait_time) if wait_time > 0
retry
end
end
=20
attempt(3, 30) do
connect_to_something
end

One problem that I see, Jim. In Daniel's pseudo-code, he can rescue
specific exceptions:

begin
connect_to_something
rescue NetworkBusyException, TimeoutException
retry(3, 30) or raise
rescue NetworkUnavailableException
retry(5, 60) or raise
end

Yours just allows for one attempt wrapper and doesn't specify what
sort of exceptions can be caught.

It's a neat idea, though, and I think that it could be generally
useful, not just database useful.

-austin
--=20
Austin Ziegler * (e-mail address removed)
* Alternate: (e-mail address removed)
 
J

Jim Weirich

Austin Ziegler said:
One problem that I see, Jim. In Daniel's pseudo-code, he can rescue
specific exceptions:

begin
connect_to_something
rescue NetworkBusyException, TimeoutException
retry(3, 30) or raise
rescue NetworkUnavailableException
retry(5, 60) or raise
end

Gah! What is the semantics of that? What if you get a
NetworkBusyException, so you want to retry 3 times. But the next time yo=
u
get a NetworkUnavailable exception. Do you leave it at 3 retries or up i=
t
to a max of 5 tries. Or do the tries for each exception count
individually, i.e. we could have up to 8 retries, 5 for Unavailable and 3
for Busy/Timeout. Or is a a max of 11 retries because retries for timeou=
t
and busy count together.
Yours just allows for one attempt wrapper and doesn't specify what
sort of exceptions can be caught.

So use ...

attempt([NetworkBusyException, TimeoutException] =3D> [3,30],
NetworkUnavailableException =3D> [5, 60]) do
connect_to_something
end

or something like this ...

Attempt =3D RetryController.new do |retry|
retry.on NetworkBusyException, TimeoutException, :trys=3D>3, :timeou=
t=3D>30
retry.on NetworkUnavailableException, :trys=3D>5, :timeout=3D>60
end

# ... later in code ...

Attempt.call { connect_to_something }

All still doable without changing the language.

--=20
-- Jim Weirich (e-mail address removed) http://onestepback.org
 
A

Ara.T.Howard

It's a neat idea, though, and I think that it could be generally
useful, not just database useful.

i use these in my personal lib (http://codeforpeople.com/lib/ruby/alib/):

#
# initiates a catch block to do 'something'. if the method try_again! is
# called the block is done over - if the method give_up! is called the block
# is aborted. calls to attempt may be nested.
#
def attempt label = 'attempt'
ret = nil
n_attempts = 0
loop{ break unless catch("#{ label }"){ ret = yield(n_attempts += 1) } == 'try_again' }
ret
end
#
# see attempt
#
def try_again! label = 'attempt'
throw "#{ label }", 'try_again'
end
#
# see attempt
#
def give_up! label = 'attempt'
throw "#{ label }", 'give_up'
end

usage like

attempt do |n_attempts|
begin
db = connect
rescue
sleep 2 and try_again! if n_attempts < 3
end
end

and can be nested like

attempt('connect') do |connect_attempts|
begin
db = connect

attempt('transaction') do |transaction_attempts|
begin
db.execute sql
rescue => e
case e
when ConnectionError
sleep 2 and try_again!('connect') if connection_attempts < 3
when TransactionError
sleep 3 and try_again!('transaction') if transaction_attempts < 4
else
raise
end
end
end

rescue
sleep 2 and try_again!('connect') if connection_attempts < 3
end
end


and end up using it alot for network type code.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 
J

Joel VanderWerf

Ara.T.Howard said:
i use these in my personal lib (http://codeforpeople.com/lib/ruby/alib/):

#
# initiates a catch block to do 'something'. if the method
try_again! is
# called the block is done over - if the method give_up! is called
the block
# is aborted. calls to attempt may be nested.
#
def attempt label = 'attempt'
ret = nil
n_attempts = 0
loop{ break unless catch("#{ label }"){ ret = yield(n_attempts
+= 1) } == 'try_again' }
ret
end
#
# see attempt
#
def try_again! label = 'attempt'
throw "#{ label }", 'try_again'
end
#
# see attempt
#
def give_up! label = 'attempt'
throw "#{ label }", 'give_up'
end

usage like

attempt do |n_attempts|
begin
db = connect
rescue
sleep 2 and try_again! if n_attempts < 3
end
end

and can be nested like

attempt('connect') do |connect_attempts|
begin
db = connect

attempt('transaction') do |transaction_attempts|
begin
db.execute sql
rescue => e
case e
when ConnectionError
sleep 2 and try_again!('connect') if connection_attempts < 3
when TransactionError
sleep 3 and try_again!('transaction') if
transaction_attempts < 4
else
raise
end
end
end

rescue
sleep 2 and try_again!('connect') if connection_attempts < 3
end
end


and end up using it alot for network type code.

-a

Well, that's timely and useful for some network code I'm working on that
has to expect a lot of disconnects and timeouts. I'll try wrapping the
attempt as an object:

class Attempt
attr_reader :attempts, :label
def initialize label
@label = "__attempt__#{label}__".intern
@attempts = 0
end

TRY_AGAIN = :try_again
GIVE_UP = :give_up

def try_again
@attempts += 1
throw @label, TRY_AGAIN
end

def give_up
throw @label, GIVE_UP
end
end

def attempt label
att = Attempt.new(label)
ret = nil
op = Attempt::TRY_AGAIN

while op == Attempt::TRY_AGAIN
op = catch att.label do
ret = yield(att)
end
end

ret
end

x = 0
r = attempt 'add' do |add|
begin
x += 1
if x < 10
raise ArgumentError
end
rescue ArgumentError
add.try_again if add.attempts < 15
end
x
end

p r
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,176
Messages
2,570,949
Members
47,500
Latest member
ArianneJsb

Latest Threads

Top