Skip to main content

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}