-
Notifications
You must be signed in to change notification settings - Fork 53
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
fix: Correct errors.WithStack behaviour #984
Changes from 5 commits
357a46e
cf3f478
4e30081
5ae9133
ada8cfc
dae4290
f35f269
bf3d13d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,14 +38,20 @@ func NewKV(key string, value any) KV { | |
// pairs provided. | ||
// | ||
// A stacktrace will be yielded if formatting with a `+`, e.g `fmt.Sprintf("%+v", err)`. | ||
// This function will not be inlined by the compiler as it will spoil any stacktrace | ||
// generated. | ||
//go:noinline | ||
func New(message string, keyvals ...KV) error { | ||
return newError(message, keyvals...) | ||
return newError(message, 1, keyvals...) | ||
} | ||
|
||
// Wrap creates a new error of the given message that contains | ||
// the given inner error, suffixing any key-value pairs provided. | ||
// This function will not be inlined by the compiler as it will spoil any stacktrace | ||
// generated. | ||
//go:noinline | ||
func Wrap(message string, inner error, keyvals ...KV) error { | ||
err := newError(message, keyvals...) | ||
err := newError(message, 1, keyvals...) | ||
err.inner = inner | ||
return err | ||
} | ||
|
@@ -54,19 +60,30 @@ func Is(err, target error) bool { | |
return errors.Is(err, target) | ||
} | ||
|
||
// This function will not be inlined by the compiler as it will spoil any stacktrace | ||
// generated. | ||
//go:noinline | ||
func WithStack(err error, keyvals ...KV) error { | ||
return withStackTrace(err.Error(), keyvals...) | ||
return withStackTrace(err.Error(), 1, keyvals...) | ||
} | ||
|
||
func newError(message string, keyvals ...KV) *defraError { | ||
return withStackTrace(message, keyvals...) | ||
// This function will not be inlined by the compiler as it will spoil any stacktrace | ||
// generated. | ||
//go:noinline | ||
func newError(message string, additionalStackDepthToSkip int, keyvals ...KV) *defraError { | ||
return withStackTrace(message, additionalStackDepthToSkip+1, keyvals...) | ||
} | ||
|
||
func withStackTrace(message string, keyvals ...KV) *defraError { | ||
// This function will not be inlined by the compiler as it will spoil any stacktrace | ||
// generated. | ||
//go:noinline | ||
func withStackTrace(message string, additionalStackDepthToSkip int, keyvals ...KV) *defraError { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: I see why you've introduce this new parameter but I think we should avoid it. If we add error creation functions in the future, it requires knowledge of what this means which I think shouldn't be necessary. What I would do instead is remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That wouldn't protect against the same problem being introduced by new functions, and hides the bug in the same way that it was hidden before. The new parameter forces visibility of the relationship between parent-child functions and the resultant number. It also gives us a natural route to expose this parameter if we ever find the need to.
This knowledge is required either way if they want a correct stacktrace (unless it accidentally has the same depth, which is exactly what caused this bug in the first place) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cognitive requirements are much lower if we don't need to calculate the depth where we are at when writing the function. Asking to have the
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not for new public functions - they would require new tests that explicitly test the stacktrace. Assuming that those will always be written is wishful thinking IMO.
The cognitive requirements exist either way, they were just hidden in the old version. Which is pretty much the only reason the bug existed (plus poor testing ofc - somewhat validating my 'wishful thinking' point above).
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I would say adding the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The thing is that although this is localized for the purpose of the discussion, I see the outcome of discussions like this to have a very broad impact as it will influence our (at least mine) approach to other implementations. I also think about new devs looking at this package (since it's small and approachable) to observe our coding style and them seeing either the opinionated approach or the flexible one and then copying the observed approach for their own work. This is why I'm pushing back and I'm not convinced by your explanation (doesn't mean it's not valid). If someone else prefers your approach, I'll be happy to approve the PR :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gone through this thread a few times now, read the old code, the new PR code, the references In the majority of cases I prefer "explicit" convention that is documented, compared to complete freedom, however this package has a fairly small surface area, and although the cognitive load is certainly higher to understand the skipStack parameters, any time you touch the code in this package, it's a manageable price, for the clarity it provides. So I think its OK to keep as is. However, I also think that its within scope to drop There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can get on board with that change. Extra nitpick: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I'll add some docs.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've also removed newError |
||
stackBuffer := make([]uintptr, MaxStackDepth) | ||
|
||
// Skip the first X frames as they are part of this library (and dependencies) and are | ||
// best hidden. | ||
length := runtime.Callers(4, stackBuffer[:]) | ||
// best hidden, also account for any parent calls within this library. | ||
const depthFromHereToSkip int = 2 | ||
length := runtime.Callers(depthFromHereToSkip+additionalStackDepthToSkip, stackBuffer[:]) | ||
stack := stackBuffer[:length] | ||
stackText := toString(stack) | ||
|
||
|
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.
todo: If
newError
will be kept, would be nice to have a line of documentation for theadditionalStackDepthToSkip
parameter.