picodata_plugin/internal/
mod.rs

1//! Picodata internal API.
2pub mod cas;
3pub(crate) mod ffi;
4pub mod types;
5
6use crate::internal::ffi::{
7    pico_ffi_cluster_uuid, pico_ffi_instance_info, pico_ffi_raft_info, pico_ffi_rpc_version,
8    pico_ffi_version,
9};
10use crate::internal::types::InstanceInfo;
11use abi_stable::derive_macro_reexports::RResult;
12use std::{env, fs, io, process};
13use tarantool::error::BoxError;
14
15#[deprecated(note = "use [`authentication::authenticate`] instead")]
16pub use crate::authentication::authenticate;
17
18/// Return picodata version.
19pub fn picodata_version() -> &'static str {
20    let ptr_and_len = unsafe { pico_ffi_version() };
21    // SAFETY: ptr points to static string
22    let slice = unsafe { std::slice::from_raw_parts(ptr_and_len.0, ptr_and_len.1) };
23    std::str::from_utf8(slice).expect("should be valid utf8")
24}
25
26/// Return picodata RPC API version.
27pub fn rpc_version() -> &'static str {
28    let ptr_and_len = unsafe { pico_ffi_rpc_version() };
29    // SAFETY: ptr points to static string
30    let slice = unsafe { std::slice::from_raw_parts(ptr_and_len.0, ptr_and_len.1) };
31    std::str::from_utf8(slice).expect("should be valid utf8")
32}
33
34/// Return UUID of the cluster the current instance belongs to.
35pub fn cluster_uuid() -> Result<String, BoxError> {
36    match unsafe { pico_ffi_cluster_uuid() } {
37        RResult::ROk(rstring) => Ok(rstring.into()),
38        RResult::RErr(_) => {
39            let error = BoxError::last();
40            Err(error)
41        }
42    }
43}
44
45/// Return information about current picodata instance.
46pub fn instance_info() -> Result<InstanceInfo, BoxError> {
47    match unsafe { pico_ffi_instance_info() } {
48        RResult::ROk(info) => Ok(info),
49        RResult::RErr(_) => {
50            let error = BoxError::last();
51            Err(error)
52        }
53    }
54}
55
56/// Return information about RAFT protocol state.
57pub fn raft_info() -> types::RaftInfo {
58    unsafe { pico_ffi_raft_info() }
59}
60
61/// Dump the backtrace to a file to make debugging easier.
62/// This is also used in integration tests.
63fn dump_backtrace(msg: &str) -> Result<(), io::Error> {
64    let should_dump = env::var("PICODATA_INTERNAL_BACKTRACE_DUMP")
65        .map(|v| !v.is_empty())
66        .unwrap_or(false);
67
68    if !should_dump {
69        return Ok(());
70    }
71
72    let name = format!("picodata-{}.backtrace", process::id());
73    let path = env::current_dir()?.join(&name);
74
75    fs::write(&name, msg)
76        .map(|_| tarantool::say_info!("dumped panic backtrace to `{}`", path.display()))
77        .inspect_err(|e| tarantool::say_info!("{}", e))?;
78
79    Ok(())
80}
81
82#[inline]
83pub fn set_panic_hook() {
84    // NOTE: this function is called ASAP when starting up the process.
85    // Even if `say` isn't properly initialized yet, we
86    // still should be able to print a simplified line to stderr.
87    std::panic::set_hook(Box::new(|info| {
88        let version = crate::internal::picodata_version();
89
90        // Capture a backtrace regardless of RUST_BACKTRACE and such.
91        let backtrace = std::backtrace::Backtrace::force_capture();
92        let message = format!(
93            "Picodata {version}\n\n{info}\n\nbacktrace:\n{backtrace}\naborting due to panic"
94        );
95
96        // Dump backtrace to logs and file if needed
97        tarantool::say_crit!("\n\n{message}");
98        dump_backtrace(&message)
99            .unwrap_or_else(|e| tarantool::say_info!("Failed to dump panic backtrace: {}", e));
100
101        std::process::abort();
102    }));
103}
104
105#[derive(thiserror::Error, Debug)]
106pub enum InternalError {
107    #[error("timeout")]
108    Timeout,
109    #[error("internal error: {0}")]
110    Any(BoxError),
111}