From af2959d2f8d617bdbfdf092c4f35ffd94c16bd38 Mon Sep 17 00:00:00 2001 From: Raja Subramanian Date: Mon, 5 Oct 2026 15:11:42 +0530 Subject: [PATCH] fix: use the fast debounce for the first negotiation (#4936) * fix: use the fast debounce for the first negotiation A new transport set lastNegotiate to its creation time. A non-forced Negotiate soon after creation then waited for the 150 ms debounce instead of 10 ms, although nothing was negotiated yet. Start from the zero time so that the first offer uses the fast debounce. Co-Authored-By: Claude Opus 5.5 * test: check the first negotiation condition without a deadline Co-Authored-By: Claude Opus 5.5 * test: check that the first offer goes out before a debounced one Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- pkg/rtc/transport.go | 1 - pkg/rtc/transport_test.go | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/pkg/rtc/transport.go b/pkg/rtc/transport.go index 4baf2e6e0..dd27aa4b9 100644 --- a/pkg/rtc/transport.go +++ b/pkg/rtc/transport.go @@ -566,7 +566,6 @@ func NewPCTransport(params TransportParams) (*PCTransport, error) { previousTrackDescription: make(map[string]*trackDescription), canReuseTransceiver: true, connectionDetails: types.NewICEConnectionDetails(params.Transport, params.Logger), - lastNegotiate: time.Now(), } t.localOfferId.Store(uint32(rand.Intn(1<<8) + 1)) diff --git a/pkg/rtc/transport_test.go b/pkg/rtc/transport_test.go index b586d16e6..3eeef2d5c 100644 --- a/pkg/rtc/transport_test.go +++ b/pkg/rtc/transport_test.go @@ -207,6 +207,50 @@ func TestNegotiationTiming(t *testing.T) { transportB.Close() } +func TestFirstNegotiationUsesFastDebounce(t *testing.T) { + offered := make(chan string, 2) + newOfferer := func(name string) *PCTransport { + handler := &transportfakes.FakeHandler{} + handler.OnOfferCalls(func(webrtc.SessionDescription, uint32, map[string]string) error { + select { + case offered <- name: + default: + } + return nil + }) + transport, err := NewPCTransport(TransportParams{ + Config: &WebRTCConfig{}, + IsOfferer: true, + Handler: handler, + }) + require.NoError(t, err) + t.Cleanup(transport.Close) + _, err = transport.pc.CreateDataChannel(ReliableDataChannel, nil) + require.NoError(t, err) + return transport + } + + // a transport that negotiated just now waits for the full debounce + recent := newOfferer("recent") + recent.lock.Lock() + recent.updateLastNegotiateLocked() + recent.lock.Unlock() + recent.Negotiate(false) + + // a new transport starts later, but its first offer uses the fast debounce and goes out first + time.Sleep(20 * time.Millisecond) + newOfferer("fresh").Negotiate(false) + + for _, want := range []string{"fresh", "recent"} { + select { + case got := <-offered: + require.Equal(t, want, got) + case <-time.After(5 * time.Second): + t.Fatalf("no offer from %s", want) + } + } +} + func TestFirstOfferMissedDuringICERestart(t *testing.T) { params := TransportParams{ Config: &WebRTCConfig{},