W3C home > Mailing lists > Public > public-webrtc-logs@w3.org > January 2019

Re: [webrtc-pc] (How) does SCTP handle CPU-bound congestion on JavaScript thread? (#2086)

From: henbos via GitHub <sysbot+gh@w3.org>
Date: Tue, 29 Jan 2019 22:02:04 +0000
To: public-webrtc-logs@w3.org
Message-ID: <issue_comment.created-458724853-1548799322-sysbot+gh@w3.org>
If you don't think being CPU-bound is a big issue then you shouldn't be affected by CPU-bound applications letting the receive buffer get congested, as long as it doesn't have significant downsides for non-CPU-bound scenarios.

I think we can allow [larger messages than receive buffers](http://i.imgur.com/4lpVkTS.gif) with intermediate buffers, and perhaps we could avoid data channels from monopolizing by letting the sender allocate messages for its channels before other channels are allowed to steal that buffer space. If [stream schedulers](https://tools.ietf.org/html/draft-ietf-tsvwg-sctp-ndata-13#section-3) like a round robin scheduler (SCTP_SS_RR) with interleaving enabled is used we could prevent starvation too?

Forgive me if all of this has been covered before or if I'm speaking out of ignorance. I am not an SCTP expert. I'm throwing ideas out there with little background.

**Proposal Part 1: The receiver**

> Let each underlying SCTP data channel have an internal slot [[MessageBuffer]] that is a buffer of size max-message-size. (Note: We will only touch [[MessageBuffer]] on the network thread.)
>
> **process chunk steps** (on network thread): When receiving an I-DATA chunk into the receive buffer a_rwnd is decremented by the size of the chunk. If there is enough space in the corresponding channel's [[MessageBuffer]], move the chunk into [[MessageBuffer]] and increment a_rwnd by the size of the chunk.
> When a [[MessageBuffer]] is full, queue a task (to the JavaScript thread) with a _message_ set to the value of [[MessageBuffer]] that runs the following steps:
> 1. In parallel, run the following steps (on the network thread):
> - Clear [[MessageBuffer]] and run the **process chunk steps** on any I-DATA chunks for this channel that is currently in the receive buffer.
> 2. Convert _message_ to the appropriate JavaScript object and fire "onmessage" with it.

This takes care of the receiver side without sacrificing large messages. One data channel can block other data channels if that channel's buffer is full _AND_ the receive buffer is full. But we would only get this far if we are CPU-bound and can't handle more messages anyway. As long as chunks are arriving for all channels, all channels will have their [[MessageBuffer]] filled up and emptied accordingly. You _can't_ ignore a particular channel, all channels' messages are handled (whether or not the application has an onmessage listener).

If the receive buffer gets full, whether because of network performance or being CPU-bound, the sender will know about it with `rwnd`.

**Proposal Part 2: The sender**

We want to avoid one channel monopolizing the send buffer. (E.g. channel A sends lots of small messages so that channel B cannot send its large messages because the buffer is full. As soon as the buffer frees up a little bit, channel A fills it up again. Channel B is a sad data channel.) These are examples of `rwnd` being low or messages being really big.

New API:
```
partial interface RTCSctpTransport {
  Promise<unsigned long> allocateSendMessage(unsigned long size);
};
```

_allocateSendMessage()_ will affectively decrease the _available buffer size of the send()_ (see step 5 of [send()](https://w3c.github.io/webrtc-pc/#dfn-send)). When the true underlying buffer has _size_ amount of space available, the _available buffer size of the send()_ will be increased by _size_ again and the return promise is resolved with _size_. This guarantees that subsequent send() calls with messages of this size will succeed step 5, and in the meantime blocking other channels from performing send() if that would cause our allocated message to not fit into the buffer.

Multiple allocations (multiple allocateSendMessage() calls) are queued. Example:
```
sctpTransport.allocateSendMessage(aMessage.length).then(() => a.send(aMessage));
sctpTransport.allocateSendMessage(bMessage.length).then(() => b.send(bMessage));
```
If both messages fit in the buffer at the same time both promises will be resolved immediately, otherwise a's allocation is queued before b's allocation. There is no way for b's message(s) to block a's message from sending because of the order. This allows custom application logic for how to prioritize different channels, e.g. "for every X message/bytes sent by channel A I want Y messages/bytes to be sent by channel B".

Does this address most of the issues within the scope of 1.0, or is are my proposals fundamentally flawed?

-- 
GitHub Notification of comment by henbos
Please view or discuss this issue at https://github.com/w3c/webrtc-pc/issues/2086#issuecomment-458724853 using your GitHub account
Received on Tuesday, 29 January 2019 22:02:06 UTC

This archive was generated by hypermail 2.3.1 : Tuesday, 4 June 2019 15:32:54 UTC