use crate::encoder::{Encodable, ThreadLocalEncoder};
use crate::primitives::sync::Arc;
use crate::shared_state::SharedState;
use crate::source::Source;
use crate::thread::ThreadTrackingGuard;
use arc_swap::ArcSwapOption;
use std::any::Any;
use std::cell::RefCell;
fn find_source<T: Source>(sources: &mut [Box<dyn Source>]) -> Option<&mut T> {
sources
.iter_mut()
.find_map(|source| (&mut **source as &mut dyn Any).downcast_mut::<T>())
}
crate::primitives::thread_local! {
static CURRENT_HANDLE: RefCell<Option<Dial9Handle>> = const { RefCell::new(None) };
}
static GLOBAL_HANDLE: ArcSwapOption<HandleInner> = ArcSwapOption::const_empty();
fn global_handle() -> Option<Dial9Handle> {
GLOBAL_HANDLE.load().as_ref().map(|inner| Dial9Handle {
inner: Some((**inner).clone()),
})
}
pub(crate) enum ControlCommand {
FinalizeAndStop(crate::primitives::sync::mpsc::SyncSender<()>),
}
#[derive(Clone)]
pub struct Dial9Handle {
inner: Option<HandleInner>,
}
#[derive(Clone)]
struct HandleInner {
shared: Arc<SharedState>,
control_tx: crate::primitives::sync::mpsc::SyncSender<ControlCommand>,
}
impl std::fmt::Debug for Dial9Handle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Dial9Handle")
.field("enabled", &self.is_enabled())
.finish_non_exhaustive()
}
}
impl Dial9Handle {
pub(crate) fn enabled(
shared: Arc<SharedState>,
control_tx: crate::primitives::sync::mpsc::SyncSender<ControlCommand>,
) -> Self {
Self {
inner: Some(HandleInner { shared, control_tx }),
}
}
pub fn disabled() -> Self {
Self { inner: None }
}
pub fn is_enabled(&self) -> bool {
self.inner.as_ref().is_some_and(|i| i.shared.is_enabled())
}
crate::test_util_pub! {
fn shared(&self) -> Option<&Arc<SharedState>> {
self.inner.as_ref().map(|i| &i.shared)
}
}
pub(crate) fn control_tx(
&self,
) -> Option<&crate::primitives::sync::mpsc::SyncSender<ControlCommand>> {
self.inner.as_ref().map(|i| &i.control_tx)
}
#[cfg(feature = "pipeline")]
pub fn dump_trigger(&self) -> Option<crate::dump::DumpTrigger> {
self.inner
.as_ref()
.and_then(|i| i.shared.dump_trigger().cloned())
}
pub fn current() -> Self {
CURRENT_HANDLE
.with(|cell| cell.borrow().clone())
.or_else(global_handle)
.unwrap_or_else(Self::disabled)
}
pub fn try_current_thread() -> Option<Self> {
CURRENT_HANDLE.with(|cell| cell.borrow().clone())
}
pub fn enable(&self) {
if let Some(inner) = &self.inner {
inner.shared.enable();
}
}
pub fn disable(&self) {
if let Some(inner) = &self.inner {
inner.shared.disable();
}
}
pub fn track_current_thread(&self) -> std::io::Result<ThreadTrackingGuard> {
let Some(inner) = &self.inner else {
return Ok(ThreadTrackingGuard::new(self.clone()));
};
let started = inner.shared.with_sources_mut(|sources| {
let mut done = 0;
let mut failure = None;
for source in sources.iter_mut() {
match source.on_thread_start() {
Ok(()) => done += 1,
Err(e) => {
failure = Some(e);
break;
}
}
}
match failure {
Some(e) => {
for source in &mut sources[..done] {
source.on_thread_stop();
}
Err(e)
}
None => Ok(()),
}
});
match started {
Some(Ok(())) => Ok(ThreadTrackingGuard::new(self.clone())),
Some(Err(e)) => Err(e),
None => Err(std::io::Error::other("dial9: sources lock poisoned")),
}
}
pub fn is_connected(&self) -> bool {
self.inner.is_some()
}
pub fn is_stopped(&self) -> bool {
self.inner.as_ref().is_some_and(|i| i.shared.is_stopped())
}
pub fn with_source<T: Source, R>(&self, f: impl FnOnce(&mut T) -> R) -> Option<R> {
let inner = self.inner.as_ref()?;
inner
.shared
.with_sources_mut(|sources| Some(f(find_source::<T>(sources)?)))
.flatten()
}
pub fn with_source_or_insert<T: Source, R>(
&self,
make: impl FnOnce() -> T,
f: impl FnOnce(&mut T) -> R,
) -> Option<R> {
let inner = self.inner.as_ref()?;
inner
.shared
.with_sources_vec(|sources| {
if inner.shared.is_stopped() {
return None;
}
if find_source::<T>(sources).is_none() {
sources.push(Box::new(make()));
}
Some(f(find_source::<T>(sources).expect("just registered")))
})
.flatten()
}
pub fn record_event(&self, event: impl Encodable) {
if let Some(inner) = &self.inner {
inner
.shared
.if_enabled(|buf| buf.record_encodable_event(&event));
}
}
pub fn record_event_with<E: Encodable>(&self, make: impl FnOnce() -> E) {
if let Some(inner) = &self.inner {
inner
.shared
.if_enabled(|buf| buf.record_encodable_event(&make()));
}
}
#[doc(hidden)]
pub fn with_encoder(&self, f: impl FnOnce(&mut ThreadLocalEncoder<'_>)) {
if let Some(inner) = &self.inner {
inner.shared.if_enabled(|buf| buf.with_encoder(f));
}
}
}
pub fn set_tl_handle(handle: Dial9Handle) {
CURRENT_HANDLE.with(|cell| *cell.borrow_mut() = Some(handle));
}
pub fn clear_tl_handle() {
CURRENT_HANDLE.with(|cell| *cell.borrow_mut() = None);
}
pub(crate) fn set_global_handle(handle: Dial9Handle) -> Result<(), InstallGlobalHandleError> {
let previous =
GLOBAL_HANDLE.compare_and_swap(&None::<Arc<HandleInner>>, handle.inner.map(Arc::new));
match previous.is_some() {
true => Err(InstallGlobalHandleError),
false => Ok(()),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct InstallGlobalHandleError;
impl std::fmt::Display for InstallGlobalHandleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("a process-global Dial9Handle is already installed")
}
}
impl std::error::Error for InstallGlobalHandleError {}
pub(crate) fn clear_global_handle_for(shared: &Arc<SharedState>) {
let current = GLOBAL_HANDLE.load();
if current
.as_ref()
.is_some_and(|i| Arc::ptr_eq(&i.shared, shared))
{
GLOBAL_HANDLE.compare_and_swap(¤t, None);
}
}
pub fn current_handle() -> Dial9Handle {
Dial9Handle::current()
}