Skip to content

DataChannel: add bufferedAmount back-pressure surface (W3C §6.2)#45

Merged
nus merged 1 commit into
mainfrom
datachannel-buffered-amount
Jun 9, 2026
Merged

DataChannel: add bufferedAmount back-pressure surface (W3C §6.2)#45
nus merged 1 commit into
mainfrom
datachannel-buffered-amount

Conversation

@nus

@nus nus commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Addresses the DataChannel flow-control gap from the RFC/W3C divergence audit.

Problem

DataChannel had no flow-control surface: send()/sendBinary() pushed straight to SCTP with no visibility into how much was still in flight, so callers couldn't back-pressure large transfers.

Change

  • SCTP (state_machine.dart): when a SACK clears the retransmit queue, tally acked application bytes per stream (excluding DCEP control and the empty-message padding byte) and report them via a new onBytesAcked(streamId, bytes) callback — same streamId fan-out idiom as onData/onStreamReset.
  • DataChannel (data_channel.dart): bufferedAmount (un-acked app bytes; += on send, -= on ack), bufferedAmountLowThreshold, and onBufferedAmountLow (fires on the downward threshold crossing). PeerConnection routes onBytesAcked to the owning channel.

This gives app-level back-pressure: a caller that pauses while bufferedAmount is high and resumes on onBufferedAmountLow is paced by the peer's SACK rate.

Scope / follow-ups (BACKLOG)

  • bufferedAmount is un-acked (sent-but-not-acked) rather than the W3C "queued-but-not-yet-sent", because sendData transmits immediately — documented on the API and in BACKLOG.
  • Internal SCTP send flow control (hold chunks when the remote a_rwnd is exhausted) and binaryType (no Dart analog — messages are always Uint8List) are left as BACKLOG items.
  • Separately discovered while testing: webdartc↔webdartc data channels never finish the DCEP open (the opener's onOpen never fires in pure loopback; DC e2e tests all use a browser). Recorded in BACKLOG — it's why the integration test runs against Chrome rather than loopback.

Tests

  • SCTP onBytesAcked (buffered_amount_test.dart): per-stream tally, empty-message = 0, two state machines.
  • Real-Chrome e2e (Scenario 1): the full lifecycle — bufferedAmount rose to 66583, drained to 0, onBufferedAmountLow fired.
  • The stats loopback harness was extracted to test/peer_connection/loopback.dart for reuse.

dart analyze clean; 664 unit + 22 e2e pass. Reviewed with /simplify.

🤖 Generated with Claude Code

DataChannel had no flow-control surface: send()/sendBinary() pushed to SCTP
with no visibility into how much was still in flight, so callers couldn't
back-pressure large transfers.

- SCTP: tally acked application bytes per stream when a SACK clears the
  retransmit queue (excluding DCEP control + the empty-message padding
  byte) and report them via a new onBytesAcked callback.
- DataChannel: bufferedAmount (un-acked app bytes; += on send, -= on ack),
  bufferedAmountLowThreshold, and onBufferedAmountLow (fires on the
  downward threshold crossing). PeerConnection routes onBytesAcked to the
  owning channel.

This gives app-level back-pressure: a caller that pauses while
bufferedAmount is high and resumes on onBufferedAmountLow is paced by the
peer's SACK rate. (Internal SCTP send flow control — holding chunks when
the remote a_rwnd is exhausted — and binaryType are left as BACKLOG
follow-ups; binaryType has no Dart analog since messages are always
Uint8List.)

Tested: SCTP onBytesAcked accounting (per-stream, empty=0) via two
state machines; and the full bufferedAmount lifecycle against real Chrome
in Scenario 1 (rose to 66583, drained to 0, onBufferedAmountLow fired).
The stats loopback harness was extracted to test/peer_connection/loopback.dart
for reuse. A separate finding — webdartc↔webdartc data channels never finish
the DCEP open — is recorded in BACKLOG (it blocked a pure-loopback DC test).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nus nus merged commit 5c73381 into main Jun 9, 2026
29 of 30 checks passed
@nus nus deleted the datachannel-buffered-amount branch June 9, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant