Skip to content

Add format_trace(impl fmt::Write, PrintFmt) #265

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

Closed
wants to merge 2 commits into from

Conversation

SimonSapin
Copy link

This is similar to write!(stream, "{:?}", Backtrace::new()), but avoids memory allocation. This can be useful in a signal handler, where allocation may cause deadlocks.

@SimonSapin
Copy link
Author

Servo has a signal handler that prints a backtrace on segfaults. Currently it uses println!("{:?}", Backtrace::new) but that seems to cause deadlocks, possibly in the memory allocator: servo/servo#24881. Using lower-level API instead to avoid allocating seems to fix the deadlocks, but takes a fair amount of code that is not at all Servo-specific and could be useful to other projects.

This is similar to `write!(stream, "{:?}", Backtrace::new())`, but avoids memory allocation.
This can be useful in a signal handler, where allocation may cause deadlocks.
@SimonSapin
Copy link
Author

cargo fmt --all -- --check fails, but it also does on master.

bors-servo pushed a commit to servo/servo that referenced this pull request Nov 27, 2019
Avoid allocation in the signal handler to fix a deadlock

Fixes #24881
CC rust-lang/backtrace-rs#265
bors-servo pushed a commit to servo/servo that referenced this pull request Nov 27, 2019
Avoid allocation in the signal handler to fix a deadlock

Fixes #24881
CC rust-lang/backtrace-rs#265
bors-servo pushed a commit to servo/servo that referenced this pull request Nov 27, 2019
Avoid allocation in the signal handler to fix a deadlock

Fixes #24881
CC rust-lang/backtrace-rs#265
bors-servo pushed a commit to servo/servo that referenced this pull request Nov 27, 2019
Avoid allocation in the signal handler to fix a deadlock

Fixes #24881
CC rust-lang/backtrace-rs#265
Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a reasonable feature to me, thanks for this!

///
/// This is similar to `write!(stream, "{:?}", Backtrace::new())`,
/// but avoids memory allocation.
/// This can be useful in a signal handler, where allocation may cause deadlocks.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unfortunately technically isn't quite true because the underlying implementation almost always allocates memory in one way or another. While this avoids all Rust-based allocations, the backtrace unwinder/etc, if called for the first time will allocate memory here.

In general nothing in this crate is suitable for usage in a signal handler right now unfortunately.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see.

For what it’s worth using something like this PR instead of Backtrace::new() in Servo’s signal handler does seem to fix a deadlock, presumably through reducing the number of allocations. (Or at least I couldn’t reproduce it after running the test case a hundred times, whereas with Backtrace::new() I would reliably reproduce within ten runs.) But it’s quite possible that there’s still another deadlock waiting to happen and maybe we should remove that handler entirely.

I’ll remove mention of signal handler in these doc-comments. What else do you think could be a good use case for this new feature? In theory this is doing less work and might be more efficient than creating a Backtrace only to print it and immediately drop it, but I don’t know if the difference has a practical impact.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it's just a deadlock waiting to happen. The underlying implementation of backtraces are not at all guaranteed to not malloc, but they try not to to be efficient. Backtrace::new() is guaranteed to allocate, where this API as-is just allocates some of the time, not all the time.

Getting a backtrace from a signal handler is a really tricky thing to do and generally not covered by this crate.

TBH I'm not sure what a use case for this feature is, folks are generally ok with the mild cost that Backtrace::new has

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, my motivation for this PR was not the CPU time cost but trading a deadlock I could reliably reproduce for a possible one I haven’t seen happen. (Knowing this still isn’t a fully correct solution.)

If the only use case for this is incorrect and something the project doesn’t want to cover, maybe it shouldn’t be included? Even though based on what this code does it seems like within this crate would be the right location.

I’m OK with closing this PR in that case.

/// This function strives to never panic, but if the `cb` provided panics then
/// some platforms will force a double panic to abort the process. Some
/// platforms use a C library which internally uses callbacks which cannot be
/// unwound through, so panicking from `cb` may trigger a process abort.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the docs here include an example of how to call this?

/// platforms use a C library which internally uses callbacks which cannot be
/// unwound through, so panicking from `cb` may trigger a process abort.
#[cfg(feature = "std")]
#[inline(never)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind throwing a small comment on this explaining why it's inline(never)?


impl<W: std::io::Write> std::fmt::Write for Utf8<W> {
fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
self.0.write_all(s.as_bytes()).map_err(|_| std::fmt::Error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind backing this out by the time this lands?

Useful for context though!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean removing changes to this file entirely? Or should they be a separate example program?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh just in the sense that this example file was intended to just print one backtrace, but this might make a good # Examples section for the new API?

@alexcrichton
Copy link
Member

Ok I'm gonna close this due to the discussion at #265 (comment), but perhaps clarifying in the README that this is not appropriate for usage in signal handlers would be good to indicate!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants