use std::{
future::{self, Future},
io,
pin::pin,
sync::{Arc, Condvar, Mutex, OnceLock},
task, thread,
time::{Duration, Instant},
};
use crate::{BatchError, Channel, Receiver, Sender, Wait};
pub fn spawn<T: Channel + Send + 'static>(
thread_name: impl Into<String>,
receiver: Receiver<T>,
on_batch: impl FnMut(T) -> Result<(), BatchError<T>> + Send + 'static,
) -> io::Result<thread::JoinHandle<()>>
where
T::Item: Send + 'static,
{
#![allow(unreachable_code)]
#[cfg(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))]
{
let _ = (thread_name, receiver, on_batch);
return Err(io::Error::new(
io::ErrorKind::Other,
"blocking channel spawning is not supported on this platform",
));
}
let mut on_batch = on_batch;
thread::Builder::new()
.name(thread_name.into())
.spawn(move || {
let shared = receiver.shared.clone();
block_on(receiver.exec_inner(
move |wait, delay| {
future::ready(match wait {
Wait::Idle => {
shared.receiver_notifier.sync.wait_timeout(delay);
}
Wait::Retry => thread::sleep(delay),
})
},
move |batch| future::ready(on_batch(batch)),
))
})
}
pub fn blocking_flush<T: Channel>(sender: &Sender<T>, timeout: Duration) -> bool {
#![allow(unreachable_code)]
#[cfg(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))]
{
let _ = (sender, timeout);
emit::warn!(rt: emit::runtime::internal(), "blocking flush is not supported on this platform");
return false;
}
let notifier = Trigger::new();
sender.when_flushed_inner({
let notifier = notifier.clone();
move |flushed| {
notifier.trigger(flushed);
}
});
notifier.wait_timeout(timeout)
}
pub fn blocking_send<T: Channel>(
sender: &Sender<T>,
msg: T::Item,
timeout: Duration,
) -> Result<(), BatchError<T::Item>> {
#![allow(unreachable_code)]
#[cfg(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))]
{
let _ = (sender, msg, timeout);
return Err(BatchError::no_retry(io::Error::new(
io::ErrorKind::Other,
"blocking send is not supported on this platform",
)));
}
let start = Instant::now();
block_on(sender.send_or_wait(
msg,
timeout,
|| start.elapsed(),
|sender, timeout| {
let notifier = Trigger::new();
sender.when_empty({
let notifier = notifier.clone();
move || {
let _ = notifier.trigger(true);
}
});
notifier.wait_timeout(timeout);
future::ready(())
},
))
}
#[derive(Clone)]
pub(crate) struct Trigger(Arc<(Mutex<Option<bool>>, Condvar)>);
impl Trigger {
pub fn new() -> Self {
Trigger(Arc::new((Mutex::new(None), Condvar::new())))
}
pub fn trigger(&self, value: bool) {
*(self.0).0.lock().unwrap() = Some(value);
(self.0).1.notify_all();
}
pub fn wait_timeout(&self, mut timeout: Duration) -> bool {
let mut triggered_slot = (self.0).0.lock().unwrap();
loop {
if let Some(triggered) = triggered_slot.take() {
return triggered;
}
if timeout == Duration::ZERO {
return false;
}
let now = Instant::now();
match (self.0).1.wait_timeout(triggered_slot, timeout).unwrap() {
(triggered, r) if !r.timed_out() => {
triggered_slot = triggered;
timeout = match timeout.checked_sub(now.elapsed()) {
Some(timeout) => timeout,
None => {
return triggered_slot.take().unwrap_or(false);
}
};
continue;
}
(mut triggered_slot, _) => {
return triggered_slot.take().unwrap_or(false);
}
}
}
}
}
fn block_on<R>(fut: impl Future<Output = R>) -> R {
static WAKER: OnceLock<Arc<NeverWake>> = OnceLock::new();
struct NeverWake;
impl task::Wake for NeverWake {
fn wake(self: Arc<Self>) {}
}
let mut fut = pin!(fut);
let waker = WAKER.get_or_init(|| Arc::new(NeverWake)).clone().into();
let mut cx = task::Context::from_waker(&waker);
loop {
match fut.as_mut().poll(&mut cx) {
task::Poll::Ready(r) => return r,
task::Poll::Pending => continue,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{sync::mpsc, thread};
use crate::Receiver;
enum SenderCommand<T> {
BlockingSend(T, Duration),
Stop,
}
impl<T> SenderCommand<T> {
fn blocking_send(msg: T, timeout: Duration) -> Self {
SenderCommand::BlockingSend(msg, timeout)
}
fn stop() -> Self {
SenderCommand::Stop
}
}
enum ReceiverCommand<T> {
ProcessBatch(Box<dyn FnOnce(Vec<T>) -> Result<(), BatchError<Vec<T>>> + Send>),
}
impl<T> ReceiverCommand<T> {
fn process_batch(
f: impl FnOnce(Vec<T>) -> Result<(), BatchError<Vec<T>>> + Send + 'static,
) -> Self {
ReceiverCommand::ProcessBatch(Box::new(f))
}
}
fn spawn_sender<T: Send + 'static>(
sender: Sender<Vec<T>>,
) -> (mpsc::Sender<SenderCommand<T>>, thread::JoinHandle<()>) {
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
loop {
match rx.recv().unwrap() {
SenderCommand::BlockingSend(msg, timeout) => {
let _ = blocking_send(&sender, msg, timeout);
}
SenderCommand::Stop => return,
}
}
});
(tx, handle)
}
fn spawn_receiver<T: Send + 'static>(
receiver: Receiver<Vec<T>>,
) -> (mpsc::Sender<ReceiverCommand<T>>, thread::JoinHandle<()>) {
let (tx, rx) = mpsc::channel();
let handle = spawn("test_receiver", receiver, move |batch| match rx.recv() {
Ok(ReceiverCommand::ProcessBatch(p)) => p(batch),
_ => Ok(()),
})
.unwrap();
(tx, handle)
}
#[test]
fn send_recv() {
let received = Arc::new(Mutex::new(0));
let (sender, receiver) = crate::bounded(10);
let (receiver, receiver_handle) = spawn_receiver(receiver);
for _ in 0..10 {
sender.send(());
}
for _ in 0..2 {
receiver
.send(ReceiverCommand::process_batch({
let received = received.clone();
move |batch| {
*received.lock().unwrap() += batch.len();
Ok(())
}
}))
.unwrap();
}
while { *received.lock().unwrap() } != 10 {}
drop(sender);
receiver_handle.join().unwrap();
}
#[test]
fn send_full_capacity() {
let received = Arc::new(Mutex::new(Vec::new()));
let (sender, receiver) = crate::bounded(5);
for i in 0..10 {
sender.send(i);
}
let (receiver, receiver_handle) = spawn_receiver(receiver);
receiver
.send(ReceiverCommand::process_batch({
let received = received.clone();
move |batch| {
received.lock().unwrap().extend(batch);
Ok(())
}
}))
.unwrap();
while { received.lock().unwrap().len() } == 0 {}
assert_eq!(vec![5, 6, 7, 8, 9], *received.lock().unwrap());
drop(sender);
receiver_handle.join().unwrap();
}
#[test]
fn blocking_send_full_capacity() {
let received = Arc::new(Mutex::new(0));
let (sender, receiver) = crate::bounded(5);
let (sender, sender_handle) = spawn_sender(sender);
let (receiver, receiver_handle) = spawn_receiver(receiver);
for _ in 0..10 {
sender
.send(SenderCommand::blocking_send((), Duration::from_secs(1)))
.unwrap();
}
for _ in 0..10 {
receiver
.send(ReceiverCommand::process_batch({
let received = received.clone();
move |batch| {
*received.lock().unwrap() += batch.len();
Ok(())
}
}))
.unwrap();
}
while { *received.lock().unwrap() } != 10 {}
sender.send(SenderCommand::stop()).unwrap();
sender_handle.join().unwrap();
receiver_handle.join().unwrap();
}
#[test]
fn blocking_send_full_capacity_timeout() {
let received = Arc::new(Mutex::new(Vec::new()));
let (sender, receiver) = crate::bounded(5);
let (sender, sender_handle) = spawn_sender(sender);
let (receiver, _) = spawn_receiver(receiver);
for i in 0..10 {
sender
.send(SenderCommand::blocking_send(i, Duration::from_millis(1)))
.unwrap();
}
receiver
.send(ReceiverCommand::process_batch({
let received = received.clone();
move |batch| {
received.lock().unwrap().extend(batch);
Ok(())
}
}))
.unwrap();
while { received.lock().unwrap().len() } == 0 {}
sender.send(SenderCommand::stop()).unwrap();
sender_handle.join().unwrap();
}
#[test]
fn try_send_on_closed_channel() {
let (sender, receiver) = crate::bounded::<Vec<i32>>(10);
drop(receiver);
let result = sender.try_send(1);
assert!(result.is_err());
let err = result.err().unwrap();
assert!(err.into_retryable().is_none());
}
#[test]
fn flush_reports_failed_batch() {
let (sender, receiver) = crate::bounded::<Vec<i32>>(10);
let handle = spawn("test_receiver", receiver, |_| {
Err(BatchError::no_retry(io::Error::new(
io::ErrorKind::Other,
"explicit failure",
)))
})
.unwrap();
sender.send(1);
assert!(!blocking_flush(&sender, Duration::from_secs(5)));
drop(sender);
handle.join().unwrap();
}
#[test]
fn flush_reports_closed_channel() {
let (sender, receiver) = crate::bounded::<Vec<i32>>(10);
sender.send(1);
drop(receiver);
assert!(!blocking_flush(&sender, Duration::from_secs(5)));
}
#[test]
fn flush_reports_target_batch_outcome() {
let (started_tx, started_rx) = mpsc::channel();
let (continue_tx, continue_rx) = mpsc::channel();
let (sender, receiver) = crate::bounded::<Vec<i32>>(10);
let handle = spawn("test_receiver", receiver, move |_| {
started_tx.send(()).unwrap();
continue_rx.recv().unwrap();
Err(BatchError::no_retry(io::Error::new(
io::ErrorKind::Other,
"explicit failure",
)))
})
.unwrap();
sender.send(1);
started_rx.recv().unwrap();
let (flushed_tx, flushed_rx) = mpsc::channel();
sender.when_flushed_inner(move |flushed| {
flushed_tx.send(flushed).unwrap();
});
continue_tx.send(()).unwrap();
assert!(!flushed_rx.recv().unwrap());
drop(sender);
handle.join().unwrap();
}
#[test]
fn flush_wakes_idle_receiver() {
let received = Arc::new(Mutex::new(0));
let (sender, receiver) = crate::bounded(10);
let handle = spawn("test_receiver", receiver, {
let received = received.clone();
move |batch: Vec<()>| {
*received.lock().unwrap() += batch.len();
Ok(())
}
})
.unwrap();
for _ in 0..3 {
thread::sleep(Duration::from_millis(550));
sender.send(());
assert!(blocking_flush(&sender, Duration::from_millis(200)));
}
assert_eq!(3, *received.lock().unwrap());
drop(sender);
handle.join().unwrap();
}
#[test]
fn drop_wakes_idle_receiver() {
let (sender, receiver) = crate::bounded::<Vec<()>>(10);
let handle = spawn("test_receiver", receiver, |_| Ok(())).unwrap();
thread::sleep(Duration::from_millis(550));
let dropped = Instant::now();
drop(sender);
handle.join().unwrap();
assert!(dropped.elapsed() < Duration::from_millis(250));
}
#[test]
fn channel_pressure_wakes_idle_receiver() {
let received = Arc::new(Mutex::new(0));
let (sender, receiver) = crate::bounded(10);
let handle = spawn("test_receiver", receiver, {
let received = received.clone();
move |batch: Vec<()>| {
*received.lock().unwrap() += batch.len();
Ok(())
}
})
.unwrap();
thread::sleep(Duration::from_millis(550));
for _ in 0..5 {
sender.send(());
}
let deadline = Instant::now() + Duration::from_millis(200);
while { *received.lock().unwrap() } < 5 {
assert!(
Instant::now() < deadline,
"receiver didn't wake on pressure"
);
thread::sleep(Duration::from_millis(1));
}
drop(sender);
handle.join().unwrap();
}
#[test]
fn flush_empty() {
let (sender, receiver) = crate::bounded(10);
let (_, receiver_handle) = spawn_receiver::<()>(receiver);
assert!(blocking_flush(&sender, Duration::ZERO));
drop(sender);
receiver_handle.join().unwrap();
}
#[test]
fn flush_active() {
let (sender, receiver) = crate::bounded(10);
let (receiver, receiver_handle) = spawn_receiver::<()>(receiver);
for _ in 0..3 {
sender.send(());
}
while !sender.shared.state.lock().unwrap().is_in_batch {}
for _ in 0..3 {
sender.send(());
}
thread::scope(|s| {
let handle = s.spawn(|| blocking_flush(&sender, Duration::from_secs(1)));
for _ in 0..2 {
receiver
.send(ReceiverCommand::process_batch(|_| Ok(())))
.unwrap();
receiver
.send(ReceiverCommand::process_batch(|_| Ok(())))
.unwrap();
}
handle.join().unwrap();
assert_eq!(
0,
sender.shared.state.lock().unwrap().next_batch.channel.len()
);
});
drop(sender);
receiver_handle.join().unwrap();
}
}