rust/library/proc_macro
bors a0984b4e4c Auto merge of #92598 - Badel2:panic-update-hook, r=yaahc
Implement `panic::update_hook`

Add a new function `panic::update_hook` to allow creating panic hooks that forward the call to the previously set panic hook, without race conditions. It works by taking a closure that transforms the old panic hook into a new one, while ensuring that during the execution of the closure no other thread can modify the panic hook. This is a small function so I hope it can be discussed here without a formal RFC, however if you prefer I can write one.

Consider the following example:

```rust
let prev = panic::take_hook();
panic::set_hook(Box::new(move |info| {
    println!("panic handler A");
    prev(info);
}));
```

This is a common pattern in libraries that need to do something in case of panic: log panic to a file, record code coverage, send panic message to a monitoring service, print custom message with link to github to open a new issue, etc. However it is impossible to avoid race conditions with the current API, because two threads can execute in this order:

* Thread A calls `panic::take_hook()`
* Thread B calls `panic::take_hook()`
* Thread A calls `panic::set_hook()`
* Thread B calls `panic::set_hook()`

And the result is that the original panic hook has been lost, as well as the panic hook set by thread A. The resulting panic hook will be the one set by thread B, which forwards to the default panic hook. This is not considered a big issue because the panic handler setup is usually run during initialization code, probably before spawning any other threads.

Using the new `panic::update_hook` function, this race condition is impossible, and the result will be either `A, B, original` or `B, A, original`.

```rust
panic::update_hook(|prev| {
    Box::new(move |info| {
        println!("panic handler A");
        prev(info);
    })
});
```

I found one real world use case here: 988cf403e7/src/detection.rs (L32) the workaround is to detect the race condition and panic in that case.

The pattern of `take_hook` + `set_hook` is very common, you can see some examples in this pull request, so I think it's natural to have a function that combines them both. Also using `update_hook` instead of `take_hook` + `set_hook` reduces the number of calls to `HOOK_LOCK.write()` from 2 to 1, but I don't expect this to make any difference in performance.

### Unresolved questions:

* `panic::update_hook` takes a closure, if that closure panics the error message is "panicked while processing panic" which is not nice. This is a consequence of holding the `HOOK_LOCK` while executing the closure. Could be avoided using `catch_unwind`?

* Reimplement `panic::set_hook` as `panic::update_hook(|_prev| hook)`?
2022-01-16 02:18:42 +00:00
..
src Change panic::update_hook to simplify usage 2022-01-08 00:57:59 +01:00
Cargo.toml Switch all libraries to the 2021 edition 2021-12-23 19:03:47 +08:00