use anyhow::Result;
use bytes::BytesMut;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::Duration;
use webrtc::data_channel::{DataChannel, DataChannelEvent, RTCDataChannelInit};
use webrtc::peer_connection::{PeerConnection, PeerConnectionBuilder, PeerConnectionEventHandler};
use webrtc::peer_connection::{RTCIceGatheringState, RTCPeerConnectionState};
use webrtc::runtime::{Runtime, Sender, channel};
mod common;
use common::{block_on, runtime, sleep, timeout};
const CHUNK: usize = 4096; const TOTAL_BYTES: usize = 8 * 1024 * 1024;
const SEND_LIMIT: usize = 256 * 1024;
const OUTSTANDING_BOUND: usize = SEND_LIMIT + 64 * 1024;
struct GatherHandler {
gather_tx: Sender<()>,
connected_tx: Sender<()>,
}
#[async_trait::async_trait]
impl PeerConnectionEventHandler for GatherHandler {
async fn on_ice_gathering_state_change(&self, state: RTCIceGatheringState) {
if state == RTCIceGatheringState::Complete {
let _ = self.gather_tx.try_send(());
}
}
async fn on_connection_state_change(&self, state: RTCPeerConnectionState) {
if state == RTCPeerConnectionState::Connected {
let _ = self.connected_tx.try_send(());
}
}
}
struct ReceiverHandler {
gather_tx: Sender<()>,
connected_tx: Sender<()>,
received: Arc<AtomicUsize>,
runtime: Arc<dyn Runtime>,
}
#[async_trait::async_trait]
impl PeerConnectionEventHandler for ReceiverHandler {
async fn on_ice_gathering_state_change(&self, state: RTCIceGatheringState) {
if state == RTCIceGatheringState::Complete {
let _ = self.gather_tx.try_send(());
}
}
async fn on_connection_state_change(&self, state: RTCPeerConnectionState) {
if state == RTCPeerConnectionState::Connected {
let _ = self.connected_tx.try_send(());
}
}
async fn on_data_channel(&self, dc: Arc<dyn DataChannel>) {
let received = self.received.clone();
self.runtime.spawn(Box::pin(async move {
while let Some(event) = dc.poll().await {
match event {
DataChannelEvent::OnMessage(msg) => {
received.fetch_add(msg.data.len(), Ordering::Relaxed);
}
DataChannelEvent::OnClose | DataChannelEvent::OnError => break,
_ => {}
}
}
}));
}
}
#[test]
fn test_data_channel_blocking_send_bounded_and_delivered() {
block_on(run()).unwrap();
}
async fn run() -> Result<()> {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.is_test(true)
.try_init()
.ok();
let runtime = runtime();
let (snd_gather_tx, mut snd_gather_rx) = channel::<()>(1);
let (snd_conn_tx, mut snd_conn_rx) = channel::<()>(1);
let (rcv_gather_tx, mut rcv_gather_rx) = channel::<()>(1);
let (rcv_conn_tx, mut rcv_conn_rx) = channel::<()>(1);
let received = Arc::new(AtomicUsize::new(0));
let sender_pc = PeerConnectionBuilder::new()
.with_handler(Arc::new(GatherHandler {
gather_tx: snd_gather_tx,
connected_tx: snd_conn_tx,
}))
.with_runtime(runtime.clone())
.with_udp_addrs(vec!["127.0.0.1:0".to_string()])
.with_data_channel_send_buffer_limit(SEND_LIMIT)
.build()
.await?;
let dc = sender_pc
.create_data_channel("blocking", Some(RTCDataChannelInit::default()))
.await?;
let (open_tx, mut open_rx) = channel::<()>(1);
{
let dc = dc.clone();
runtime.spawn(Box::pin(async move {
while let Some(event) = dc.poll().await {
match event {
DataChannelEvent::OnOpen => {
let _ = open_tx.try_send(());
}
DataChannelEvent::OnClose => break,
_ => {}
}
}
}));
}
let offer = sender_pc.create_offer(None).await?;
sender_pc.set_local_description(offer).await?;
let _ = timeout(Duration::from_secs(5), snd_gather_rx.recv()).await;
let offer_sdp = sender_pc
.local_description()
.await
.expect("sender local description");
let receiver_pc = PeerConnectionBuilder::new()
.with_handler(Arc::new(ReceiverHandler {
gather_tx: rcv_gather_tx,
connected_tx: rcv_conn_tx,
received: received.clone(),
runtime: runtime.clone(),
}))
.with_runtime(runtime.clone())
.with_udp_addrs(vec!["127.0.0.1:0".to_string()])
.build()
.await?;
receiver_pc.set_remote_description(offer_sdp).await?;
let answer = receiver_pc.create_answer(None).await?;
receiver_pc.set_local_description(answer).await?;
let _ = timeout(Duration::from_secs(5), rcv_gather_rx.recv()).await;
let answer_sdp = receiver_pc
.local_description()
.await
.expect("receiver local description");
sender_pc.set_remote_description(answer_sdp).await?;
timeout(Duration::from_secs(15), snd_conn_rx.recv())
.await
.map_err(|_| anyhow::anyhow!("timeout: sender connect"))?;
timeout(Duration::from_secs(5), rcv_conn_rx.recv())
.await
.map_err(|_| anyhow::anyhow!("timeout: receiver connect"))?;
timeout(Duration::from_secs(10), open_rx.recv())
.await
.map_err(|_| anyhow::anyhow!("timeout: data channel open"))?;
let flood_done = Arc::new(AtomicBool::new(false));
let flood_fatal = Arc::new(AtomicBool::new(false));
{
let dc = dc.clone();
let flood_done = flood_done.clone();
let flood_fatal = flood_fatal.clone();
runtime.spawn(Box::pin(async move {
let chunk = BytesMut::from(vec![0u8; CHUNK].as_slice());
let mut sent = 0usize;
while sent < TOTAL_BYTES {
if let Err(e) = dc.send(chunk.clone()).await {
log::error!("blocking flood: unexpected send error: {e:?}");
flood_fatal.store(true, Ordering::Relaxed);
break;
}
sent += CHUNK;
}
flood_done.store(true, Ordering::Relaxed);
}));
}
let mut max_outstanding = 0usize;
let deadline_ticks = 600; let mut ticks = 0;
while !flood_done.load(Ordering::Relaxed) {
let o = dc.outstanding_bytes().await?;
max_outstanding = max_outstanding.max(o);
assert!(
o <= OUTSTANDING_BOUND,
"outstanding_bytes {o} exceeded the send-buffer bound {OUTSTANDING_BOUND} during \
the blocking flood — send() is not blocking on the send-buffer limit"
);
ticks += 1;
assert!(
ticks < deadline_ticks,
"blocking flood did not complete within 60s"
);
sleep(Duration::from_millis(100)).await;
}
assert!(
!flood_fatal.load(Ordering::Relaxed),
"blocking flood hit an unexpected send error (see log)"
);
let mut final_outstanding = dc.outstanding_bytes().await?;
for _ in 0..300 {
final_outstanding = dc.outstanding_bytes().await?;
if final_outstanding < 64 * 1024 {
break;
}
sleep(Duration::from_millis(100)).await;
}
assert!(
final_outstanding < 64 * 1024,
"outstanding_bytes did not drain (leak / undelivered?): {final_outstanding} bytes \
still outstanding"
);
let got = received.load(Ordering::Relaxed);
log::info!("blocking-send: max_outstanding={max_outstanding} app_received={got}");
sender_pc.close().await?;
receiver_pc.close().await?;
Ok(())
}