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