maplibre_native_ffi/report.rs
1//! The process-wide reporter for failures that no caller can receive.
2
3use std::io::Write;
4use std::panic::{AssertUnwindSafe, catch_unwind};
5use std::sync::{Arc, Mutex};
6
7use crate::Error;
8use crate::handle::NativeHandleLeak;
9
10/// A failure that the binding contained because no caller could receive it.
11#[derive(Debug, Clone, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum Report {
14 /// A handle that a destructor could not destroy. The handle stays live.
15 /// An explicit `dispose` or `release` returns the same failure as an
16 /// error.
17 LeakedHandle(NativeHandleLeak),
18 /// An error or panic that a callback contained. A caught panic carries
19 /// the native-error status.
20 CallbackError {
21 /// The C callback type that failed, such as
22 /// `mln_http_header_transform_callback`.
23 callback: &'static str,
24 /// The error that native could not receive.
25 error: Error,
26 },
27}
28
29/// Receives each [`Report`].
30pub type Reporter = Box<dyn Fn(Report) + Send + Sync>;
31
32type SharedReporter = Arc<dyn Fn(Report) + Send + Sync>;
33
34static REPORTER: Mutex<Option<SharedReporter>> = Mutex::new(None);
35
36/// Installs the process-wide reporter, replacing any previous one, and returns
37/// whether one was installed. With no reporter, each report goes to standard
38/// error.
39///
40/// The reporter may run on any thread, including a native callback thread, so
41/// it should return quickly. A callback error reaches it on that callback's
42/// stack. A panic it raises is discarded.
43pub fn set_reporter(reporter: Option<Reporter>) -> bool {
44 let mut slot = REPORTER
45 .lock()
46 .unwrap_or_else(|poisoned| poisoned.into_inner());
47 let replaced = slot.is_some();
48 *slot = reporter.map(Arc::from);
49 replaced
50}
51
52/// Delivers a report to the installed reporter, or to standard error. Never
53/// panics: it is called from `Drop` and from native callbacks, where unwinding
54/// would abort.
55pub fn report(report: Report) {
56 // The reporter runs outside the lock, so it may replace itself.
57 let reporter = REPORTER
58 .lock()
59 .unwrap_or_else(|poisoned| poisoned.into_inner())
60 .clone();
61 let _ = catch_unwind(AssertUnwindSafe(|| match reporter {
62 Some(reporter) => reporter(report),
63 None => {
64 let line = match report {
65 Report::LeakedHandle(leak) => format!(
66 "Leaked {} native handle {:#x}; release it explicitly.",
67 leak.type_name, leak.id
68 ),
69 Report::CallbackError { callback, error } => {
70 format!("{callback} failed: {error}")
71 }
72 };
73 let _ = writeln!(std::io::stderr(), "maplibre-native-ffi: {line}");
74 }
75 }));
76}