-
Notifications
You must be signed in to change notification settings - Fork 10
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
Add UnwrapFrame: reverse of Wrap #102
Conversation
frame.go
Outdated
return Frame{}, err, false | ||
} | ||
|
||
frames := runtime.CallersFrames([]uintptr{e.pc}) |
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 is a good place to start, just wanted to think about a couple of things:
- should we return a frame with the func/line as frields, or should we instead return a type that wraps the
pc
and lazily pulls the frame as needed. - I wonder if there's a performance benefit in
CallersFrames
when multiple pcs are passed.
I don't think this function is expected to be used in any performance-sensitive paths, so don't think it's worth optimizing for it yet.
In the future, if batching is faster, we may want to switch to doing that internally (in which case we couldn't use this function), which is still OK.
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.
Yeah, I was unsure about expose Frame with decoded information or just a hidden pc that loads the frame lazily.
I started with exposed frame since that's what we had for traceFrame, and it's the most "here's just data,"
but I think lazy decoding leaves room for frame iteration in the future, should we want that.
I wonder if there's a performance benefit in CallersFrames when multiple pcs are passed.
Doesn't look like it. https://cs.opensource.google/go/go/+/refs/tags/go1.22.2:src/runtime/symtab.go;l=91
You pay for one caller at a time, although it pays for two frames for the initial Next()
call if there are multiple frames.
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.
Re: lazy vs eager, I'm leaning towards the eager approach like you have it for simplicity. That said, what do you think our own Frame type vs exposing the runtime type?
Re: performance benefit of batching,
I wrote a benchmark for an error with ~6 frames and compared a for loop that builds up []uintptr
of PCs, and uses CallerFrames
to then generate a []string
of function names, and compared doing the same using UnwrapFrame
. The time is similar, though it is more allocation-friendly to batch CallerFrames
calls,
BenchmarkUnwrapBatch 2007859 578.3 ns/op 464 B/op 3 allocs/op
BenchmarkUnwrapFunc 1301568 924.6 ns/op 1552 B/op 13 allocs/op
So if we want to optimize the tree building, batching may be worth it, but I don't think that's worth changing the API for.
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.
- should we return a frame with the func/line as frields, or should we instead return a type that wraps the
pc
and lazily pulls the frame as needed.
Are there any plans to not use PCs in the errtrace implementation?
In https://go.dev/issue/63358, a few people have said that []uintptr
seems like a good way to represent a trace, though https://go.dev/issue/60873 may be more appropriate for that discussion.
Personally, I do not mind creating runtime.Frame
s myself from one or more uintptr
. 🤔 I think I just want some way to read the unexported pc
field.
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.
Are there any plans to not use PCs in the errtrace implementation?
No, no plans to use non-PC sources of information.
[]uintptr
for a trace seems like a good idea, but those discussions do not seem close to resolving.
I think we could expose these frames as-is for now, and add a means of exposing the []uintptr
in the future.
That may happen anyway if we implement #104.
frame.go
Outdated
|
||
// Frame is a single frame in an error trace | ||
// identifying a site where an error was wrapped. | ||
type Frame struct { |
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.
optional: should we use runtime.Frame
here?
Overall, I like using For exposing frames, I think the current API is fine, though wanted to capture alternatives and tradeoffs. Do you think there's much value in our own type over the runtime type? A couple of scenarios it could be useful:
|
Per discussion in #71, add an UnwrapFrame function that wraps the outermost errtrace frame from an error. As a result of this change, it's possible to implement buildTraceTree or a variant of it using exported functionality exclusively. This makes it possible for users to use a different trace formatting mechanism than the trees generated by FormatString. Resolves #71
Turn the example into an example test, fix the `:=` versus `=`, and demonstrate how to use `errors.Unwrap` for the full trace.
Matches runtime.Frame.
My main reason for that was to get the custom |
Uses runtime.Frame instead of a custom Frame type. Also exposes the PC as a result.
I've added a runtime.Frame-based version in a new commit just to see how it looks. |
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.
My main reason for that was to get the custom
String()
function, but I'm not tied to that. We can useruntime.Frame
. That has a side benefit of exposing thepc
as well for folks like #102 (comment).
Yeah there's a few options here:
- Some underlying method on the error that returns the
pc uintptr
directly (or maybe[]uintptr
), that can be used as an interface that other types can implement - Method to extract a single frame like we have that could work with other types if they match an interface
- Whether the frame is our own type or the runtime type
I'm leaning more towards exposing the pc uintptr
directly in the future if there's some standard method, and using the runtime.Frame
for now to minimize code in errtrace, but don't feel strongly either way (our own type vs runtime type).
Excellent. That's what we're doing now! |
Nice, my stamp was from before I saw the latest commit, but I'm fine with either with a slight preference for |
Per discussion in #71, add an UnwrapFrame function
that wraps the outermost errtrace frame from an error.
As a result of this change,
it's possible to implement buildTraceTree or a variant of it
using exported functionality exclusively.
This makes it possible for users to use a different trace formatting
mechanism than the trees generated by FormatString.
Resolves #71