use std::{marker::PhantomData, sync::Arc, time::Instant};
use crossbeam_channel::{bounded, RecvTimeoutError};
use parking_lot::Mutex;
use crate::rt::{AsyncRuntime, serial_local_thread::LocalTaskRuntime};
use super::{counts::RuntimeMetricCounts, sample_error::RuntimeMetricSampleError,
identity::RuntimeMetricIdentity};
pub(super) trait RuntimeMetricsSource: Send + Sync {
fn sample(&self, deadline: Instant) -> Result<RuntimeMetricCounts, RuntimeMetricSampleError>;
}
pub(super) struct RuntimeSource<R, O> {
runtime: R,
output: PhantomData<fn() -> O>,
}
impl<R, O> RuntimeSource<R, O> {
pub(super) fn new(runtime: R) -> Self { Self { runtime, output: PhantomData } }
}
impl<R: AsyncRuntime<O>, O: Default + 'static> RuntimeMetricsSource for RuntimeSource<R, O> {
fn sample(&self, deadline: Instant) -> Result<RuntimeMetricCounts, RuntimeMetricSampleError> {
if Instant::now() >= deadline { return Err(RuntimeMetricSampleError::DeadlineExceeded); }
let counts = RuntimeMetricCounts::new(self.runtime.len(), Some(self.runtime.wait_len()));
if Instant::now() >= deadline { return Err(RuntimeMetricSampleError::DeadlineExceeded); }
Ok(counts)
}
}
pub(super) struct LocalRuntimeSource<O: Default + 'static> {
runtime: Mutex<LocalTaskRuntime<O>>,
identity: Arc<RuntimeMetricIdentity>,
}
impl<O: Default + 'static> LocalRuntimeSource<O> {
pub(super) fn new(runtime: LocalTaskRuntime<O>, identity: Arc<RuntimeMetricIdentity>) -> Self {
Self { runtime: Mutex::new(runtime), identity }
}
}
impl<O: Default + 'static> RuntimeMetricsSource for LocalRuntimeSource<O> {
fn sample(&self, deadline: Instant) -> Result<RuntimeMetricCounts, RuntimeMetricSampleError> {
if Instant::now() >= deadline { return Err(RuntimeMetricSampleError::DeadlineExceeded); }
let permit = self.identity.try_claim().ok_or(RuntimeMetricSampleError::RequestInFlight)?;
let (sender, receiver) = bounded(1);
let runtime = self.runtime.lock().clone();
if Instant::now() >= deadline { return Err(RuntimeMetricSampleError::DeadlineExceeded); }
runtime.send_metric_sample(sender, permit);
let reply = receiver.recv_deadline(deadline);
if Instant::now() >= deadline { return Err(RuntimeMetricSampleError::DeadlineExceeded); }
match reply {
Ok(len) => Ok(RuntimeMetricCounts::new(len, None)),
Err(RecvTimeoutError::Timeout) => Err(RuntimeMetricSampleError::DeadlineExceeded),
Err(RecvTimeoutError::Disconnected) => Err(RuntimeMetricSampleError::ReplyClosed),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rt::{single_thread::SingleTaskRunner, serial_local_thread::LocalTaskRunner};
use std::{rc::Rc, sync::{OnceLock, atomic::{AtomicUsize, Ordering}}, thread, time::Duration};
fn time_loop() {
static CLOCK: OnceLock<crate::rt::GlobalTimeLoopHandle> = OnceLock::new();
CLOCK.get_or_init(|| crate::rt::startup_global_time_loop(1).expect("专项应独占全局时钟"));
}
fn wait_queued<O: Default + 'static>(runtime: &LocalTaskRuntime<O>) {
let deadline = Instant::now() + Duration::from_secs(2);
while runtime.len() == 0 {
assert!(Instant::now() < deadline, "真实send未投递采样任务");
thread::yield_now();
}
}
fn values(result: Result<RuntimeMetricCounts, RuntimeMetricSampleError>) -> (usize, Option<usize>) {
match result {
Ok(counts) => (counts.len(), counts.wait_len()),
Err(_) => panic!("真实采样必须成功"),
}
}
#[test]
fn test_direct_source_preserves_original_getters() {
time_loop();
let runner = SingleTaskRunner::<()>::default();
let runtime = runner.startup().unwrap();
let source = Arc::new(RuntimeSource::<_, ()>::new(runtime.clone()));
for _ in 0..3 { runtime.spawn(async {}).unwrap(); }
let copy = source.clone();
assert_eq!(thread::spawn(move || values(copy.sample(Instant::now() + Duration::from_secs(2)))).join().unwrap(),
(runtime.len(), Some(runtime.wait_len())));
let copy = source.clone();
assert!(matches!(thread::spawn(move || copy.sample(Instant::now())).join().unwrap(),
Err(RuntimeMetricSampleError::DeadlineExceeded)));
assert_eq!(runtime.len(), 3);
for _ in 0..3 { runner.run_once().unwrap(); }
assert_eq!(thread::spawn(move || values(source.sample(Instant::now() + Duration::from_secs(2)))).join().unwrap(), (0, Some(0)));
}
static LOCAL_DROPS: AtomicUsize = AtomicUsize::new(0);
struct ThreadBound(Rc<thread::ThreadId>);
impl Default for ThreadBound {
fn default() -> Self { Self(Rc::new(thread::current().id())) }
}
impl Drop for ThreadBound {
fn drop(&mut self) {
assert_eq!(*self.0, thread::current().id(), "!Send输出必须在构造线程析构");
LOCAL_DROPS.fetch_add(1, Ordering::Relaxed);
}
}
#[test]
fn test_native_deadline_in_flight_late_reply_and_thread_bound_output() {
let runner = LocalTaskRunner::<ThreadBound>::new();
let runtime = runner.get_runtime();
let identity = runtime.runtime_metric_slot().get_or_allocate().unwrap();
let source = Arc::new(LocalRuntimeSource::new(runtime.clone(), identity.clone()));
let before = LOCAL_DROPS.load(Ordering::Relaxed);
let copy = source.clone();
let expired = thread::spawn(move || copy.sample(Instant::now())).join().unwrap();
assert!(matches!(expired, Err(RuntimeMetricSampleError::DeadlineExceeded)));
assert_eq!(runtime.len(), 0);
let copy = source.clone();
let result = thread::spawn(move || copy.sample(Instant::now() + Duration::from_millis(20))).join().unwrap();
assert!(matches!(result, Err(RuntimeMetricSampleError::DeadlineExceeded)));
assert_eq!(runtime.len(), 1);
assert!(identity.try_claim().is_none());
for _ in 0..3 {
let copy = source.clone();
let result = thread::spawn(move || copy.sample(Instant::now() + Duration::from_secs(2))).join().unwrap();
assert!(matches!(result, Err(RuntimeMetricSampleError::RequestInFlight)));
assert_eq!(runtime.len(), 1);
}
runner.poll();
runner.run_once();
assert_eq!(runtime.len(), 0);
assert_eq!(LOCAL_DROPS.load(Ordering::Relaxed), before + 1);
assert!(identity.try_claim().is_some());
let copy = source.clone();
let receiver = thread::spawn(move || copy.sample(Instant::now() + Duration::from_secs(2)));
wait_queued(&runtime);
runner.poll();
runner.run_once();
assert_eq!(values(receiver.join().unwrap()), (0, None));
assert_eq!(LOCAL_DROPS.load(Ordering::Relaxed), before + 2);
assert_eq!(runtime.len(), 0);
}
#[test]
fn test_native_default_panic_closes_reply_and_releases_permit() {
struct Panics;
impl Default for Panics { fn default() -> Self { panic!("真实用户Default故障"); } }
let runner = LocalTaskRunner::<Panics>::new();
let runtime = runner.get_runtime();
let identity = runtime.runtime_metric_slot().get_or_allocate().unwrap();
let source = Arc::new(LocalRuntimeSource::new(runtime.clone(), identity.clone()));
let receiver = thread::spawn(move || source.sample(Instant::now() + Duration::from_secs(2)));
wait_queued(&runtime);
runner.poll();
assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| runner.run_once())).is_err());
assert!(matches!(receiver.join().unwrap(), Err(RuntimeMetricSampleError::ReplyClosed)));
assert!(identity.try_claim().is_some());
assert_eq!(runtime.len(), 0);
}
}