Skip to content

Commit 7013dea

Browse files
authored
feat(webrtc): producer backpressure (#575)
Sctp data channels have a limit over the amount of messages that can be saved within its internal buffer before sending them. Writes fail if there is no more space, messages needs to be saved as pending and sent again as soon as there is avaiable space.
1 parent 4da8723 commit 7013dea

2 files changed

Lines changed: 200 additions & 55 deletions

File tree

src/transport/webrtc/connection.rs

Lines changed: 178 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@ use crate::{
3838
PeerId,
3939
};
4040

41-
use futures::{Stream, StreamExt};
41+
use futures::{task::AtomicWaker, Stream, StreamExt};
4242
use indexmap::IndexMap;
4343
use str0m::{
44-
channel::{ChannelConfig, ChannelId},
44+
channel::{Channel, ChannelConfig, ChannelId},
4545
net::{Protocol as Str0mProtocol, Receive},
4646
Event, IceConnectionState, Input, Output, Rtc,
4747
};
4848
use tokio::{net::UdpSocket, sync::mpsc::Receiver};
4949

5050
use std::{
51-
collections::HashMap,
51+
collections::{HashMap, HashSet, VecDeque},
5252
net::SocketAddr,
5353
pin::Pin,
5454
sync::Arc,
@@ -59,6 +59,12 @@ use std::{
5959
/// Logging target for the file.
6060
const LOG_TARGET: &str = "litep2p::webrtc::connection";
6161

62+
/// Threshold under which str0m emits Event::ChannelBufferedAmountLow.
63+
const BACKPRESSURE_THRESHOLD: usize = 16 * (1 << 10); // 16 KB
64+
65+
/// Maximum number of pending messages supported per channel.
66+
const MAX_PENDING_PER_CHANNEL: usize = 16;
67+
6268
/// Opening channel context.
6369
#[derive(Debug)]
6470
struct ChannelContext {
@@ -88,6 +94,12 @@ struct SubstreamHandleSet {
8894

8995
/// Substream handles.
9096
handles: IndexMap<ChannelId, SubstreamHandle>,
97+
98+
/// Substreams that have pending messages.
99+
pending: HashSet<ChannelId>,
100+
101+
/// Waker used to drive the stream when no handle can make progress.
102+
waker: AtomicWaker,
91103
}
92104

93105
impl SubstreamHandleSet {
@@ -96,6 +108,8 @@ impl SubstreamHandleSet {
96108
Self {
97109
index: 0usize,
98110
handles: IndexMap::new(),
111+
pending: HashSet::new(),
112+
waker: AtomicWaker::new(),
99113
}
100114
}
101115

@@ -107,11 +121,25 @@ impl SubstreamHandleSet {
107121
/// Insert new handle to [`SubstreamHandleSet`].
108122
pub fn insert(&mut self, key: ChannelId, handle: SubstreamHandle) {
109123
assert!(self.handles.insert(key, handle).is_none());
124+
self.waker.wake();
110125
}
111126

112127
/// Remove handle from [`SubstreamHandleSet`].
113128
pub fn remove(&mut self, key: &ChannelId) -> Option<SubstreamHandle> {
114-
self.handles.shift_remove(key)
129+
self.pending.remove(key);
130+
self.handles.swap_remove(key)
131+
}
132+
133+
/// Mark channel as having pending messages.
134+
pub fn add_pending(&mut self, key: ChannelId) {
135+
self.pending.insert(key);
136+
}
137+
138+
/// Unmark channel as having pending messages.
139+
pub fn clear_pending(&mut self, key: &ChannelId) {
140+
if self.pending.remove(key) {
141+
self.waker.wake();
142+
}
115143
}
116144
}
117145

@@ -120,7 +148,10 @@ impl Stream for SubstreamHandleSet {
120148

121149
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
122150
let len = match self.handles.len() {
123-
0 => return Poll::Pending,
151+
0 => {
152+
self.waker.register(cx.waker());
153+
return Poll::Pending;
154+
}
124155
len => len,
125156
};
126157
let start_index = self.index;
@@ -129,13 +160,19 @@ impl Stream for SubstreamHandleSet {
129160
let index = self.index % len;
130161
self.index += 1;
131162

132-
let (key, stream) = self.handles.get_index_mut(index).expect("handle to exist");
133-
match stream.poll_next_unpin(cx) {
134-
Poll::Pending => {}
135-
Poll::Ready(event) => return Poll::Ready(Some((*key, event))),
163+
let key =
164+
self.handles.get_index(index).map(|(k, _)| k).cloned().expect("handle to exist");
165+
166+
if !self.pending.contains(&key) {
167+
let (key, stream) = self.handles.get_index_mut(index).expect("handle to exist");
168+
match stream.poll_next_unpin(cx) {
169+
Poll::Pending => {}
170+
Poll::Ready(event) => return Poll::Ready(Some((*key, event))),
171+
}
136172
}
137173

138174
if self.index == start_index + len {
175+
self.waker.register(cx.waker());
139176
break Poll::Pending;
140177
}
141178
}
@@ -205,6 +242,10 @@ pub struct WebRtcConnection {
205242
/// Pending outbound channels.
206243
pending_outbound: HashMap<ChannelId, ChannelContext>,
207244

245+
/// Pending outbound messages,
246+
/// at most [`MAX_PENDING_PER_CHANNEL`] per channel.
247+
pending_messages: HashMap<ChannelId, VecDeque<Vec<u8>>>,
248+
208249
/// Open channels.
209250
channels: HashMap<ChannelId, ChannelState>,
210251

@@ -234,6 +275,7 @@ impl WebRtcConnection {
234275
endpoint,
235276
dgram_rx,
236277
pending_outbound: HashMap::new(),
278+
pending_messages: HashMap::new(),
237279
channels: HashMap::new(),
238280
handles: SubstreamHandleSet::new(),
239281
}
@@ -259,6 +301,10 @@ impl WebRtcConnection {
259301
"channel opened",
260302
);
261303

304+
if let Some(mut channel) = self.rtc.channel(channel_id) {
305+
channel.set_buffered_amount_low_threshold(BACKPRESSURE_THRESHOLD);
306+
}
307+
262308
let Some(mut context) = self.pending_outbound.remove(&channel_id) else {
263309
tracing::trace!(
264310
target: LOG_TARGET,
@@ -281,11 +327,7 @@ impl WebRtcConnection {
281327
WebRtcDialerState::propose(context.protocol.clone(), fallback_names)?;
282328
let message = WebRtcMessage::encode(message, None);
283329

284-
self.rtc
285-
.channel(channel_id)
286-
.ok_or(Error::ChannelDoesntExist)?
287-
.write(true, message.as_ref())
288-
.map_err(Error::WebRtc)?;
330+
self.write(channel_id, message)?;
289331

290332
self.channels.insert(
291333
channel_id,
@@ -298,6 +340,108 @@ impl WebRtcConnection {
298340
Ok(())
299341
}
300342

343+
// Attempt to write a message over the specified channel,
344+
// save the message as pending if `WebRtcConnection` didn't have
345+
// enough space.
346+
fn write(&mut self, channel_id: ChannelId, message: Vec<u8>) -> Result<(), Error> {
347+
let Some(mut channel) = self.rtc.channel(channel_id) else {
348+
tracing::trace!(
349+
target: LOG_TARGET,
350+
peer = ?self.peer,
351+
?channel_id,
352+
"protocol rejected received for non-existing channel",
353+
);
354+
return Err(Error::ChannelDoesntExist);
355+
};
356+
357+
match self.pending_messages.get_mut(&channel_id) {
358+
Some(messages) if !messages.is_empty() => {
359+
if messages.len() >= MAX_PENDING_PER_CHANNEL {
360+
return Err(Error::ChannelClogged);
361+
}
362+
363+
messages.push_back(message);
364+
return Ok(());
365+
}
366+
_ => (),
367+
}
368+
369+
let succeeded = Self::channel_write(&mut channel, channel_id, &message, self.peer)?;
370+
371+
if !succeeded {
372+
let pending_messages = self.pending_messages.entry(channel_id).or_default();
373+
if pending_messages.len() >= MAX_PENDING_PER_CHANNEL {
374+
return Err(Error::ChannelClogged);
375+
}
376+
377+
pending_messages.push_back(message);
378+
self.handles.add_pending(channel_id);
379+
return Ok(());
380+
}
381+
382+
Ok(())
383+
}
384+
385+
fn channel_write(
386+
channel: &mut Channel<'_>,
387+
channel_id: ChannelId,
388+
message: &[u8],
389+
peer: PeerId,
390+
) -> Result<bool, Error> {
391+
match channel.write(true, message) {
392+
Ok(succeeded) => Ok(succeeded),
393+
Err(e) => {
394+
tracing::trace!(
395+
target: LOG_TARGET,
396+
peer = ?peer,
397+
?channel_id,
398+
?e,
399+
"failed to write message to webrtc channel",
400+
);
401+
Err(Error::WebRtc(e))
402+
}
403+
}
404+
}
405+
406+
// Attempt to write all pending messages of the specified ChannelId.
407+
// Returns whether all messages have been sent or not.
408+
fn write_pending(&mut self, channel_id: ChannelId) -> Result<bool, Error> {
409+
let Some(mut channel) = self.rtc.channel(channel_id) else {
410+
tracing::trace!(
411+
target: LOG_TARGET,
412+
peer = ?self.peer,
413+
?channel_id,
414+
"protocol rejected received for non-existing channel",
415+
);
416+
return Err(Error::ChannelDoesntExist);
417+
};
418+
419+
loop {
420+
let Some(pending_messages) = self.pending_messages.get_mut(&channel_id) else {
421+
// This should never happen, `write_pending` should be called
422+
// for only the channel with pending messages. Treat as a no-op
423+
// instead of panicking to stay defensive.
424+
self.handles.clear_pending(&channel_id);
425+
return Ok(true);
426+
};
427+
428+
let Some(message) = pending_messages.front() else {
429+
self.pending_messages.remove(&channel_id);
430+
self.handles.clear_pending(&channel_id);
431+
break Ok(true);
432+
};
433+
434+
let succeeded = Self::channel_write(&mut channel, channel_id, message, self.peer)?;
435+
if succeeded {
436+
self.pending_messages
437+
.get_mut(&channel_id)
438+
.and_then(|messages| messages.pop_front());
439+
} else {
440+
break Ok(false);
441+
}
442+
}
443+
}
444+
301445
/// Handle closed channel.
302446
async fn on_channel_closed(&mut self, channel_id: ChannelId) -> crate::Result<()> {
303447
tracing::trace!(
@@ -309,6 +453,7 @@ impl WebRtcConnection {
309453

310454
self.pending_outbound.remove(&channel_id);
311455
self.channels.remove(&channel_id);
456+
self.pending_messages.remove(&channel_id);
312457
self.handles.remove(&channel_id);
313458

314459
Ok(())
@@ -351,14 +496,8 @@ impl WebRtcConnection {
351496
| ListenerSelectResult::PendingProtocol { message } => (message, None),
352497
};
353498

354-
self.rtc
355-
.channel(channel_id)
356-
.ok_or(Error::ChannelDoesntExist)?
357-
.write(
358-
true,
359-
WebRtcMessage::encode(response.to_vec(), None).as_ref(),
360-
)
361-
.map_err(Error::WebRtc)?;
499+
let message = WebRtcMessage::encode(response.to_vec(), None);
500+
self.write(channel_id, message)?;
362501

363502
let Some(protocol) = negotiated else {
364503
tracing::trace!(
@@ -468,30 +607,9 @@ impl WebRtcConnection {
468607

469608
let message = WebRtcMessage::encode(message, None);
470609

471-
let Some(mut channel) = self.rtc.channel(channel_id) else {
472-
tracing::trace!(
473-
target: LOG_TARGET,
474-
peer = ?self.peer,
475-
?channel_id,
476-
"protocol rejected received for non-existing channel",
477-
);
478-
return Err(SubstreamError::NegotiationError(
479-
NegotiationError::Failed.into(),
480-
));
481-
};
482-
483-
if let Err(err) = channel.write(true, message.as_ref()) {
484-
tracing::trace!(
485-
target: LOG_TARGET,
486-
peer = ?self.peer,
487-
?channel_id,
488-
?err,
489-
"failed to write multistream-select fallback proposal",
490-
);
491-
return Err(SubstreamError::NegotiationError(
492-
NegotiationError::Failed.into(),
493-
));
494-
};
610+
self.write(channel_id, message).map_err(|_| {
611+
SubstreamError::NegotiationError(NegotiationError::Failed.into())
612+
})?;
495613

496614
self.channels.insert(
497615
channel_id,
@@ -755,12 +873,8 @@ impl WebRtcConnection {
755873
"send data",
756874
);
757875

758-
self.rtc
759-
.channel(channel_id)
760-
.ok_or(Error::ChannelDoesntExist)?
761-
.write(true, WebRtcMessage::encode(data, flag).as_ref())
762-
.map_err(Error::WebRtc)
763-
.map(|_| ())
876+
let message = WebRtcMessage::encode(data, flag);
877+
self.write(channel_id, message)
764878
}
765879

766880
/// Open outbound substream.
@@ -868,6 +982,8 @@ impl WebRtcConnection {
868982
continue;
869983
}
870984
Event::ChannelClose(channel_id) => {
985+
// This event is emitted once the rtc instance
986+
// completes the call to `close_data_channel(channel_id)`.
871987
if let Err(error) = self.on_channel_closed(channel_id).await {
872988
tracing::debug!(
873989
target: LOG_TARGET,
@@ -893,6 +1009,13 @@ impl WebRtcConnection {
8931009

8941010
continue;
8951011
}
1012+
Event::ChannelBufferedAmountLow(_channel_id) => {
1013+
let channel_ids: Vec<_> = self.pending_messages.keys().cloned().collect();
1014+
for channel_id in channel_ids {
1015+
let _ = self.write_pending(channel_id);
1016+
}
1017+
continue;
1018+
}
8961019
event => {
8971020
tracing::debug!(
8981021
target: LOG_TARGET,
@@ -959,6 +1082,9 @@ impl WebRtcConnection {
9591082
?error,
9601083
"failed to send data to remote peer",
9611084
);
1085+
1086+
self.channels.insert(channel_id, ChannelState::Closing);
1087+
self.rtc.direct_api().close_data_channel(channel_id);
9621088
}
9631089
}
9641090
Some((_, Some(SubstreamEvent::RecvClosed))) => {}

0 commit comments

Comments
 (0)