Files
upstream-livekit/pkg/sfu/datachannel/datachannel_writer.go
upstream-mirror 78704103fc
Some checks failed
Release to Docker / docker (push) Failing after 2s
Release Test Server to Docker / docker (push) Failing after 3s
Test / test (push) Failing after 16s
snapshot of https://github.com/livekit/livekit.git @ 08c5433449c106943231037d0b892895f6e92d3b (2026-09-15)
2026-09-15 16:14:22 +05:00

153 lines
4.6 KiB
Go

package datachannel
import (
"context"
"errors"
"fmt"
"time"
"github.com/pion/datachannel"
"github.com/livekit/protocol/utils/mono"
)
const (
singleWriteTimeout = 50 * time.Millisecond
)
var (
ErrDataDroppedBySlowReader = errors.New("data dropped by slow reader")
ErrDataDroppedByHighBufferedAmount = errors.New("data dropped due to high buffered amount")
)
type BufferedAmountGetter interface {
BufferedAmount() uint64
}
type DataChannelWriter[T BufferedAmountGetter] struct {
bufferGetter T
rawDC datachannel.ReadWriteCloserDeadliner
slowThreshold int
rate *BitrateCalculator
reliable bool
targetLatency time.Duration
minBufferedAmount uint64
}
// NewDataChannelWriterReliable creates a new DataChannelWriter for reliable data channel by
// detaching it, when writing to the datachanel times out, it will block and retry if the
// receiver's bitrate is above the slowThreshold or drop the data if it's below the threshold.
// If the slowThreshold is 0, it will never retry on write timeout.
func NewDataChannelWriterReliable[T BufferedAmountGetter](bufferGetter T, rawDC datachannel.ReadWriteCloserDeadliner, slowThreshold int) *DataChannelWriter[T] {
var rate *BitrateCalculator
if slowThreshold > 0 {
rate = NewBitrateCalculator(BitrateDuration, BitrateWindow, BitrateModeBusyOnly)
}
return &DataChannelWriter[T]{
bufferGetter: bufferGetter,
rawDC: rawDC,
slowThreshold: slowThreshold,
rate: rate,
reliable: true,
}
}
// NewDataChannelWriterUnreliable creates a new DataChannelWriter for unreliable data channel.
// It will drop data when the buffered amount is too high to maintain the target latency.
// The latency is estimated based on the bitrate in past 1 second. If targetLatency is 0, no
// buffering control is applied.
func NewDataChannelWriterUnreliable[T BufferedAmountGetter](bufferGetter T, rawDC datachannel.ReadWriteCloserDeadliner, targetLatency time.Duration, minBufferedAmount uint64) *DataChannelWriter[T] {
var rate *BitrateCalculator
if targetLatency > 0 {
rate = NewBitrateCalculator(BitrateDuration, BitrateWindow, BitrateModeBusyOnly)
}
return &DataChannelWriter[T]{
bufferGetter: bufferGetter,
rawDC: rawDC,
rate: rate,
targetLatency: targetLatency,
minBufferedAmount: minBufferedAmount,
reliable: false,
}
}
func (w *DataChannelWriter[T]) BufferedAmountGetter() T {
return w.bufferGetter
}
func (w *DataChannelWriter[T]) Write(p []byte) (n int, err error) {
if w.reliable {
return w.writeReliable(p)
} else {
return w.writeUnreliable(p)
}
}
func (w *DataChannelWriter[T]) writeReliable(p []byte) (n int, err error) {
for {
err = w.rawDC.SetWriteDeadline(time.Now().Add(singleWriteTimeout))
if err != nil {
return 0, err
}
n, err = w.rawDC.Write(p)
if w.slowThreshold == 0 {
return
}
now := mono.Now()
w.rate.AddBytes(n, int(w.bufferGetter.BufferedAmount()), now)
// retry if the write timed out on a non-slow receiver
if errors.Is(err, context.DeadlineExceeded) {
if bitrate, ok := w.rate.Bitrate(now); !ok || bitrate >= w.slowThreshold {
continue
} else {
err = fmt.Errorf("%w: bitrate %d, threshold %d", ErrDataDroppedBySlowReader, bitrate, w.slowThreshold)
}
}
return
}
}
func (w *DataChannelWriter[T]) writeUnreliable(p []byte) (n int, err error) {
if w.targetLatency == 0 {
err = w.rawDC.SetWriteDeadline(time.Now().Add(singleWriteTimeout))
if err != nil {
return 0, err
}
return w.rawDC.Write(p)
}
if bitrate, ok := w.rate.Bitrate(time.Now()); ok {
// control buffer latency to target
bufferedAmount := w.bufferGetter.BufferedAmount()
targetLatencyLimit := uint64(time.Duration(bitrate) * w.targetLatency / 8 / time.Second)
if bufferedAmount > targetLatencyLimit && bufferedAmount > w.minBufferedAmount {
return 0, fmt.Errorf(
"%w: bitrate %d, buffered amount %d, target latency limit %d, min buffered amount %d",
ErrDataDroppedByHighBufferedAmount,
bitrate,
bufferedAmount,
targetLatencyLimit,
w.minBufferedAmount,
)
}
}
err = w.rawDC.SetWriteDeadline(time.Now().Add(singleWriteTimeout))
if err != nil {
return 0, err
}
n, err = w.rawDC.Write(p)
// Track drained bytes on every write (not just failures) so the bitrate
// calculator has samples; otherwise Bitrate() has no data and the
// latency-based drop threshold above collapses to the static minBufferedAmount.
w.rate.AddBytes(n, int(w.bufferGetter.BufferedAmount()), mono.Now())
return
}
func (w *DataChannelWriter[T]) Close() error {
return w.rawDC.Close()
}