use std::cell::RefCell;
use crate::lifecycle;
use crate::per_thread_queue::PerThreadProducer;
use crate::queue::Producer;
thread_local! {
static TL_PRODUCER: RefCell<Option<PerThreadProducer>> = const { RefCell::new(None) };
}
pub fn ensure_producer_registered() {
TL_PRODUCER.with(|cell| {
let mut borrow = cell.borrow_mut();
if borrow.is_none() {
*borrow = Some(lifecycle::get_backend().create_producer());
}
});
}
pub fn with_producer<R>(f: impl FnOnce(&mut Producer) -> R) -> R {
ensure_producer_registered();
TL_PRODUCER.with(|cell| {
f(&mut cell.borrow_mut().as_mut().unwrap().producer)
})
}
#[cfg(test)]
mod tests {
use std::ptr;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::Duration;
use super::with_producer;
use crate::backend::BackendOptions;
use crate::decode::LogRecord;
use crate::level::LogLevel;
use crate::lifecycle;
use crate::metadata::LogMetadata;
use crate::record::RecordHeader;
use crate::sink::{Sink, SinkError};
use crate::testutil::spin_until;
fn fast_options() -> BackendOptions {
BackendOptions {
idle_sleep: Duration::from_micros(10),
idle_yield_rounds: 0,
..BackendOptions::default()
}
}
fn write_raw_record(
producer: &mut crate::queue::Producer,
metadata: &'static LogMetadata,
logger: &Arc<crate::logger::Logger>,
) {
let header = RecordHeader::new(
0,
ptr::from_ref(metadata) as usize,
Arc::as_ptr(logger) as usize,
0,
);
producer
.write(RecordHeader::SIZE, |buf| {
unsafe { header.write_to(buf.as_mut_ptr()) };
})
.expect("queue write must succeed on a fresh producer");
}
#[test]
fn first_with_producer_registers_one_context_second_reuses_miri_slow() {
let _guard = lifecycle::start(fast_options()).expect("start must succeed");
let backend = lifecycle::get_backend();
assert_eq!(
backend.consumer_count(),
0,
"fresh backend must have no contexts"
);
with_producer(|_| {});
assert_eq!(
backend.consumer_count(),
1,
"first call must register exactly one context"
);
with_producer(|_| {});
assert_eq!(
backend.consumer_count(),
1,
"second call on the same thread must reuse the TLS slot"
);
}
#[cfg(not(miri))]
#[test]
fn n_distinct_threads_produce_n_contexts() {
const N: usize = 4;
let _guard = lifecycle::start(fast_options()).expect("start must succeed");
let backend = lifecycle::get_backend();
let barrier = Arc::new(Barrier::new(N + 1));
let handles: Vec<_> = (0..N)
.map(|_| {
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
with_producer(|_| {});
barrier.wait(); barrier.wait(); })
})
.collect();
barrier.wait(); assert_eq!(
backend.consumer_count(),
N,
"N distinct threads must produce N contexts"
);
barrier.wait();
for h in handles {
h.join().expect("thread must not panic");
}
}
#[test]
#[should_panic(expected = "insomnilog: call insomnilog::start() before using the logger")]
fn with_producer_before_start_panics_with_not_started_message() {
with_producer(|_| {});
}
#[cfg(not(miri))]
#[test]
fn with_producer_from_worker_thread_hits_reentrancy_guard() {
use std::sync::atomic::AtomicBool;
struct ReentrantSink {
did_panic: Arc<AtomicBool>,
}
impl Sink for ReentrantSink {
fn write_record(&self, _record: &LogRecord) -> Result<(), SinkError> {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
with_producer(|_| {});
}));
if let Err(payload) = result {
self.did_panic.store(true, Ordering::Release);
std::panic::resume_unwind(payload);
}
Ok(())
}
fn flush(&self) -> Result<(), SinkError> {
Ok(())
}
fn level(&self) -> LogLevel {
LogLevel::Trace
}
}
static META: LogMetadata = LogMetadata {
level: LogLevel::Info,
fmt_str: "",
file: "frontend_test.rs",
line: 1,
module_path: "tests",
arg_count: 0,
};
let did_panic = Arc::new(AtomicBool::new(false));
let _guard = lifecycle::start(fast_options()).expect("start must succeed");
let backend = lifecycle::get_backend();
let logger = backend
.create_logger(
"frontend_reentrant",
vec![Arc::new(ReentrantSink {
did_panic: Arc::clone(&did_panic),
}) as Arc<dyn Sink>],
LogLevel::Trace,
)
.expect("logger must be created");
let mut handle = backend.create_producer();
write_raw_record(&mut handle.producer, &META, &logger);
drop(handle);
let reached = spin_until(|| did_panic.load(Ordering::Acquire), Duration::from_secs(5));
assert!(
reached,
"re-entrancy panic must be observed by the worker within 5 s"
);
}
}