use log::{debug, info};
use std::sync::atomic::{AtomicBool, Ordering};
use tokio::sync::mpsc::Sender;
#[inline]
pub async fn send_string_logged(tx: &Sender<String>, line: String, context: &'static str) -> bool {
match tx.send(line).await {
Ok(()) => true,
Err(_e) => {
debug!(
target: "crabmate::sse_mpsc",
"SSE mpsc String send failed context={} (receiver dropped or channel closed)",
context
);
false
}
}
}
#[inline]
pub async fn send_string_logged_cooperative_cancel(
tx: &Sender<String>,
line: String,
context: &'static str,
cancel: Option<&AtomicBool>,
) -> bool {
match tx.send(line).await {
Ok(()) => true,
Err(_e) => {
debug!(
target: "crabmate::sse_mpsc",
"SSE mpsc String send failed context={} (receiver dropped or channel closed)",
context
);
if let Some(c) = cancel {
let prev = c.swap(true, Ordering::SeqCst);
if !prev {
info!(
target: "crabmate::sse_mpsc",
"SSE 发送失败,已置协作取消标志 context={}",
context
);
}
}
false
}
}
}
#[cfg(test)]
mod tests {
use tokio::sync::mpsc;
use super::*;
#[tokio::test]
async fn send_string_logged_ok_when_receiver_alive() {
let (tx, mut rx) = mpsc::channel::<String>(2);
assert!(send_string_logged(&tx, "hello".to_string(), "ctx_ok").await);
assert_eq!(rx.recv().await.expect("recv"), "hello");
}
#[tokio::test]
async fn send_string_logged_false_when_receiver_dropped() {
let (tx, rx) = mpsc::channel::<String>(1);
drop(rx);
assert!(!send_string_logged(&tx, "x".to_string(), "ctx_drop").await);
}
#[tokio::test]
async fn cooperative_cancel_flips_on_send_failure() {
let (tx, rx) = mpsc::channel::<String>(1);
drop(rx);
let cancel = AtomicBool::new(false);
assert!(
!send_string_logged_cooperative_cancel(
&tx,
"y".to_string(),
"ctx_coop",
Some(&cancel),
)
.await
);
assert!(cancel.load(Ordering::SeqCst));
}
#[tokio::test]
async fn cooperative_cancel_no_flag_without_atomic() {
let (tx, rx) = mpsc::channel::<String>(1);
drop(rx);
assert!(
!send_string_logged_cooperative_cancel(&tx, "z".to_string(), "ctx_no_atomic", None,)
.await
);
}
}