|
| 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