Skip to content

Commit e4fd4e6

Browse files
committed
Allow a custom panic handler
1 parent 8e2d3a3 commit e4fd4e6

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed

text/0000-global-panic-handler.md

+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
- Feature Name: panic_handler
2+
- Start Date: 2015-10-08
3+
- RFC PR: (leave this empty)
4+
- Rust Issue: (leave this empty)
5+
6+
# Summary
7+
8+
When a thread panics in Rust, the unwinding runtime currently prints a message
9+
to standard error containing the panic argument as well as the filename and
10+
line number corresponding to the location from which the panic originated.
11+
This RFC proposes a mechanism to allow user code to replace this logic with
12+
custom handlers that will run before unwinding begins.
13+
14+
# Motivation
15+
16+
The default behavior is not always ideal for all programs:
17+
18+
* Programs with command line interfaces do not want their output polluted by
19+
random panic messages.
20+
* Programs using a logging framework may want panic messages to be routed into
21+
that system so that they can be processed like other events.
22+
* Programs with graphical user interfaces may not have standard error attached
23+
at all and want to be notified of thread panics to potentially display an
24+
internal error dialog to the user.
25+
26+
The standard library [previously
27+
supported](https://doc.rust-lang.org/1.3.0/std/rt/unwind/fn.register.html) (in
28+
unstable code) the registration of a set of panic handlers. This API had
29+
several issues:
30+
31+
* The system supported a fixed but unspecified number of handlers, and a
32+
handler could never be unregistered once added.
33+
* The callbacks were raw function pointers rather than closures.
34+
* Handlers would be invoked on nested panics, which would result in a stack
35+
overflow if a handler itself panicked.
36+
* The callbacks were specified to take the panic message, file name and line
37+
number directly. This would prevent us from adding more functionality in
38+
the future, such as access to backtrace information. In addition, the
39+
presence of file names and line numbers for all panics causes some amount of
40+
binary bloat and we may want to add some avenue to allow for the omission of
41+
those values in the future.
42+
43+
# Detailed design
44+
45+
A new module, `std::panic`, will be created with a panic handling API:
46+
47+
```rust
48+
/// Unregisters the current panic handler, returning it.
49+
///
50+
/// If no custom handler is registered, the default handler will be returned.
51+
///
52+
/// # Panics
53+
///
54+
/// Panics if called from a panicking thread. Note that this will be a nested
55+
/// panic and therefore abort the process.
56+
pub fn take_handler() -> Box<Fn(&PanicInfo) + 'static + Sync + Send> { ... }
57+
58+
/// Registers a custom panic handler, replacing any that was previously
59+
/// registered.
60+
///
61+
/// # Panics
62+
///
63+
/// Panics if called from a panicking thread. Note that this will be a nested
64+
/// panic and therefore abort the process.
65+
pub fn set_handler<F>(handler: F) where F: Fn(&PanicInfo) + 'static + Sync + Send { ... }
66+
67+
/// A struct providing information about a panic.
68+
pub struct PanicInfo { ... }
69+
70+
impl PanicInfo {
71+
/// Returns the payload associated with the panic.
72+
///
73+
/// This will commonly, but not always, be a `&'static str` or `String`.
74+
pub fn payload(&self) -> &Any + Send { ... }
75+
76+
/// Returns information about the location from which the panic originated,
77+
/// if available.
78+
pub fn location(&self) -> Option<Location> { ... }
79+
}
80+
81+
/// A struct containing information about the location of a panic.
82+
pub struct Location<'a> { ... }
83+
84+
impl<'a> Location<'a> {
85+
/// Returns the name of the source file from which the panic originated.
86+
pub fn file(&self) -> &str { ... }
87+
88+
/// Returns the line number from which the panic originated.
89+
pub fn line(&self) -> u32 { ... }
90+
}
91+
```
92+
93+
When a panic occurs, but before unwinding begins, the runtime will call the
94+
registered panic handler. After the handler returns, the runtime will then
95+
unwind the thread. If a thread panics while panicking (a "double panic"), the
96+
panic handler will *not* be invoked and the process will abort. Note that the
97+
thread is considered to be panicking while the panic handler is running, so a
98+
panic originating from the panic handler will result in a double panic.
99+
100+
The `take_handler` method exists to allow for handlers to "chain" by closing
101+
over the previous handler and calling into it:
102+
103+
```rust
104+
let old_handler = panic::take_handler();
105+
panic::set_handler(move |info| {
106+
println!("uh oh!");
107+
old_handler(info);
108+
});
109+
```
110+
111+
This is obviously a racy operation, but as a single global resource, the global
112+
panic handler should only be adjusted by applications rather than libraries,
113+
most likely early in the startup process.
114+
115+
The implementation of `set_handler` and `take_handler` will have to be
116+
carefully synchronized to ensure that a handler is not replaced while executing
117+
in another thread. This can be accomplished in a manner similar to [that used
118+
by the `log`
119+
crate](https://github.com/rust-lang-nursery/log/blob/aa8618c840dd88b27c487c9fc9571d89751583f3/src/lib.rs).
120+
`take_handler` and `set_handler` will wait until no other threads are currently
121+
running the panic handler, at which point they will atomically swap the handler
122+
out as appropriate.
123+
124+
Note that `location` will always return `Some` in the current implementation.
125+
It returns an `Option` to hedge against possible future changes to the panic
126+
system that would allow a crate to be compiled with location metadata removed
127+
to minimize binary size.
128+
129+
## Prior Art
130+
131+
C++ has a
132+
[`std::set_terminate`](http://www.cplusplus.com/reference/exception/set_terminate/)
133+
function which registers a handler for uncaught exceptions, returning the old
134+
one. The handler takes no arguments.
135+
136+
Python passes uncaught exceptions to the global handler
137+
[`sys.excepthook`](https://docs.python.org/2/library/sys.html#sys.excepthook)
138+
which can be set by user code.
139+
140+
In Java, uncaught exceptions [can be
141+
handled](http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler))
142+
by handlers registered on an individual `Thread`, by the `Thread`'s,
143+
`ThreadGroup`, and by a handler registered globally. The handlers are provided
144+
with the `Throwable` that triggered the handler.
145+
146+
# Drawbacks
147+
148+
The more infrastructure we add to interact with panics, the more attractive it
149+
becomes to use them as a more normal part of control flow.
150+
151+
# Alternatives
152+
153+
Panic handlers could be run after a panicking thread has unwound rather than
154+
before. This is perhaps a more intuitive arrangement, and allows `catch_panic`
155+
to prevent panic handlers from running. However, running handlers before
156+
unwinding allows them access to more context, for example, the ability to take
157+
a stack trace.
158+
159+
`PanicInfo::location` could be split into `PanicInfo::file` and
160+
`PanicInfo::line` to cut down on the API size, though that would require
161+
handlers to deal with weird cases like a line number but no file being
162+
available.
163+
164+
[RFC 1100](https://github.com/rust-lang/rfcs/pull/1100) proposed an API based
165+
around thread-local handlers. While there are reasonable use cases for the
166+
registration of custom handlers on a per-thread basis, most of the common uses
167+
for custom handlers want to have a single set of behavior cover all threads in
168+
the process. Being forced to remember to register a handler in every thread
169+
spawned in a program is tedious and error prone, and not even possible in many
170+
cases for threads spawned in libraries the author has no control over.
171+
172+
While out of scope for this RFC, a future extension could add thread-local
173+
handlers on top of the global one proposed here in a straightforward manner.
174+
175+
The implementation could be simplified by altering the API to store, and
176+
`take_logger` to return, an `Arc<Fn(&PanicInfo) + 'static + Sync + Send>` or
177+
a bare function pointer. This seems like a somewhat weirder API, however, and
178+
the implementation proposed above should not end up complex enough to justify
179+
the change.
180+
181+
# Unresolved questions
182+
183+
None at the moment.

0 commit comments

Comments
 (0)