Skip to content

Commit

Permalink
add retry and @catch per
Browse files Browse the repository at this point in the history
JuliaLang#14843

Add default small delay to retry.

50ms delay on first retry.
250ms delay on 2nd retry.

This at least gives other tasks a chance to run.

If retry n is set higher, the delay increases to 1250ms, 6250ms ...
max_delay caps the dealy at 10s by default.

This should handle network-timescale issues without creating undue load.

tweak test/error.jl per https://travis-ci.org/JuliaLang/julia/jobs/114700424#L1597
  • Loading branch information
samoconnor committed Mar 24, 2016
1 parent 77936d0 commit 8561984
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 1 deletion.
44 changes: 44 additions & 0 deletions base/error.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,47 @@ macro assert(ex, msgs...)
end
:($(esc(ex)) ? $(nothing) : throw(Main.Base.AssertionError($msg)))
end


"""
retry(f, [condition], n=3; max_delay=10)
Returns a lambda that retries function `f` up to `n` times in the
event of an exception. If `condition` is a `Type` then retry only
for exceptions of that type. If `condition` is a function
`cond(::Exception) -> Bool` then retry only if it is true.
e.g. `retry(http_get, e->e.status == "503")(url)` or `retry(read, UVError)(io)`.
"""
function retry(f::Function, condition::Function=e->true, n::Int=3; max_delay=10)
(args...) -> begin
delay = 0.05
for i = 1:n
try
return f(args...)
catch e
if i == n || try condition(e) end != true
rethrow(e)
end
end
sleep(delay * (0.8 + (rand() * 0.4)))
delay = min(max_delay, delay * 5)
end
end
end

retry(f::Function, condition::Type, n::Int=3) = retry(f, e->isa(e, condition), n)


"""
@catch(f)
Returns a lambda that executes `f` and returns either the result of `f` or
an `Exception` thrown by `f`.
e.g. `@catch(length)() == MethodError(length,())`
`@catch(length)([1,2,3]) == 3`
"""
macro catch(f)
:((args...) -> try $(esc(f))(args...) catch ex; ex end)
end
2 changes: 2 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1045,9 +1045,11 @@ export
# errors
assert,
backtrace,
@catch,
catch_backtrace,
error,
rethrow,
retry,
systemerror,

# stack traces
Expand Down
16 changes: 16 additions & 0 deletions doc/stdlib/base.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,22 @@ Errors
An error occurred when running a module's ``__init__`` function. The actual error thrown is available in the ``.error`` field.

.. function:: retry(f, [condition], n=3; max_delay=10)

.. Docstring generated from Julia source
Returns a lambda that retries function ``f`` up to ``n`` times in the event of an exception. If ``condition`` is a ``Type`` then retry only for exceptions of that type. If ``condition`` is a function ``cond(::Exception) -> Bool`` then retry only if it is true.

e.g. ``retry(http_get, e->e.status == "503")(url)`` or ``retry(read, UVError)(io)``\ .

.. function:: @catch(f)

.. Docstring generated from Julia source
Returns a lambda that executes ``f`` and returns either the result of ``f`` or an ``Exception`` thrown by ``f``\ .

e.g. ``@catch(length)() == MethodError(length,())`` ``@catch(length)([1,2,3]) == 3``

Events
------

Expand Down
2 changes: 1 addition & 1 deletion test/choosetests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function choosetests(choices = [])
"markdown", "base64", "serialize", "functors", "misc", "threads",
"enums", "cmdlineargs", "i18n", "workspace", "libdl", "int",
"checked", "intset", "floatfuncs", "compile", "parallel", "inline",
"boundscheck"
"boundscheck", "error"
]

if Base.USE_GPL_LIBS
Expand Down
72 changes: 72 additions & 0 deletions test/error.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# This file is a part of Julia. License is MIT: http://julialang.org/license


@test map(typeof, map(@catch(i->[1,2,3][i]), 1:6)) ==
[Int, Int, Int, BoundsError, BoundsError, BoundsError]

@test typeof(@catch(open)("/no/file/with/this/name")) == SystemError


let

function foo_error(c, n)
c[1] += 1
if c[1] <= n
error("foo")
end
return 7
end

# Success on first attempt...
c = [0]
@test retry(foo_error)(c,0) == 7
@test c[1] == 1

# Success on second attempt...
c = [0]
@test retry(foo_error)(c,1) == 7
@test c[1] == 2

# Success on third attempt...
c = [0]
@test retry(foo_error)(c,2) == 7
@test c[1] == 3

# 3 failed attempts, so exception is raised...
c = [0]
ex = @catch(retry(foo_error))(c,3)
@test ex.msg == "foo"
@test c[1] == 3

c = [0]
ex = @catch(retry(foo_error, ErrorException))(c,3)
@test typeof(ex) == ErrorException
@test ex.msg == "foo"
@test c[1] == 3

c = [0]
ex = @catch(retry(foo_error, e->e.msg == "foo"))(c,3)
@test typeof(ex) == ErrorException
@test ex.msg == "foo"
@test c[1] == 3

# No retry if condition does not match...
c = [0]
ex = @catch(retry(foo_error, e->e.msg == "bar"))(c,3)
@test typeof(ex) == ErrorException
@test ex.msg == "foo"
@test c[1] == 1

c = [0]
ex = @catch(retry(foo_error, e->e.http_status_code == "503"))(c,3)
@test typeof(ex) == ErrorException
@test ex.msg == "foo"
@test c[1] == 1

c = [0]
ex = @catch(retry(foo_error, SystemError))(c,3)
@test typeof(ex) == ErrorException
@test ex.msg == "foo"
@test c[1] == 1

end

0 comments on commit 8561984

Please sign in to comment.