mirror of
https://github.com/openlibrecommunity/olcrtc.git
synced 2026-07-03 14:05:39 +02:00
13f77b1da7
add a silent dead-link mode to the memory carrier (link stays up, frames vanish) and a flag-gated TestDeadLinkDetectionWindow that measures how long the datachannel client takes to notice a dead link and reconnect. on the conservative liveness window it reconnects in ~58s; with the buggy 120s smux keepalive it fails to detect within 75s. also drop dead restartControlKCP (both callers moved to restartControlKCPWithHeader)
121 lines
4.0 KiB
Go
121 lines
4.0 KiB
Go
package e2e
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/openlibrecommunity/olcrtc/internal/client"
|
|
"github.com/openlibrecommunity/olcrtc/internal/control"
|
|
"github.com/openlibrecommunity/olcrtc/internal/server"
|
|
)
|
|
|
|
// deadLinkEnabled gates TestDeadLinkDetectionWindow. It runs a real
|
|
// in-process client/server tunnel over the memory carrier, then silently
|
|
// black-holes the carrier (link stays "up", frames vanish) and measures how
|
|
// long the client takes to notice the dead link and trigger a reconnect.
|
|
//
|
|
// This is the exact failure mode behind the jitsi/datachannel regression:
|
|
// the peer leaves but the WebRTC PC has not torn down, so the only signal is
|
|
// the control-stream liveness timeout. The bad commit widened that timeout to
|
|
// 120s globally; the fix scopes the long window to ControlPlane transports so
|
|
// datachannel detects a dead link on the conservative ~30s smux keepalive.
|
|
var deadLinkEnabled = flag.Bool( //nolint:gochecknoglobals // package-level state intentional
|
|
"olcrtc.deadlink",
|
|
false,
|
|
"run TestDeadLinkDetectionWindow (measures dead-link detection latency)",
|
|
)
|
|
|
|
// TestDeadLinkDetectionWindow proves the datachannel dead-link detection
|
|
// window returned to the conservative band after the fix. It fails if the
|
|
// client does not detect the silently-dead carrier and reconnect within the
|
|
// expected window (well under the buggy 120s, comfortably above the
|
|
// conservative 30s keepalive timeout).
|
|
func TestDeadLinkDetectionWindow(t *testing.T) {
|
|
if !*deadLinkEnabled {
|
|
t.Skip("dead-link test disabled; pass -olcrtc.deadlink to enable")
|
|
}
|
|
|
|
const (
|
|
transportName = transportData // datachannel: no isolated control plane
|
|
// The conservative smux keepalive timeout is 30s (interval 10s). Allow
|
|
// generous headroom for scheduling + a probe cycle, but stay well below
|
|
// the buggy 120s so the bad commit fails this bound.
|
|
detectionBudget = 75 * time.Second
|
|
setupBudget = 30 * time.Second
|
|
)
|
|
|
|
carrierName, room := registerMemoryCarrier(t)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
|
|
socksAddr := freeLocalAddr(ctx, t)
|
|
|
|
// Count reconnects observed by the client's health hook. A dead-link
|
|
// detection manifests as a reconnect (control loop ends -> handleReconnect).
|
|
var reconnects atomic.Uint64
|
|
detected := make(chan struct{}, 1)
|
|
onHealth := func(st control.Status) {
|
|
if st.Reconnects > 0 || st.UnhealthyEvents > 0 {
|
|
if reconnects.Swap(st.Reconnects) == 0 {
|
|
select {
|
|
case detected <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
serverErr := make(chan error, 1)
|
|
go func() {
|
|
serverErr <- server.Run(ctx, server.Config{
|
|
Transport: transportName,
|
|
Carrier: carrierName,
|
|
RoomURL: testRoom,
|
|
KeyHex: testKeyHex,
|
|
DNSServer: localDNSServer,
|
|
})
|
|
}()
|
|
room.waitConnected(t, 1)
|
|
|
|
ready := make(chan struct{})
|
|
clientErr := make(chan error, 1)
|
|
go func() {
|
|
clientErr <- client.RunWithReady(ctx, client.Config{
|
|
Transport: transportName,
|
|
Carrier: carrierName,
|
|
RoomURL: testRoom,
|
|
KeyHex: testKeyHex,
|
|
DeviceID: testClientDeviceID,
|
|
LocalAddr: socksAddr,
|
|
DNSServer: localDNSServer,
|
|
OnHealth: onHealth,
|
|
}, func() { close(ready) })
|
|
}()
|
|
waitForReadyWithin(t, ready, setupBudget)
|
|
|
|
// Let the control stream settle into a healthy steady state first.
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Silently kill the carrier: link stays up, frames are dropped. Only the
|
|
// control-stream liveness timeout can detect this.
|
|
t.Logf("[deadlink] enabling blackhole at %s", time.Now().Format("15:04:05"))
|
|
start := time.Now()
|
|
room.enableBlackhole()
|
|
|
|
select {
|
|
case <-detected:
|
|
elapsed := time.Since(start)
|
|
t.Logf("[deadlink] dead link detected + reconnect in %s", elapsed.Round(time.Millisecond))
|
|
if elapsed > detectionBudget {
|
|
t.Fatalf("dead-link detection took %s, want <= %s (regression: liveness window too wide)",
|
|
elapsed.Round(time.Second), detectionBudget)
|
|
}
|
|
case <-time.After(detectionBudget):
|
|
t.Fatalf("dead link NOT detected within %s (regression: client treats dead carrier as alive)",
|
|
detectionBudget)
|
|
}
|
|
}
|