From 15db473be3b404a7a3cf2cb3212036f4c9e04089 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Fri, 3 Sep 2021 11:59:31 +0200 Subject: [PATCH 1/9] Print stacktraces from asyncmap'd tasks ```julia julia> f(i) = (sleep(i); i == 5 && error("5")) f (generic function with 1 method) julia> asyncmap(f, 1:5) # master ERROR: 5 Stacktrace: [1] (::Base.var"#881#883")(x::Task) @ Base ./asyncmap.jl:177 [2] foreach(f::Base.var"#881#883", itr::Vector{Any}) @ Base ./abstractarray.jl:2606 [3] maptwice(wrapped_f::Function, chnl::Channel{Any}, worker_tasks::Vector{Any}, c::UnitRange{Int64}) @ Base ./asyncmap.jl:177 [4] wrap_n_exec_twice @ ./asyncmap.jl:153 [inlined] [5] async_usemap(f::typeof(f), c::UnitRange{Int64}; ntasks::Int64, batch_size::Nothing) @ Base ./asyncmap.jl:103 [6] #asyncmap#865 @ ./asyncmap.jl:81 [inlined] [7] asyncmap(f::Function, c::UnitRange{Int64}) @ Base ./asyncmap.jl:81 [8] top-level scope @ REPL[4]:1 julia> function Base.start_worker_task!(worker_tasks, exec_func, chnl, batch_size=nothing) # monkeypatch t = @async begin retval = nothing try if isa(batch_size, Number) while isopen(chnl) # The mapping function expects an array of input args, as it processes # elements in a batch. batch_collection=Any[] n = 0 for exec_data in chnl push!(batch_collection, exec_data) n += 1 (n == batch_size) && break end if n > 0 exec_func(batch_collection) end end else for exec_data in chnl exec_func(exec_data...) end end catch e close(chnl) Base.display_error(stderr, Base.catch_stack()) retval = e end retval end push!(worker_tasks, t) end julia> function Base.start_worker_task!(worker_tasks, exec_func, chnl, batch_size=nothing) t = @async begin retval = nothing try if isa(batch_size, Number) while isopen(chnl) # The mapping function expects an array of input args, as it processes # elements in a batch. julia> function Base.start_worker_task!(worker_tasks, exec_func, chnl, batch_size=nothing) t = @async begin retval = nothing try if isa(batch_size, Number) while isopen(chnl) # The mapping function expects an array of input args, as it processes # elements in a batch. julia> asyncmap(f, 1:5) # post-patch ERROR: 5 Stacktrace: [1] error(s::String) @ Base ./error.jl:33 [2] f @ ./REPL[3]:1 [inlined] [3] (::Base.var"#871#876"{typeof(f)})(r::Base.RefValue{Any}, args::Tuple{Int64}) @ Base ./asyncmap.jl:100 [4] macro expansion @ ./REPL[5]:23 [inlined] [5] (::var"#1#2"{Base.var"#871#876"{typeof(f)}, Channel{Any}, Nothing})() @ Main ./task.jl:411 ERROR: 5 Stacktrace: [1] (::Base.var"#881#883")(x::Task) @ Base ./asyncmap.jl:177 [2] foreach(f::Base.var"#881#883", itr::Vector{Any}) @ Base ./abstractarray.jl:2606 [3] maptwice(wrapped_f::Function, chnl::Channel{Any}, worker_tasks::Vector{Any}, c::UnitRange{Int64}) @ Base ./asyncmap.jl:177 [4] wrap_n_exec_twice @ ./asyncmap.jl:153 [inlined] [5] async_usemap(f::typeof(f), c::UnitRange{Int64}; ntasks::Int64, batch_size::Nothing) @ Base ./asyncmap.jl:103 [6] #asyncmap#865 @ ./asyncmap.jl:81 [inlined] [7] asyncmap(f::Function, c::UnitRange{Int64}) @ Base ./asyncmap.jl:81 [8] top-level scope @ REPL[6]:1 ``` --- base/asyncmap.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/asyncmap.jl b/base/asyncmap.jl index 10dcf23420c16..588df330dc271 100644 --- a/base/asyncmap.jl +++ b/base/asyncmap.jl @@ -236,6 +236,7 @@ function start_worker_task!(worker_tasks, exec_func, chnl, batch_size=nothing) end catch e close(chnl) + display_error(stderr, catch_stack()) retval = e end retval From d78d6ee4705be30ab5a820b5b7010f47ce37eef4 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Tue, 28 Dec 2021 02:26:45 -0500 Subject: [PATCH 2/9] better fix --- base/asyncmap.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/base/asyncmap.jl b/base/asyncmap.jl index 588df330dc271..e4c1e75c828e6 100644 --- a/base/asyncmap.jl +++ b/base/asyncmap.jl @@ -236,8 +236,7 @@ function start_worker_task!(worker_tasks, exec_func, chnl, batch_size=nothing) end catch e close(chnl) - display_error(stderr, catch_stack()) - retval = e + retval = CapturedException(e, catch_backtrace()) end retval end From aa1f35014c2b4199d0b53357e97a84f2254245d9 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Tue, 28 Dec 2021 20:30:15 -0500 Subject: [PATCH 3/9] update tests --- stdlib/Distributed/test/distributed_exec.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/Distributed/test/distributed_exec.jl b/stdlib/Distributed/test/distributed_exec.jl index 69d9fb47eccc5..522f144de9fdd 100644 --- a/stdlib/Distributed/test/distributed_exec.jl +++ b/stdlib/Distributed/test/distributed_exec.jl @@ -634,7 +634,7 @@ let error_thrown = false try pmap(x -> x == 50 ? error("foobar") : x, 1:100) catch e - @test e.captured.ex.msg == "foobar" + @test e.ex.captured.ex.msg == "foobar" error_thrown = true end @test error_thrown @@ -1723,7 +1723,7 @@ let (h, t) = Distributed.head_and_tail(Int[], 0) end # issue #35937 -let e = @test_throws RemoteException pmap(1) do _ +let e = @test_throws CapturedException pmap(1) do _ wait(@async error(42)) end # check that the inner TaskFailedException is correctly formed & can be printed From 1786c4e22175db5ac63fff771a7cc1c8026a98e9 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Tue, 28 Dec 2021 20:37:33 -0500 Subject: [PATCH 4/9] add test --- test/asyncmap.jl | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/asyncmap.jl b/test/asyncmap.jl index 04c215af7bb60..ec49230dbce14 100644 --- a/test/asyncmap.jl +++ b/test/asyncmap.jl @@ -54,6 +54,19 @@ len_only_iterable = (1,2,3,4,5) @test_throws ArgumentError asyncmap(identity, 1:10; batch_size="10") @test_throws ArgumentError asyncmap(identity, 1:10; ntasks="10") +# Check that we throw a `CapturedException` holding the stacktrace if `f` throws +f42105(i) = i == 5 ? error("captured") : i +let + e = try + asyncmap(f42105, 1:5) + catch e + e + end + @test e isa CapturedException + @test e.ex == ErrorException("captured") + @test e.processed_bt[2][1].func == :f42105 +end + include("generic_map_tests.jl") generic_map_tests(asyncmap, asyncmap!) From abff73a93f9693c80ccbfcfc63161d6a739a9ebf Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Wed, 29 Dec 2021 23:42:10 -0500 Subject: [PATCH 5/9] rm trailing whitespace From 8e986cfb11eb783c0083532e7b18ea10c455766e Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Sun, 2 Jan 2022 19:03:56 -0500 Subject: [PATCH 6/9] restore check that we're throwing a RemoteException --- stdlib/Distributed/test/distributed_exec.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/Distributed/test/distributed_exec.jl b/stdlib/Distributed/test/distributed_exec.jl index 522f144de9fdd..d41b589fc16c9 100644 --- a/stdlib/Distributed/test/distributed_exec.jl +++ b/stdlib/Distributed/test/distributed_exec.jl @@ -1726,6 +1726,7 @@ end let e = @test_throws CapturedException pmap(1) do _ wait(@async error(42)) end + @test e.value.ex isa RemoteException # check that the inner TaskFailedException is correctly formed & can be printed es = sprint(showerror, e.value) @test contains(es, ":\nTaskFailedException\nStacktrace:\n") From 97f4398214e25d43f120c58e21160d0ec74dfefc Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Sun, 2 Jan 2022 21:49:14 -0500 Subject: [PATCH 7/9] don't capture RemoteException's --- base/asyncmap.jl | 2 +- base/task.jl | 4 ++++ stdlib/Distributed/src/process_messages.jl | 2 ++ stdlib/Distributed/test/distributed_exec.jl | 5 ++--- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/base/asyncmap.jl b/base/asyncmap.jl index e4c1e75c828e6..354e4fedc66d1 100644 --- a/base/asyncmap.jl +++ b/base/asyncmap.jl @@ -236,7 +236,7 @@ function start_worker_task!(worker_tasks, exec_func, chnl, batch_size=nothing) end catch e close(chnl) - retval = CapturedException(e, catch_backtrace()) + retval = capture_exception(e, catch_backtrace()) end retval end diff --git a/base/task.jl b/base/task.jl index fb32df9e57c65..12eaee8de074d 100644 --- a/base/task.jl +++ b/base/task.jl @@ -25,6 +25,10 @@ function showerror(io::IO, ce::CapturedException) showerror(io, ce.ex, ce.processed_bt, backtrace=true) end +# wraps `CapturedException` but can be overloaded to be a no-op +# when a stacktrace is already captured (e.g. `Distributed.RemoteException`). +capture_exception(ex, bt) = CapturedException(ex, bt) + """ CompositeException diff --git a/stdlib/Distributed/src/process_messages.jl b/stdlib/Distributed/src/process_messages.jl index a093ffff01d34..369ff9212a912 100644 --- a/stdlib/Distributed/src/process_messages.jl +++ b/stdlib/Distributed/src/process_messages.jl @@ -44,6 +44,8 @@ struct RemoteException <: Exception captured::CapturedException end +Base.capture_exception(ex::RemoteException, bt) = ex + """ RemoteException(captured) diff --git a/stdlib/Distributed/test/distributed_exec.jl b/stdlib/Distributed/test/distributed_exec.jl index d41b589fc16c9..69d9fb47eccc5 100644 --- a/stdlib/Distributed/test/distributed_exec.jl +++ b/stdlib/Distributed/test/distributed_exec.jl @@ -634,7 +634,7 @@ let error_thrown = false try pmap(x -> x == 50 ? error("foobar") : x, 1:100) catch e - @test e.ex.captured.ex.msg == "foobar" + @test e.captured.ex.msg == "foobar" error_thrown = true end @test error_thrown @@ -1723,10 +1723,9 @@ let (h, t) = Distributed.head_and_tail(Int[], 0) end # issue #35937 -let e = @test_throws CapturedException pmap(1) do _ +let e = @test_throws RemoteException pmap(1) do _ wait(@async error(42)) end - @test e.value.ex isa RemoteException # check that the inner TaskFailedException is correctly formed & can be printed es = sprint(showerror, e.value) @test contains(es, ":\nTaskFailedException\nStacktrace:\n") From 505dabdeea327cb5650185238531fb4198464a9b Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Mon, 3 Jan 2022 22:15:59 -0500 Subject: [PATCH 8/9] add docstrings --- base/task.jl | 10 ++++++++-- stdlib/Distributed/src/process_messages.jl | 5 +++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/base/task.jl b/base/task.jl index 12eaee8de074d..87574ef958718 100644 --- a/base/task.jl +++ b/base/task.jl @@ -25,8 +25,14 @@ function showerror(io::IO, ce::CapturedException) showerror(io, ce.ex, ce.processed_bt, backtrace=true) end -# wraps `CapturedException` but can be overloaded to be a no-op -# when a stacktrace is already captured (e.g. `Distributed.RemoteException`). +""" + capture_exception(ex, bt) -> Exception + +Returns an exception, possibly incorporating information from a backtrace `bt`. Defaults to returning [`CapturedException(ex, bt)`](@ref). + +Used in [`asyncmap`](@ref) and [`asyncmap!`](@ref) to capture exceptions thrown during +the user-supplied function call. +""" capture_exception(ex, bt) = CapturedException(ex, bt) """ diff --git a/stdlib/Distributed/src/process_messages.jl b/stdlib/Distributed/src/process_messages.jl index 369ff9212a912..59a4c8179d675 100644 --- a/stdlib/Distributed/src/process_messages.jl +++ b/stdlib/Distributed/src/process_messages.jl @@ -44,6 +44,11 @@ struct RemoteException <: Exception captured::CapturedException end +""" + capture_exception(ex::RemoteException, bt) = ex + +Returns `ex` which has already captured a backtrace (via it's [`CapturedException`](@ref)). +""" Base.capture_exception(ex::RemoteException, bt) = ex """ From aef10d943db2920a27761b1628700dba656a5ba5 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Mon, 3 Jan 2022 22:20:37 -0500 Subject: [PATCH 9/9] tweak wording --- stdlib/Distributed/src/process_messages.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/Distributed/src/process_messages.jl b/stdlib/Distributed/src/process_messages.jl index 59a4c8179d675..7bbf7cfde943b 100644 --- a/stdlib/Distributed/src/process_messages.jl +++ b/stdlib/Distributed/src/process_messages.jl @@ -45,9 +45,9 @@ struct RemoteException <: Exception end """ - capture_exception(ex::RemoteException, bt) = ex + capture_exception(ex::RemoteException, bt) -Returns `ex` which has already captured a backtrace (via it's [`CapturedException`](@ref)). +Returns `ex::RemoteException` which has already captured a backtrace (via it's [`CapturedException`](@ref) field `captured`). """ Base.capture_exception(ex::RemoteException, bt) = ex