use crate::internal::ffi::pico_ffi_register_metrics_handler;
use crate::plugin::interface::PicoContext;
use crate::transport::rpc::server::PackedServiceIdentifier;
use crate::util::copy_to_region;
use crate::util::FfiSafeStr;
use std::mem::MaybeUninit;
use tarantool::error::BoxError;
pub fn register_metrics_handler(
context: &PicoContext,
callback: impl Fn() -> String,
) -> Result<(), BoxError> {
let identifier = PackedServiceIdentifier::pack(
"",
context.plugin_name(),
context.service_name(),
context.plugin_version(),
)?;
let handler = FfiMetricsHandler::new(identifier, callback);
let rc = unsafe { pico_ffi_register_metrics_handler(handler) };
if rc != 0 {
return Err(BoxError::last());
}
Ok(())
}
type FfiMetricsCallback = extern "C-unwind" fn(
handler: *const FfiMetricsHandler,
output: *mut FfiSafeStr,
) -> std::ffi::c_int;
#[repr(C)]
pub struct FfiMetricsHandler {
callback: FfiMetricsCallback,
drop: extern "C-unwind" fn(*mut FfiMetricsHandler),
closure_pointer: *mut (),
pub identifier: PackedServiceIdentifier,
}
impl Drop for FfiMetricsHandler {
#[inline(always)]
fn drop(&mut self) {
(self.drop)(self)
}
}
impl FfiMetricsHandler {
fn new<F>(identifier: PackedServiceIdentifier, f: F) -> Self
where
F: Fn() -> String,
{
let closure = Box::new(f);
let closure_pointer: *mut F = Box::into_raw(closure);
Self {
callback: Self::trampoline::<F>,
drop: Self::drop_handler::<F>,
closure_pointer: closure_pointer.cast(),
identifier,
}
}
extern "C-unwind" fn trampoline<F>(
handler: *const Self,
output: *mut FfiSafeStr,
) -> std::ffi::c_int
where
F: Fn() -> String,
{
let closure_pointer: *const F = unsafe { (*handler).closure_pointer.cast::<F>() };
let closure = unsafe { &*closure_pointer };
let response = closure();
let region_slice = match copy_to_region(response.as_bytes()) {
Ok(v) => v,
Err(e) => {
e.set_last();
return -1;
}
};
let region_str = unsafe { FfiSafeStr::from_utf8_unchecked(region_slice) };
unsafe { std::ptr::write(output, region_str) }
0
}
extern "C-unwind" fn drop_handler<F>(handler: *mut Self) {
unsafe {
let closure_pointer: *mut F = (*handler).closure_pointer.cast::<F>();
let closure = Box::from_raw(closure_pointer);
drop(closure);
if cfg!(debug_assertions) {
(*handler).closure_pointer = 0xcccccccccccccccc_u64 as _;
}
(*handler).identifier.drop();
}
}
#[inline(always)]
pub fn identity(&self) -> usize {
self.callback as *const FfiMetricsCallback as _
}
#[inline(always)]
#[allow(clippy::result_unit_err)]
pub fn call(&self) -> Result<&'static str, ()> {
let mut output = MaybeUninit::uninit();
let rc = (self.callback)(self, output.as_mut_ptr());
if rc == -1 {
return Err(());
}
let result = unsafe { output.assume_init().as_str() };
Ok(result)
}
}