-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wrong stacktrace shown when rethrowing an exception after catching another one #19979
Comments
We should somehow clarify that if you care about the backtrace of an exception, you need to call |
I've been trying to figure out why the backtraces are mostly corrupted when my Retry.jl package is used. I have always assumed that my naive macro hacking was messing up the line number metadata in the AST somehow. I spent a long time chasing that down, but I now think the root of the problem is this issue. It seems there are three problems: the backtrace can be empty; the rethrown backtrace can be wrong; or the rethrown error and backtrace can be wrong. The minimal cases I've boiled it down to follow:
|
@JeffBezanson I'm not sure how to apply your advice that "you need to call catch_backtrace immediately". I see that the runtime calls It seems like maybe I need to do I found that Maybe |
Retry.jl used to use try/catch inside the catch block's "@ignore if ..." condition. The purpose of this was to treat if conditions that throw errors as false. e.f. @ignore if e.reason == "Foo" end might throw an exception if e does not have a reason field. Retry.jl used to allow this and treat it as false. At present doing a nested try/catch in a catch block causes the Julia stacktrace to be corrupted: JuliaLang/julia#19979 (comment) After this commit, Retry.jl not requires @ignore if conditions to return true or false and not throw exceptions. The ecode() function has been exported to allow replacing @ignore if e.code == "404" end with @ignore if ecode(e) == "404" end
JuliaWeb/Retry.jl@6c565a3 JuliaLang/julia#19979 (comment) Retry.jl no longer allows @ignore if conditions to throw errors. To support this: - Dynamic generation of exception types is removed from AWSException.jl (All exceptions are now type AWSException with fields: code, message, info and cause. - Exception handling conditions have added type checks.
I've definitely seen the case for |
I think the fastest way to fix this is to add a method of
|
Here's a patch for 0.6: --- a/src/task.c
+++ b/src/task.c
@@ -576,6 +576,16 @@ JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e)
throw_internal(e);
}
+JL_DLLEXPORT void jl_rethrow_other_bt(jl_value_t *e, jl_value_t *bt)
+{
+ size_t btsz = jl_array_len(bt);
+ size_t sz = btsz > JL_MAX_BT_SIZE ? JL_MAX_BT_SIZE : btsz;
+ jl_ptls_t ptls = jl_get_ptls_states();
+ memcpy(ptls->bt_data, jl_array_data(bt), sz * sizeof(void*));
+ ptls->bt_size = sz;
+ throw_internal(e);
+}
+
JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, size_t ssize) With wrapper: rethrow(e, bt::Array{Ptr{Void},1}) = ccall(:jl_rethrow_other_bt, Union{}, (Any,Any), e, bt) This can be added to 0.7 as well though the patch will be significantly different. In fact we should probably fully deprecate |
Naively it seems possible to fix this and make Is it worth considering this approach or is there some fundamental reason why it won't work? |
The problem is that collecting the backtrace is expensive, so you only want to do that if you're actually gonna save it. It is quite possible however to have some sort of magic symbol that the compiler looks for to see if it needs to save the backtrace (which could but need not be syntactically exposed). @JeffBezanson said there was an issue about that at some point, but I couldn't find it. |
I spent a few hours poking around in the runtime. The backtrace may be expensive, but it looks like the cost of recording it is unconditionally borne inside But that seems like a distraction - what I'm proposing is that if there's a current backtrace, we should record it when entering a try block, and restore it when leaving. Seems like a bit of pointer shuffling may be all that's required in For context, I'm just mulling over what to do about Jeff's comment at #25370 (comment) and wondering if a solution to the underlying problem is within reach. |
Hah, I should have known better :-) . I started prototyping a solution, but restoring the exception can't happen inside I now think we should generalize the To address the task switching problems, the current exception stack would need to be saved and restored during context switching which means more copies of If we did all this, I believe |
@c42f a stack of backtraces is what I had in mind #19979 (comment) ...
|
Yes, a stack of backtraces is a good idea. That would let us keep |
This reminds me of an old niggling thought: can we do better that the
try
...
peek e
@warn "my code caused an error" e
end
try
...
catch e
e isa IOError && e.code == :FOO && delete!(e)
end |
@samoconnor that's an interesting idea; I think the key thing you're doing with the I think a better way to do this might be to add a new exception to the stack - that is, instead of the try
...
catch
throw(MyException("my code caused an error"))
end Notably, because this In general I think the pattern of logging an exception and rethrowing it isn't great: Somewhere at an outer scope the exception will also be caught and reported in some way (probably via logging but without rethrowing). Logging and rethrowing just leads to duplicate error information due to several catch/log/rethrows down the stack. This is very confusing in log files. I'd say it's much better to preserve the information and make it available as a full stack of exceptions at outer scope instead. |
Hi @c42f, It would be really awesome to have rethrow working :) (This reminds me of: #15906) |
It's an interesting problem but I haven't made any implementation progress. My original motivation was to solve #25370 (comment), but in the end it didn't seem that fixing |
I had a 24 hour plane journey so I started looking into this in more detail. Here's some notes I made for myself about how exceptions and backtraces are implemented in the runtime (perhaps could become devdocs if there's interest?) Capturing backtracesIn the runtime, backtraces are recorded by calling the low level function
For use from julia code, Format of the raw instruction pointer arrayThe julia runtime executes programs as a mixture of interpreted and compiled
To get stack traces to work when interpreted and compiled code are mixed Exception handlingTry-catch mechanismJulia exception handling is built on top of the C
The state to be restored must be recorded at the try site and be accessible at The C mechanism is as follows:
The julia codegen mechnism essentially mirrors the C version, but is a little Throwing exceptionsFrom juliaWhen an exception is raised with
From the runtimeThe runtime can raise exceptions using There's also some special circumstances which require extra care:
These are caught by the runtime using signal handlers (for example, stack |
To follow on from the above, a possible implementation plan: First, fix the task-vs-thread local state bugs in the current implementation. This should give experience going to the next step:
To tie things off, replace the task-based exception/backtrace with a stack:
|
I've been slowly poking away at this. It looks reasonably easy to preserve the exception and backtrace data during task switching - that's just a few changes to the runtime C code. The more tricky problem is to insert an exception stack pop intrinsic (which I've currently called Before()
try
InTry()
catch
InCatch()
end
After() lowers to
The state of the stack at I've got three ideas:
@Keno any thoughts? |
It turns out that of the ideas above, producing a token from |
Here's a PR which fixes this: #28878 |
Fixed in #28878 |
produces the misleading
This just hit me in a really wicked situation: The stacktrace actually lead me to a line erroneously throwing the reported exception. I fixed it (to throw the correct exception), but the same error kept showing, leaving me completely puzzled for quite some time...
The text was updated successfully, but these errors were encountered: