-
-
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
Fixed long stacktrace printing #28453
Conversation
@@ -507,7 +506,7 @@ function show_reduced_backtrace(io::IO, t::Vector, with_prefix::Bool) | |||
while i < length(t) && t[i] == t[j] | |||
i+=1 ; j+=1 | |||
end | |||
if j >= frame_counter | |||
if j >= frame_counter-1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes the issue with undetected cycles repeated only once.
Thanks for fixing this so quickly; would be really good to get this in before 1.0. I like the new API more. Transforming one frame at a time seems simpler.
I don't think that's true. Whether the function comes from a Ref should not affect this. |
I believe the goal of |
It would be good to have tests here to prevent this from happening again. |
Regarding tests, this only affects printing, it does not modify the stacktraces themselves so I'm not sure how to test it. |
AFAIK you will still need the invokelatest
|
Tests:
|
@KristofferC as you can see in your example, the resulting String depends on "/Users/kristoffer" and "REPL", which is not reproducible in a test. Should I remove the last two lines of the output and call it a test (by making it more complete maybe)? |
Ach, even the two first lines depend on "REPL" though. Hm... |
You don't need to test the exact string. Just that the numbers it starts with are correct. (and that the |
OK, thanks for the idea, I'll try to design a good test. |
Regarding tests, there are two possible things to test:
Testing the first issue is necessary. Should I also add a test for the second concern? It makes changes in error printing less easy, but maybe that's not really important (the tests can be changed too). |
In the rare cases that someone changes the backtrace printing, I think it is fine that they need to update a test or two. Better than it accidentally regressing. |
All right, that's also how I feel. |
I basically pasted @KristofferC's idea for the tests. Maybe someone should review? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is fine. This API indeed has advantages in efficiency, since you only correct those stackframes that you are going to show. One downside is that with the previous API, the recipient callback function got stacktraces as complete entities, and hence might be able to do more analysis. (With this API, the callback will just get a series of individual calls with no way to certainly associate them into complete stacktraces.)
But overall I think it's worth sacrificing that potential advantage of the old API. So once you synchronize LAST_SHOWN_LINE_INFOS
I think this will be good to go.
base/errorshow.jl
Outdated
show_trace_entry(io, frame, n) | ||
if with_prefix | ||
show_trace_entry(io, last_frame, n, prefix = string(" [", frame_counter-1, "] ")) | ||
push!(LAST_SHOWN_LINE_INFOS, (string(last_frame.file), last_frame.line)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need to capture the corrected frame. Otherwise LAST_SHOWN_LINE_INFOS
will be the "old" stacktrace, but what's displayed in the REPL will be the new one. Then when someone hits 15^Q
to go to the point in the file they will wonder why edit
opens the file to the wrong line number.
Good catch! I'm now actually wondering why the line push!(LAST_SHOWN_LINE_INFOS, (string(last_frame.file), last_frame.line)) is at this position anyway, shouldn't it be in |
I sort of assume here that anything which is shown through |
I now tentatively update |
Didn't check the Hmm, calling |
All right, back to editing code then |
Uh, actually the test @timholy just indicated passes: using module FuzzLineno
function updater(frame::Base.StackFrame)
return Base.StackFrame(frame.func, frame.file, frame.line+10000, frame.linfo, frame.from_c, frame.inlined, frame.pointer)
end
Base.update_stackframes_callback[] = updater
foo() = error("FOO")
end I obtain: julia> FuzzLineno.foo()
ERROR: FOO
Stacktrace:
[1] error(::String) at ./error.jl:10033
[2] foo() at /home/liozou/Documents/Boston/randomtests/callbacklatest.jl:10007
[3] top-level scope at none:10000
julia> error("REPL")
ERROR: REPL
Stacktrace:
[1] error(::String) at ./error.jl:10033
[2] top-level scope at none:10000
julia> Base.update_stackframes_callback[] = identity
identity (generic function with 2 methods)
julia> error("REPL")
ERROR: REPL
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] top-level scope at none:0
julia> include("/home/liozou/Documents/Boston/randomtests/callbacklatest.jl")
WARNING: replacing module FuzzLineno.
Main.FuzzLineno
julia> error("REPL")
ERROR: REPL
Stacktrace:
[1] error(::String) at ./error.jl:10033
[2] top-level scope at none:10000 So, no |
With an error directly in the package: module FuzzLineno
function updater(frame::Base.StackFrame)
return Base.StackFrame(frame.func, frame.file, frame.line+10000, frame.linfo, frame.from_c, frame.inlined, frame.pointer)
end
Base.update_stackframes_callback[] = updater
error("BAZ")
end It yields julia> include("/home/liozou/Documents/Boston/randomtests/callbacklatest.jl")
ERROR: LoadError: BAZ
Stacktrace:
[1] error(::String) at ./error.jl:10033
[2] top-level scope at none:10000
[3] include at ./boot.jl:10317 [inlined]
[4] include_relative(::Module, ::String) at ./loading.jl:11040
[5] include(::Module, ::String) at ./sysimg.jl:10029
[6] include(::String) at ./client.jl:10394
[7] top-level scope at none:10000
in expression starting at /home/liozou/Documents/Boston/randomtests/callbacklatest.jl:6 |
This seems intuitive to me: replacing the call to |
I'm not sure I got the module FuzzLineno
function updater(frame::Base.StackFrame)
return Base.StackFrame(frame.func, frame.file, frame.line+10000, frame.linfo, frame.from_c, frame.inlined, frame.pointer)
end
__init__() = error("BAZ")
Base.update_stackframes_callback[] = updater
end I still obtain the desired julia> include("/home/liozou/Documents/Boston/randomtests/callbacklatest.jl")
ERROR: LoadError: InitError: BAZ
Stacktrace:
[1] error(::String) at ./error.jl:10033
[2] __init__() at /home/liozou/Documents/Boston/randomtests/callbacklatest.jl:10005
[3] include at ./boot.jl:10317 [inlined]
[4] include_relative(::Module, ::String) at ./loading.jl:11040
[5] include(::Module, ::String) at ./sysimg.jl:10029
[6] include(::String) at ./client.jl:10394
[7] top-level scope at none:10000
during initialization of module FuzzLineno
in expression starting at /home/liozou/Documents/Boston/randomtests/callbacklatest.jl:6 All in all, it really seems to me that |
I'll be getting on a plane soon, but I meant something like this (untested): module FuzzLineno
function updater(frame::Base.StackFrame)
return Base.StackFrame(frame.func, frame.file, frame.line+10000, frame.linfo, frame.from_c, frame.inlined, frame.pointer)
end
foo() = error("oh no")
function __init__()
Base.update_stackframes_callback[] = updater
end
end # module
julia> using FuzzLineno; FuzzLineno.foo() With precompilation (and that's an essential part of the problem we're discussing), module-body code only runs when the package is precompiled; when you The world age problem is likely to be as follows: the Of course, this could be a case where we don't care if the updater runs properly in such cases---because of the |
Your explanation makes sense... But I still can't obtain a faulty stacktrace with your example. I'll investigate into that when I have time, but I'm afraid I won't really have any before a few days. |
Can we just add back the |
It should be clear that it would be very bad if |
No, the point of it is to have a mutable 1-element container. |
I can add the |
Yes, sounds fine. |
Fine with me. |
OK so thank you all folks for your patience and sorry for having been so stubborn about the |
I can squash the commits if you want. |
Travis failure is root on the CI machine shutting down the machine, only on x86-32 Linux. |
(cherry picked from commit ba209d5)
(cherry picked from commit ba209d5)
Fix #28442.
This PR makes long stacktrace printing work again by correcting #28328 in the case of stacktraces of length greater than
BIG_STACKTRACE_SIZE = 50
.However, it breaks the non-official API introduced in that same PR revolving around the possible modification ofupdate_stackframes_callback[]
by external packages to fix the number of lines:Before this PR,update_stackframes_callback[]
was called only once, and updated in-place the entire vector ofTuple{StackFrame, Int}
whereInt
was the number of repetitions of the stackframe.After this PR,update_stackframes_callback[]
is called on each stackframe individually and should return a corrected stackframe.I also removed the use ofinvokelatest
sinceupdate_stackframes_callback
is aRef
, thusupdate_stackframes_callback[]
should automatically refer to the latest implementation of the updater. Feel free to correct me if I was wrong here. I would also like to remove thetry...catch
block around the updater call but since it was in the original PR, I kept it here.Incidentally, this fixes a minor issue where cycles that were repeated only once were not shrunk.
@timholy I think you should review this.
I checked the code from Revise which is broken by this new API but it seems only a few lines need to be changed in the definition ofupdate_stacktrace_lineno!
(namely: get rid of the loop and returnt
instead of modifyingtrace[i]
in-place) so that shouldn't be too bad.EDIT: see #28453 (comment)