-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Expose Frames Iterator for the Backtrace Type #78299
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @m-ou-se (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
r? @KodrAus |
cc @rust-lang/project-error-handling |
Yep! These are the types I had in mind. So a few things we'll need to do are:
Once we're ready to go:
|
I think returning an empty slice makes sense if the backtrace hasn't been captured. One thing I'm not sure about is whether the captured frames need to be resolved. Or can the |
My expectation is that it will need to trigger the frame resolution if it hasn't already happened. |
@yaahc Sorry, I should've been more precise. I meant returning an empty slice makes sense if backtraces are unsupported. |
library/std/src/backtrace.rs
Outdated
pub fn frames(&self) -> &[BacktraceFrame] { | ||
let frames = match self.inner { | ||
Inner::Captured(c) => { | ||
let captured = c.lock().unwrap(); |
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 might be a bit of an issue for us. We might end up needing to wrap this up in something like:
pub struct Frames<'a> {
inner: Option<MutexGuard<'a, Capture>>,
}
impl<'a> AsRef<[BacktraceFrame]> for Frames<'a> {
fn as_ref(&self) -> &[BacktraceFrame] {
match self.inner {
Some(captured) => &*captured.frames,
None => &[],
}
}
}
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.
The reason being the lifetime of the frames we can borrow from that locked mutex won't be enough for us to return a reference to the slice of 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.
Thinking about this more, we probably don't want to hand out a quiet borrow to locked state, otherwise we might create the potential for deadlocks. Instead, we could come up with something like this:
pub struct Frames {
inner: Vec<BacktraceFrame>,
}
impl<'a> AsRef<[BacktraceFrame]> for Frames<'a> {
fn as_ref(&self) -> &[BacktraceFrame] {
&self.inner
}
}
And materialize that Vec
under the lock.
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'm not quite sure what you mean by "materialize that Vec
under the lock". Is that just referring to the Frames
type holding on to a Vec<BacktraceFrame>
so that we don't need to manually deal with the lock?
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.
Ah sorry, yes I meant making our frames()
method look something like this:
impl Backtrace {
pub fn frames(&self) -> Frames {
if let Inner(captured) = self.inner {
Frames {
frames: captured.lock().unwrap().frames.clone(),
}
} else {
Frames {
frames: vec![],
}
}
}
}
So that we're not handing out a borrow from that lock
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 Frames
should have a lifetime tied to Backtrace
even if it currently isn't used. In the future a pure rust unwinder may avoid the lock. We could then generate the frames on the fly in the Iterator
impl for Frames
, avoiding a memory allocation.
We may want to look at the formatting use-case a bit more here too to figure out how we want to implement Maybe we could implement println!("{}", bt.frames().take(5).collect::<Backtrace>()); |
Sounds good to me. Separate question: It looks like tests go in the |
We'll want to think about the
Ah that
is equivalent to:
If that makes sense 🙂 |
I'm working through compiler errors that I get when running Dropping in a bunch of |
The job Click to see the possible cause of the failure (guessed by this bot)
|
The job Click to see the possible cause of the failure (guessed by this bot)
|
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.
Thanks for working on this @seanchen1991!
I think the Mutex<Capture>
is giving us a little trouble here. Would you be happy to try replacing it with a SyncLazy<Capture>
? That way we can safely return borrows from it without the potential for deadlocks. The first caller that attempts to access the capture will perform the work of capturing it.
if let Inner::Captured(captured) = &self.inner { | ||
let frames = &captured.lock().unwrap().frames; | ||
Frames { | ||
inner: frames.iter().map(|frame| frame.clone()).collect::<Vec<BacktraceFrame>>(), |
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.
inner: frames.iter().map(|frame| frame.clone()).collect::<Vec<BacktraceFrame>>(), | |
// NOTE: Frames are cloned to avoid returning references to them under the lock | |
// This could be avoided using a `SyncLazy` to initialize an immutable set of frames | |
inner: frames.iter().map(BacktraceFrame::clone).collect(), |
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.
So with this change, what's the way to access the type inside of the SyncLazy
? Before with the Mutex
we had
if let Inner::Captured(captured) = &self.inner {
let frames = &captured.lock().unwrap().frames;
What would we need to change the let frames
line into to access the underlying frames
?
library/std/src/backtrace.rs
Outdated
} | ||
|
||
#[unstable(feature = "backtrace_frames", issue = "79676")] | ||
impl<'a> Iterator for Frames<'a> { |
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 we'll want to avoid adding an Iterator
impl here just yet, because ideally we'll want to yield &'a BacktraceFrame
s, but we can't do that until we borrow the frames from the Backtrace
instead of cloning them.
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 in the future it should actually lazily resolve the frames as .next()
is called to reduce peak memory usage.
I've opened #80736 which avoids using |
cc @rust-lang/project-error-handling |
use Once instead of Mutex to manage capture resolution For rust-lang#78299 This allows us to return borrows of the captured backtrace frames that are tied to a borrow of the Backtrace itself, instead of to some short-lived Mutex guard. We could alternatively share `&Mutex<Capture>`s and lock on-demand, but then we could potentially forget to call `resolve()` before working with the capture. It also makes it semantically clearer what synchronization is needed on the capture. cc `@seanchen1991` `@rust-lang/project-error-handling`
☔ The latest upstream changes (presumably #80960) made this pull request unmergeable. Please resolve the merge conflicts. |
The job Click to see the possible cause of the failure (guessed by this bot)
|
@@ -476,3 +511,24 @@ impl RawFrame { | |||
} | |||
} | |||
} | |||
|
|||
#[unstable(feature = "backtrace_frames", issue = "79676")] | |||
impl<'a> AsRef<[BacktraceFrame]> for Frames<'a> { |
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 would be incompatible with lazy resolving of backtraces. Only an Iterator would be compatible.
Closing this and re-opening from a clean branch. |
Adds the ability to iterate over the frames of a
Backtrace
by exposing theframes
method.