alef 0.83.3

Opinionated polyglot binding generator for Rust libraries
Documentation
static VISITOR_REPLY_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
static VISITOR_CHANNELS: std::sync::LazyLock<std::sync::Mutex<std::collections::HashMap<u64, std::sync::mpsc::SyncSender<Option<String>>>>> =
    std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));

// Bound on how long a visitor callback waits for the Elixir-side handler to reply before
// giving up and falling back to the method's default result. Mirrors the trait-bridge
// watchdog (`arm_trait_call_timeout` in `trait_support_nifs.rs.jinja`): if the calling
// process has already exited, `send_and_clear` is a silent no-op in the BEAM and no reply
// ever arrives, so an unbounded `rx.recv()` would block the OS thread forever. ~keep
const VISITOR_CALL_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);

// Spawn a watchdog that abandons `ref_id` if it is still pending after
// `VISITOR_CALL_TIMEOUT`. A no-op if the call already completed (`visitor_reply` removes
// the entry first). Dropping the sender makes the blocked `rx.recv()` in
// `visitor_send_and_wait` return `Err`, which `.ok().flatten()` already turns into `None`
// (the same "no reply" fallback used when the channel closes for any other reason). ~keep
fn arm_visitor_call_timeout(ref_id: u64) {
    std::thread::spawn(move || {
        std::thread::sleep(VISITOR_CALL_TIMEOUT);
        if let Ok(mut channels) = VISITOR_CHANNELS.lock()
            && channels.remove(&ref_id).is_some()
        {
            tracing::warn!(
                ref_id,
                timeout = ?VISITOR_CALL_TIMEOUT,
                "visitor callback timed out (host process may have exited without replying)"
            );
        }
    });
}