-
Notifications
You must be signed in to change notification settings - Fork 118
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
Unfortunate backtrace clearing by with_return
#44
Comments
I looked in my mail archives and the rationale was that if an exception raised with However, I agree that this case is annoying. Maybe we should revisit this decision. I always found backtrace management to be messy in OCaml. It would be much simpler if we just stored bakctraces in exceptions directly. |
Do you have an example where this is problematic? The ony case I can think of is if you write something like this: try
...
with exn ->
<code that ends up calling clear_backtrace>
reraise exn But even without the explicit call to clear_backtrace this code could clear the backtrace. |
One case (that is probably an ill-advised approach, I admit) is something along the lines of: match Array.fold_result ~f ... with
| Ok _ -> ...
| Error _ -> Printexc.get_raw_backtrace ... But looking again, I may have mis-blamed the try
let a = f { return } in
is_alive := false;
a
with exn ->
is_alive := false;
match exn with
| M.Return a -> a
| _ -> raise exn Perhaps |
Ah, yes that makes sense. I guess we didn't catch this internally as we just patch the compiler so that |
I submitted the change for review internally and made the change in the v0.11 branch |
Aside: I think it would be great if the patch to change |
Agreed |
I'm a bit surprised by this, the compiler should already be detecting cases like the one discussed here and turn them into a reraise, and indeed if I define type 'a return = { return : 'b. 'a -> 'b }
let with_return (type a) f =
let module M = struct
(* Raised to indicate ~return was called. Local so that the exception is tied to a
particular call of [with_return]. *)
exception Return of a
end in
let is_alive = ref true in
let return a =
if not !is_alive
then failwith "use of [return] from a [with_return] that already returned";
raise_notrace (M.Return a);
in
try
let a = f { return } in
is_alive := false;
a
with exn ->
is_alive := false;
match exn with
| M.Return a -> a
| _ -> raise exn Then
And I don't think this is a new feature of 4.06. |
I suspect that the automatic converting of |
It might be because of the I remember that the heuristics for turning |
I will check But a higher-level point, which is perhaps not really about Base, is that the difference between |
Agreed. We were talking about it the other day with @mshinwell and we want to experiment with the idea of storing backtraces inside exceptions, since it would solve all backtrace related problems. The only issue is that it causes constant exceptions to allocate again. However, we have an idea to preserve this optimization when the backtrace is not needed. We are planning to do this as an intern project. |
Nice, storing the backtraces in the exceptions themselves seems to make a lot of sense. FWIW, I think we would be happy even if to get the no-alloc behavior you had to explicitly use `raise_notrace`. I'm not sure if it would simplify the design or compiler analysis load, but perhaps.
|
I think we would be happy with that too at Jane Street. However I remember that this was one of the argument for changing the representation of exceptions some time ago, so some people might rely on it. The analysis we are thinking of should be simple. The idea is the following: if the backtrace is attached to the exception, then the function to retrieve the backtrace should have type: val get_raw_backtrace : exn -> raw_backtrace So for a given exception X constant or not, if a handler matches all possible values for X and doesn't bind the exception to a variable, then there is no way for the user to get the backtrace. This property could be stored in the binary and at runtime raise would check whether the current handler can get the backtrace or not for the exception being raised. I think this should catch all the relevant use cases. Moreover this would benefit functions such as |
Ah yes indeed, that makes sense.
And we don't want to define it as Another option would be to explicitely reraise everywhere in base (but that become tedious). |
Well, we never really discussed it. Initially it must have been me or Stephen who wrote Regarding pre-processing before pushing to github, it seems to me that the issue is the same for everyone so the solution should probably be the same as well. If the compiler heuristics are now good enough, then we should just define it as |
I looked more into this. The heuristic should work in all cases in 4.07, so we just changed the declaration of |
This is fixed in both master and v0.11.1 |
The use of
With_return.with_return
in e.g.Container.fold_result
leads to a call toExn.raise_without_backtrace
, which callsclear_backtrace
. Perhaps in other contexts it makes sense forExn.raise_without_backtrace
to clear the backtrace, but it makes debugging any code usingwith_return
rather harder.The text was updated successfully, but these errors were encountered: