-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Custom panic handlers in the standard library #30449
Comments
See rust-lang/log#67 for one usage example. |
Is there a good reason for the global panic handler to run even if the panic is #![feature(std_panic, recover)]
use std::panic;
fn main() {
panic::recover(|| {
panic!("hello");
}).unwrap_err();
} This prints |
There's some talk about this in the RFC PR, but the main reason is that it's very important for the handler to be able to take stack traces. In addition, the intended use cases for |
My biggest usecase for this is #1089, so I'd love a way to suppress the std panic handler, too. I would like to present my users with an error message, set an appropriate exit code, and be assured that desctructors ran without wrapping basically all my return values in Results when an error happens very rarely, is dependant on certain combinations of user input that can't be checked early/not very deep in the call chain. |
@shahn |
I'm trying to do something of the form: let orig_handler = panic::take_handler();
panic::set_handler(|_| ());
<an actual thing>
panic::set_handler(orig_handler); and getting the error
I tried taking ownership of the value inside the box with panic::set_handler(*orig_handler); but this gives the error
I feel like it should be easy to |
@samphippen This is ugly, but it works. #![feature(std_panic, panic_handler)]
use std::panic;
fn main() {
let orig_handler = panic::take_handler();
panic::set_handler(|_| ());
// <an actual thing>
panic::set_handler(move |info| (*orig_handler)(info));
} |
thanks! |
Further to this, I think there's a race or bug in the implementation. If this is the wrong place to report this issue please let me know, and I'll delete this comment and put it in the appropriate place. I'm aware this feature is unstable as well, but I figured I'd report some real world feedback anyway. My use case boils down to the following rust program: #![feature(panic_handler, recover, std_panic)]
use std::thread;
use std::panic::{self, recover};
fn main() {
let mut build = Vec::new();
for _ in 0..10 {
build.push(thread::spawn(|| {
let orig_handler = panic::take_handler();
panic::set_handler(|_| ());
let _res = recover(|| {
panic!("fail");
});
panic::set_handler(move |info| { (*orig_handler)(info) });
}));
}
let results: Vec<_> = build.into_iter().map(|jh| jh.join()).collect();
let all_passed = results.into_iter().all(|r| r.is_ok());
println!("{}", all_passed);
} When I run this, I get the following output:
(other runs produce more output, or garbled output, but always fewer than 10 total lines) Given that I'm running 10 threads, I'd expect to either 10 lines of output, or 0, but not two. Is this an incorrect usage of the API, or is this actually a bug I'm seeing? edit: versions
|
Having |
@samphippen The panic handler is a global resource. If multiple threads are messing with it at once without synchronization, you'll see that kind of behavior. You will want to take a different approach, possibly involving a handler that looks at a thread local which indicates if the panic should be suppressed. |
Now that |
@SimonSapin Those kinds of race conditions still exist if setting and taking were not separate actions. If multiple threads are adjusting a global resource at once without any synchronization at once, you'll see inconsistent behavior. For example, if the panic handlers were a stack, and each handler could choose whether or not to let the next handler run, the ordering of that stack would be inconsistent and so if any handler doesn't unconditionally allow the next handler to run, you'll see inconsistent behavior. @yberreby |
The issue with resetting an old handler is a problem - it's not great to force it to get wrapped in another closure every time. It seems like adjusting EDIT: Ah, it's actually a bit weirder since it's more like |
@sfackler Oh? I thought it had been. My mistake.
Couldn't all of this be solved rather easily, especially since
You usually don't want to have dependencies mess with a global resource, but the current approach encourages this behavior. If you don't want caught panics to be silent, you can always re-throw them and have them bubble up to the main thread, can't you? |
"Just one panic handler" sounds like a global resource to me. How would you configure it? It's absolutely true you don't want dependencies messing with the global handler whenever they want, but you absolutely do want it to be possible for dependencies to adjust the panic handler when asked. For example, you may want your EDIT: Ah, and as I mentioned in a previous comment, it is crucial that you be able to capture backtraces at the point that you "deal" with a panic, whether that be in a panic handler or elsewhere. |
@sfackler You're right, it should be part of "Just one panic handler" is a global resource, like, say, environment variables. However, with this approach, it's less problematic because Moreover, we already had a global panic handler. There needs to be one to provide logging in case of completely unhandled panic. It's not strictly needed for it to be writable, but it's convenient for the use-case you described (panic logging), and not having to wrap main in a giant |
From my perspective, there are a couple things here: First, Here are a couple of examples that the panic handler API is intended to help deal with. Could you go into a bit more detail on how these kinds of things would work with your proposal? I'm not sure I totally understand it. I'm writing an e.g. webserver, and use I notice some weird panics popping up, and switch to a panic handler that also captures backtraces of the panic source. I can't track down what's going on just from a backtrace, so I switch to a panic handler that will trigger a process abort for the panic I'm debugging so I can look at the core dump in a debugger. Note that the latter two of these cases require that the code that looks at the |
@sfackler Yes, synchronization is needed, and I argue that libstd should provide that synchronization by default. The API could take a closure that is given a the previous handler and return the new one, and is called with a lock held internally. Something like:
Looking at the source now, access to the private Note that the first thing |
@SimonSapin the issue is that that kind of API doesn't provide the proper sort of synchronization. For example, the race that @samphippen ran into is still present. Fundamentally, if you have multiple threads adjusting the panic handler concurrently, it seems to me like you need an external, implementation specific ordering to be imposed for the resulting handler to be consistent. What use cases were you imagining for which an atomic |
Nope, should be fine. I don't remember any deliberate decision not to include it. |
🔔 This issue is now entering its cycle-long final comment period for stabilization 🔔 |
The libs team discussed this issue during triage yesterday and the decision was to stabilize |
When the crate is compiled with |
I believe it will still be called, yeah - is that right @alexcrichton? I'll update the docs. |
Yes hooks are called regardless of the panic runtime |
This commit applies the FCP decisions made by the libs team for the 1.10 cycle, including both new stabilizations and deprecations. Specifically, the list of APIs is: Stabilized: * `os::windows::fs::OpenOptionsExt::access_mode` * `os::windows::fs::OpenOptionsExt::share_mode` * `os::windows::fs::OpenOptionsExt::custom_flags` * `os::windows::fs::OpenOptionsExt::attributes` * `os::windows::fs::OpenOptionsExt::security_qos_flags` * `os::unix::fs::OpenOptionsExt::custom_flags` * `sync::Weak::new` * `Default for sync::Weak` * `panic::set_hook` * `panic::take_hook` * `panic::PanicInfo` * `panic::PanicInfo::payload` * `panic::PanicInfo::location` * `panic::Location` * `panic::Location::file` * `panic::Location::line` * `ffi::CStr::from_bytes_with_nul` * `ffi::CStr::from_bytes_with_nul_unchecked` * `ffi::FromBytesWithNulError` * `fs::Metadata::modified` * `fs::Metadata::accessed` * `fs::Metadata::created` * `sync::atomic::Atomic{Usize,Isize,Bool,Ptr}::compare_exchange` * `sync::atomic::Atomic{Usize,Isize,Bool,Ptr}::compare_exchange_weak` * `collections::{btree,hash}_map::{Occupied,Vacant,}Entry::key` * `os::unix::net::{UnixStream, UnixListener, UnixDatagram, SocketAddr}` * `SocketAddr::is_unnamed` * `SocketAddr::as_pathname` * `UnixStream::connect` * `UnixStream::pair` * `UnixStream::try_clone` * `UnixStream::local_addr` * `UnixStream::peer_addr` * `UnixStream::set_read_timeout` * `UnixStream::set_write_timeout` * `UnixStream::read_timeout` * `UnixStream::write_Timeout` * `UnixStream::set_nonblocking` * `UnixStream::take_error` * `UnixStream::shutdown` * Read/Write/RawFd impls for `UnixStream` * `UnixListener::bind` * `UnixListener::accept` * `UnixListener::try_clone` * `UnixListener::local_addr` * `UnixListener::set_nonblocking` * `UnixListener::take_error` * `UnixListener::incoming` * RawFd impls for `UnixListener` * `UnixDatagram::bind` * `UnixDatagram::unbound` * `UnixDatagram::pair` * `UnixDatagram::connect` * `UnixDatagram::try_clone` * `UnixDatagram::local_addr` * `UnixDatagram::peer_addr` * `UnixDatagram::recv_from` * `UnixDatagram::recv` * `UnixDatagram::send_to` * `UnixDatagram::send` * `UnixDatagram::set_read_timeout` * `UnixDatagram::set_write_timeout` * `UnixDatagram::read_timeout` * `UnixDatagram::write_timeout` * `UnixDatagram::set_nonblocking` * `UnixDatagram::take_error` * `UnixDatagram::shutdown` * RawFd impls for `UnixDatagram` * `{BTree,Hash}Map::values_mut` * `<[_]>::binary_search_by_key` Deprecated: * `StaticCondvar` - this, and all other static synchronization primitives below, are usable today through the lazy-static crate on stable Rust today. Additionally, we'd like the non-static versions to be directly usable in a static context one day, so they're unlikely to be the final forms of the APIs in any case. * `CONDVAR_INIT` * `StaticMutex` * `MUTEX_INIT` * `StaticRwLock` * `RWLOCK_INIT` * `iter::Peekable::is_empty` Closes rust-lang#27717 Closes rust-lang#27720 cc rust-lang#27784 (but encode methods still exist) Closes rust-lang#30014 Closes rust-lang#30425 Closes rust-lang#30449 Closes rust-lang#31190 Closes rust-lang#31399 Closes rust-lang#31767 Closes rust-lang#32111 Closes rust-lang#32281 Closes rust-lang#32312 Closes rust-lang#32551 Closes rust-lang#33018
std: Stabilize APIs for the 1.10 release This commit applies the FCP decisions made by the libs team for the 1.10 cycle, including both new stabilizations and deprecations. Specifically, the list of APIs is: Stabilized: * `os::windows::fs::OpenOptionsExt::access_mode` * `os::windows::fs::OpenOptionsExt::share_mode` * `os::windows::fs::OpenOptionsExt::custom_flags` * `os::windows::fs::OpenOptionsExt::attributes` * `os::windows::fs::OpenOptionsExt::security_qos_flags` * `os::unix::fs::OpenOptionsExt::custom_flags` * `sync::Weak::new` * `Default for sync::Weak` * `panic::set_hook` * `panic::take_hook` * `panic::PanicInfo` * `panic::PanicInfo::payload` * `panic::PanicInfo::location` * `panic::Location` * `panic::Location::file` * `panic::Location::line` * `ffi::CStr::from_bytes_with_nul` * `ffi::CStr::from_bytes_with_nul_unchecked` * `ffi::FromBytesWithNulError` * `fs::Metadata::modified` * `fs::Metadata::accessed` * `fs::Metadata::created` * `sync::atomic::Atomic{Usize,Isize,Bool,Ptr}::compare_exchange` * `sync::atomic::Atomic{Usize,Isize,Bool,Ptr}::compare_exchange_weak` * `collections::{btree,hash}_map::{Occupied,Vacant,}Entry::key` * `os::unix::net::{UnixStream, UnixListener, UnixDatagram, SocketAddr}` * `SocketAddr::is_unnamed` * `SocketAddr::as_pathname` * `UnixStream::connect` * `UnixStream::pair` * `UnixStream::try_clone` * `UnixStream::local_addr` * `UnixStream::peer_addr` * `UnixStream::set_read_timeout` * `UnixStream::set_write_timeout` * `UnixStream::read_timeout` * `UnixStream::write_Timeout` * `UnixStream::set_nonblocking` * `UnixStream::take_error` * `UnixStream::shutdown` * Read/Write/RawFd impls for `UnixStream` * `UnixListener::bind` * `UnixListener::accept` * `UnixListener::try_clone` * `UnixListener::local_addr` * `UnixListener::set_nonblocking` * `UnixListener::take_error` * `UnixListener::incoming` * RawFd impls for `UnixListener` * `UnixDatagram::bind` * `UnixDatagram::unbound` * `UnixDatagram::pair` * `UnixDatagram::connect` * `UnixDatagram::try_clone` * `UnixDatagram::local_addr` * `UnixDatagram::peer_addr` * `UnixDatagram::recv_from` * `UnixDatagram::recv` * `UnixDatagram::send_to` * `UnixDatagram::send` * `UnixDatagram::set_read_timeout` * `UnixDatagram::set_write_timeout` * `UnixDatagram::read_timeout` * `UnixDatagram::write_timeout` * `UnixDatagram::set_nonblocking` * `UnixDatagram::take_error` * `UnixDatagram::shutdown` * RawFd impls for `UnixDatagram` * `{BTree,Hash}Map::values_mut` * `<[_]>::binary_search_by_key` Deprecated: * `StaticCondvar` - this, and all other static synchronization primitives below, are usable today through the lazy-static crate on stable Rust today. Additionally, we'd like the non-static versions to be directly usable in a static context one day, so they're unlikely to be the final forms of the APIs in any case. * `CONDVAR_INIT` * `StaticMutex` * `MUTEX_INIT` * `StaticRwLock` * `RWLOCK_INIT` * `iter::Peekable::is_empty` Closes #27717 Closes #27720 Closes #30014 Closes #30425 Closes #30449 Closes #31190 Closes #31399 Closes #31767 Closes #32111 Closes #32281 Closes #32312 Closes #32551 Closes #33018
This commit applies the FCP decisions made by the libs team for the 1.10 cycle, including both new stabilizations and deprecations. Specifically, the list of APIs is: Stabilized: * `os::windows::fs::OpenOptionsExt::access_mode` * `os::windows::fs::OpenOptionsExt::share_mode` * `os::windows::fs::OpenOptionsExt::custom_flags` * `os::windows::fs::OpenOptionsExt::attributes` * `os::windows::fs::OpenOptionsExt::security_qos_flags` * `os::unix::fs::OpenOptionsExt::custom_flags` * `sync::Weak::new` * `Default for sync::Weak` * `panic::set_hook` * `panic::take_hook` * `panic::PanicInfo` * `panic::PanicInfo::payload` * `panic::PanicInfo::location` * `panic::Location` * `panic::Location::file` * `panic::Location::line` * `ffi::CStr::from_bytes_with_nul` * `ffi::CStr::from_bytes_with_nul_unchecked` * `ffi::FromBytesWithNulError` * `fs::Metadata::modified` * `fs::Metadata::accessed` * `fs::Metadata::created` * `sync::atomic::Atomic{Usize,Isize,Bool,Ptr}::compare_exchange` * `sync::atomic::Atomic{Usize,Isize,Bool,Ptr}::compare_exchange_weak` * `collections::{btree,hash}_map::{Occupied,Vacant,}Entry::key` * `os::unix::net::{UnixStream, UnixListener, UnixDatagram, SocketAddr}` * `SocketAddr::is_unnamed` * `SocketAddr::as_pathname` * `UnixStream::connect` * `UnixStream::pair` * `UnixStream::try_clone` * `UnixStream::local_addr` * `UnixStream::peer_addr` * `UnixStream::set_read_timeout` * `UnixStream::set_write_timeout` * `UnixStream::read_timeout` * `UnixStream::write_Timeout` * `UnixStream::set_nonblocking` * `UnixStream::take_error` * `UnixStream::shutdown` * Read/Write/RawFd impls for `UnixStream` * `UnixListener::bind` * `UnixListener::accept` * `UnixListener::try_clone` * `UnixListener::local_addr` * `UnixListener::set_nonblocking` * `UnixListener::take_error` * `UnixListener::incoming` * RawFd impls for `UnixListener` * `UnixDatagram::bind` * `UnixDatagram::unbound` * `UnixDatagram::pair` * `UnixDatagram::connect` * `UnixDatagram::try_clone` * `UnixDatagram::local_addr` * `UnixDatagram::peer_addr` * `UnixDatagram::recv_from` * `UnixDatagram::recv` * `UnixDatagram::send_to` * `UnixDatagram::send` * `UnixDatagram::set_read_timeout` * `UnixDatagram::set_write_timeout` * `UnixDatagram::read_timeout` * `UnixDatagram::write_timeout` * `UnixDatagram::set_nonblocking` * `UnixDatagram::take_error` * `UnixDatagram::shutdown` * RawFd impls for `UnixDatagram` * `{BTree,Hash}Map::values_mut` * `<[_]>::binary_search_by_key` Deprecated: * `StaticCondvar` - this, and all other static synchronization primitives below, are usable today through the lazy-static crate on stable Rust today. Additionally, we'd like the non-static versions to be directly usable in a static context one day, so they're unlikely to be the final forms of the APIs in any case. * `CONDVAR_INIT` * `StaticMutex` * `MUTEX_INIT` * `StaticRwLock` * `RWLOCK_INIT` * `iter::Peekable::is_empty` Closes rust-lang#27717 Closes rust-lang#27720 cc rust-lang#27784 (but encode methods still exist) Closes rust-lang#30014 Closes rust-lang#30425 Closes rust-lang#30449 Closes rust-lang#31190 Closes rust-lang#31399 Closes rust-lang#31767 Closes rust-lang#32111 Closes rust-lang#32281 Closes rust-lang#32312 Closes rust-lang#32551 Closes rust-lang#33018
Tracking issue for rust-lang/rfcs#1328
The text was updated successfully, but these errors were encountered: