use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use leptoskit_devtools_protocol::RpcAttempt;
use leptoskit_devtools_protocol::sink::Sink;
use crate::FnError;
static ATTEMPTS: Sink<RpcAttempt> = Sink::new();
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
static EPOCH: AtomicU64 = AtomicU64::new(0);
pub fn drain() -> Vec<RpcAttempt> {
ATTEMPTS.drain()
}
pub fn set_waker(wake: Option<fn()>) {
ATTEMPTS.set_waker(wake);
}
pub fn dropped() -> u64 {
ATTEMPTS.dropped()
}
pub fn now_ms() -> u64 {
let now = leptoskit_query::now_ms();
if now == 0 {
return 0;
}
let epoch = match EPOCH.compare_exchange(0, now + 1, Ordering::Relaxed, Ordering::Relaxed) {
Ok(_) => now,
Err(stored) => stored - 1,
};
now.saturating_sub(epoch)
}
fn function_name(type_name: &'static str) -> &'static str {
let mut name = type_name;
while let Some(trimmed) = name.strip_suffix("::{{closure}}") {
name = trimmed;
}
name
}
pub(crate) struct Attempt {
record: Option<RpcAttempt>,
}
impl Attempt {
pub(crate) fn start(function: &'static str, attempt: u32) -> Self {
let record = RpcAttempt {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
function: function_name(function).to_owned(),
attempt,
started_ms: now_ms(),
finished_ms: None,
error: None,
};
ATTEMPTS.record(record.clone());
Self {
record: Some(record),
}
}
pub(crate) fn finish(mut self, error: Option<&FnError>) {
if let Some(record) = self.record.take() {
finish(record, error.map(FnError::class));
}
}
}
impl Drop for Attempt {
fn drop(&mut self) {
if let Some(record) = self.record.take() {
finish(record, Some("cancelled"));
}
}
}
fn finish(mut record: RpcAttempt, error: Option<&str>) {
record.finished_ms = Some(now_ms());
record.error = error.map(str::to_owned);
ATTEMPTS.record(record);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn closures_trim_to_the_function_that_made_them() {
assert_eq!(
function_name("app::api::clips::{{closure}}::{{closure}}"),
"app::api::clips"
);
assert_eq!(function_name("app::api::clips"), "app::api::clips");
}
}