-
-
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
Stacktrace gripes #33065
Comments
It is a bit hard to understand what is rhetorical questions and what are actual questions so just throwing some answers out there.
Everything in Base is written like this. Writing the full path would be kinda annoying but perhaps there could be a better way to write it.
It isn't, see #25467. Why would the
This is #26314. There is always a contention between showing the truth and what most users would find useful. Personally, I think that hiding stuff like keyword argument wrapper functions by default is the right thing to do. |
Ah, didn't think of multiline input, thanks. Makes sense to print it along with the frame then. The [4] could maybe go?
I agree that it would be annoying to have full paths. Why not use |
It says that because inlining breaks the ability to show the type information. (Compare the output for the non-inlined calls, there you see the argument types.)
Julia's dispatch sometimes goes pretty deep. It's both a strength (it's the foundation of abstraction) and a curse (makes things complicated). Not showing the truth has obvious downsides. There do seem to be a couple of actionable items:
|
#32763 doesn't actually help with stacktraces. But it could probably be extended to do that. |
Can't we keep keyword calls in the stacktrace object but just not print them? |
Yes, we are for example already scrubbing the backtrace before printing it: Lines 82 to 101 in cef655a
|
I see, thanks! How about [2] sqrt(inlined) at ./math.jl:492 to make that more explicit?
Which practical downsides would that be in this particular example, if we, say, replaced
by
or similar ? Sure, it's nice to know that
I think the proper fix here is to use relative paths, which would also fix the consistency with base, see comment above. To be clear, this thread is not entirely about me complaining idly. If there's a relative consensus (or at least an indication that a PR might be merged) on the easy items proposed above (inline, REPL execution counts), I can do a PR. I can try to do relative paths also, with no promises on timeframe. Kwargs and include are above my paygrade. |
All good points. I have definitely seen folks be turned off by the complexity of our stacktraces, this seems to be a necessary discussion.
An even more radical proposal: suppress type information for all calls. That would solve problems like this: julia> @noinline foo(A) = error("fake")
foo (generic function with 1 method)
julia> foo(img)
ERROR: fake
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] foo(::ImageMetadata.ImageMeta{FixedPointNumbers.Normed{UInt16,14},3,AxisArrays.AxisArray{FixedPointNumbers.Normed{UInt16,14},3,SharedArrays.SharedArray{FixedPointNumbers.Normed{UInt16,14},3},Tuple{AxisArrays.Axis{:x,StepRangeLen{Quantity{Float64,𝐋,Unitful.FreeUnits{(μm,),𝐋,nothing}},Base.TwicePrecision{Quantity{Float64,𝐋,Unitful.FreeUnits{(μm,),𝐋,nothing}}},Base.TwicePrecision{Quantity{Float64,𝐋,Unitful.FreeUnits{(μm,),𝐋,nothing}}}}},AxisArrays.Axis{:l,StepRangeLen{Quantity{Float64,𝐋,Unitful.FreeUnits{(μm,),𝐋,nothing}},Base.TwicePrecision{Quantity{Float64,𝐋,Unitful.FreeUnits{(μm,),𝐋,nothing}}},Base.TwicePrecision{Quantity{Float64,𝐋,Unitful.FreeUnits{(μm,),𝐋,nothing}}}}},AxisArrays.Axis{:time,StepRangeLen{Quantity{Float64,𝐓,Unitful.FreeUnits{(s,),𝐓,nothing}},Base.TwicePrecision{Quantity{Float64,𝐓,Unitful.FreeUnits{(s,),𝐓,nothing}}},Base.TwicePrecision{Quantity{Float64,𝐓,Unitful.FreeUnits{(s,),𝐓,nothing}}}}}}},Dict{String,Any}}) at ./REPL[32]:1
[3] top-level scope at REPL[33]:1 I'm going to start trying to notice whether I find the type information in the stack trace useful in my own debugging. We do have other tools for this available now, it may be less important than it once might have been. |
That's a very nice idea, which would reduce (but not completely solve) the problem of huge type names, aka the StridedArray of doom (cf #32470). Should we just print the argument name instead? ie |
I find type-information incredibly useful and would surely miss them. They answer the question how/why did I get there and a MethodError without type-information is useless. |
The |
This is an excellent point. I've often wished that we would scrub In general we won't be able to please everybody though with a single default; some parts of I think a good approach would be an interactive user interface for the REPL which allows the last stack trace to be re-printed with increasing or decreasing verbosity based on some keyboard commands. Then if we have a set of heuristics which label frames with a verbosity, something like from most to least verbose:
the REPL and other interfaces like Juno can use a consistent shared ordering in their interfaces — much the same way that all editors agree on the set of julia LaTeX completions because the REPL defines a standard. |
There are definitely several good points here.
I think the problem is more on the opposite end --- that repl inputs aren't labeled so you can't easily tell which input it's referring to. Maybe we should change the default prompt to be more like the numbered prompts in IJulia? We could improve the Agreed on keyword arguments; I'll think about it. I'm also sympathetic to not showing argument types by default; they can indeed be way too verbose. I think I would be ok with it if there were something like ^Q to easily show the types when wanted. |
@antoine-levitt to add a minimal version of your last example: a.jl: include("b.jl") b.jl: error("Boom") Currently in 1.2 we have
Here is a mockup showing a re-imagined version of this stack trace: Here I've done the following:
This is just a hand-edited interface mockup. If anyone wants to pursue this with code I'd suggest trying the |
Speculation: We could have a REPL mode for interactively exploring past program events, including stack traces. That might be overkill for stack traces alone, but I've also wanted such a UI for exploring log events. These seem a bit similar because they retroactively replay some captured state from a previous execution. It's a kind of debugging experience which is different from interacting with the program live but has some advantages. |
That's a great idea! Just manually inlining the function, function myinclude(fname::AbstractString)
mod = Main
path, prev = Base._include_dependency(mod, fname)
for callback in Base.include_callbacks # to preserve order, must come before Core.include
Base.invokelatest(callback, mod, path)
end
tls = Base.task_local_storage()
tls[:SOURCE_PATH] = path
local result
try
result = ccall(:jl_load_, Any, (Any, Any), mod, fname)
finally
if prev === nothing
delete!(tls, :SOURCE_PATH)
else
tls[:SOURCE_PATH] = prev
end
end
return result
end I get
which is a huge win! Perhaps we can do edit: hm, the "top-level scope at ..." already happens with master julia. Can we remove "in expression starting at" then?
That always felt very '70s to me... maybe that's just me, but I don't type that many things at the REPL, and it's always pretty easy to know what it refers to. |
agreed, but I don't think this proposal changes that: julia> foo(::Float64) = 1
foo (generic function with 1 method)
julia> foo(::Int16) = 2
foo (generic function with 2 methods)
julia> @noinline bar(x) = foo(x)
bar (generic function with 1 method)
julia> bar(1.0f0)
ERROR: MethodError: no method matching foo(::Float32)
Closest candidates are:
foo(::Int16) at REPL[2]:1
foo(::Float64) at REPL[1]:1
Stacktrace:
[1] bar(::Float32) at ./REPL[5]:1
[2] top-level scope at REPL[6]:1 While here you might say, "well, it was nice knowing that |
@c42f, I like the tabular display a lot. It would be good to have package name column as well. See #33047 wherein it's noted that we might want to install packages in locations where the package name is not part of the path; I don't actually think we should do that, but displaying the package name prominently rather than hidden away in the path name would be good. |
I think one thing that needs to be decided is whether or not we print always want full paths. If we do, then printing the package is less important. If we don't, we could do something like Juno, or have a table display with package name and path relative to the package root. That meshes well with the discussion in #25375 to use different colors for different levels of (user, package, stdlib, base). Things like (invented example)
with different colors for the base, linalg and user code. It does make it harder to get to the source, but we have ctrl-Q for that. We could also have a clean but conservative choice in base (full paths and no tables), and more fancy display (table, packages and relative paths) in a package. |
Full paths are the most useful thing in any stack trace as far as I'm concerned: I click on them to open up the location where an error happened all the time, I never use ctrl-Q. It would be fine to print paths on the next line though, which might improve readability. |
When you say click, do you mean in an IDE? Or do you have a terminal that calls $EDITOR when you click on it? How do you deal with Base, which prints as |
Terminal that opens files in my editor. Guess what directory I’m usually in when running Julia? |
While it is tempting to try to overhaul the stack trace printing, I want to point out that it has worked quite well for some time and that I think we can make significant improvements with targeted incremental changes. For example, #33087 is one step. Hiding keyword wrapping functions is another. |
Agreed: include, kwargs and fixing the paths will fix the most important issues. Doesn't mean we can't dream of the ideal stacktrace :-p |
It is also a bit interesting to note that we do a lot of these things (hiding the truth for a better experience) in the debugger already. Automatically stepping through keyword wrapper functions, rewriting function names from the lowered versions, automatically unwrapping I am heavily in favor of optimizing for the "99%" and then having "escape hatches" for those that need to see the ugly truth. |
The only word of caution I have here is that sometimes the only information you get from a user is a stacktrace and
I wasn't meaning the type information in the |
It does include my favorite terminal, though, so let's do it! 😁 But seriously, maybe we could print the link/file on the next line but detect terminals that support hyperlinks and emit hyperlinks in terminals that support it? |
Plus, let's tell @egmontkob that |
FWIW I've just enabled |
I just tried it too... it's quite nice. I guess the next question is can we enable this conditionally in the terminals that support it? Or is it harmless in terminals that don't support it (I doubt it). |
As per this, sane terminals should just print the link text and not show the URL at all (which is true for the couple of terminals I tried). We'll definitely need to figure out a good fallback. |
I'm aware of the following terminals that don't at least silently ignore these escape sequences, but rather result in visual corruption:
|
Perhaps we can heuristically autodetect this feature by attempting to print a hyperlink and checking the cursor position afterward? (Eg cursor position can be read using |
Querying the cursor position is an asynchronous operation. I loathe those, it's impossible to reliably code against them. You issue the sequence that queries the position. And then what? You wait for the response, with a timeout I presume, since probably not all terminals respond (I really don't know). How long should that timeout be? If you ssh to a server on the opposite side of the Earth, the fastest theoretical roundtrip time (going around the Earth with the speed of light – assuming you can't go straight across the Earth :)) is about 0.13 seconds. Plus the path is longer, and is full of devices that add latency. What if you receive other data before the response, within the timeout? I mean keypresses from the user, either typed ahead before your app started up, or pressed within an interactive app just when it was about to query the cursor position? Will your application properly act on them while waiting for the response? What if you reach the timeout? You need to go on. What if the response arrives later? Will all the parts of your interactive application, all the way from simple cases like a Mind you, unfortunately I don't know anything about Julia, nor the exact app or utility that you're adding this feature to (I know I should take a look at it :)) so my questions don't necessarily apply exactly as I phrased them, but some things along these lines (including user reports about heuristic behavior, unreproducible breakages) would sure cause lots of headaches for you. IMHO go for a much simpler approach. Maybe a config option, maybe tri-state with an "autodetect" option. You can whitelist VTE by looking at What I'd probably do: Just go on, enable by default, and give a config option for users to disable. Use BEL as terminator in order to please Konsole users (and change to the proper ST a few years after Konsole fixes their bug). The rest is pretty much VTE 0.28. Those users could fix it for themselves by disabling the option, and in the same time you'd do a favor by raising awareness of them using an ancient emulator and push them towards upgrading to a new one. By VTE 0.28 and other possible broken terminals we're really talking about the long tail, not the mainstream ones. (YMMV. I know that not everyone agrees with this pushy appraoch, it's absolutely reasonable for an app developer to try to stay safer and care about the long tail.) |
Is that logic available in a library, or are there sample implementations anywhere? |
(Which one do you mean? The unreliable asynchronous one, or guessing based on the environment variables? Anyway, for both of them, my answer is:) Not that I'm aware of. |
I just thought a library might aid implementation. Others are getting interested in the feature, too. |
@egmontkob thanks; I think a whitelist sounds simple and sane. The "correct" solution would seem to be negotiating terminal capabilities when the connection is intiated. But good luck getting that deployed in the terminal ecosystem which has 50 years of history or so. I also found the discussion of autodetection over at kovidgoyal/kitty#68 interesting. |
For this set of (slightly modified) frames above:
The
|
Another improvement I think someone could attempt is to remove common frames, especially those above the point where the exception was caught. This may be easier to exemplify with the new catch-stack, but could apply to anything. (there's already a hard-coded filter doing this here to cut it off for the REPL, so that could be removed, and the effect may be more dramatic for code in files): Currently:
Could try to remove the common frames, for something like:
Implementer's note: in the past, I've posited that we could handle this by only unwinding the stack until we hit the stack frame that contains the try/catch block. |
It would be good to use color more intelligently in error messages. Descriptive text should be a different colour to types so you can easily parse ambiguity errors and similar. Line numbers and paths should have different colors again so you can easily find them. |
In version 1.3.0 on Win_64 platform, I get this:
It must be a hard-coded path since this machine does not even contain a D drive. |
To record where I got to with I think this boils down to implementing the following TODO but that's about as far as I got: Line 560 in 68eaec6
|
@jkrumbiegel made some cool modifications to error printing in the slack group. I hope this ends up on Master. To improve upon his work, we should remove the full file path and give a hyperlink.
If not we may be able to decrease the characters font so they take up less space
|
Here's the experimental package mentioned above that I made to enhance the visual clarity of stacktraces https://github.com/jkrumbiegel/ClearStacktrace.jl |
Could this (module info for inlined frames) be raised to its own issue? I'm willing to try implementing it, but I haven't poked around the C++ side of Julia yet. |
@BioTurboNick ok, see #41031 |
Stacktraces are in general pretty hard to read. Here's a list of gripes. I'm not posting this as separate issues because I don't know what is bug and what is feature here. All tests are on 1.3alpha from an official release. This is related but not the same as #25375, which is about formatting.
Let's start small:
./math.jl
?Now a stdlib:
include
d user files:Failing tests:
I'm a bit confused about what's going on here to be honest...
I did not try with the other ways of including code, macros, etc. So, to sum up, in subjective increasing order of seriousness
./
path for baseinclude
are very hard to parse. This is possibly the most controversial issue because enhancing this would mean "lying" to the user, ie treatinginclude
as a special functionThe text was updated successfully, but these errors were encountered: