- From: Jay Nirmal via GitHub <noreply@w3.org>
- Date: Wed, 12 Aug 2026 19:11:47 +0000
- To: public-webrtc@w3.org
jaynirmal14 has just created a new issue for https://github.com/w3c/webrtc-pc:
== Is recovery of an existing PeerConnection after connectionState "failed" implementation-defined? ==
While building a test harness for WebRTC recovery policy, I observed a consistent difference between Chromium and Gecko in whether an ICE restart issued while `connectionState` is `"failed"` can restore the existing `RTCPeerConnection`. I could not determine from the specification whether both behaviours are conformant, and I would like to understand whether the difference is intended.
#### Question
After `connectionState` has reached `"failed"`, does the specification require, permit, or preclude an `RTCIceTransport` returning to a connected state as the result of an ICE restart on the same `RTCPeerConnection`?
Put differently: is `"failed"` intended as terminal for the transport objects, requiring a new `RTCPeerConnection`, or as a state from which an ICE restart may recover the existing one? Implementations appear to differ, and applications must choose a recovery strategy without knowing which to expect.
#### Method
Two-peer, data-channel-only sessions. Relay-only ICE (`iceTransportPolicy: "relay"`) through a local coturn server. The relay process
is paused with `SIGSTOP` until `connectionState` reaches `"failed"`, then resumed after a controlled delay of 1, 3, or 5 seconds.
The restart is asymmetric, which matters for interpreting the results: the **caller** issues `createOffer({ iceRestart: true })` followed by
`setLocalDescription()`; the **callee** calls `pc.restartIce()`. The offer carrying new ICE credentials therefore always originates from the caller. I have not yet measured the variant where both roles use `restartIce()`.
Two policies are compared:
- **Keep waiting** — the in-flight wait continues after `"failed"`, up to its own timeout.
- **Escalate immediately** — `"failed"` ends the wait and a new `RTCPeerConnection` is built.
Recovery is attributed to the original peer connection only when the connection instance that reaches `"connected"` is the same instance the restart was issued on.
5 trials per browser, per policy, per restoration delay; 60 trials total.
#### Results
| Browser | Policy | Trials | Original PC recovered | New PC required |
|---|---|---|---|---|
| Chrome 151.0.7922.34 | Keep waiting | 15 | 15/15 | 0/15 |
| Chrome 151.0.7922.34 | Escalate immediately | 15 | 2/15 \* | 15/15 |
| Firefox 153.0 | Keep waiting | 15 | 0/15 | 15/15 |
| Firefox 153.0 | Escalate immediately | 15 | 0/15 | 15/15 |
\* In two Chrome trials the original peer connection became viable again after the controller had already committed to a replacement.
In Chrome the original `RTCPeerConnection` returned to `"connected"` approximately 60 ms after relay restoration, in every trial, at every
restoration delay. In Firefox the original connection did not recover in any trial; a separate preflight measurement showed the wait reaching its timeout at 25.8 s.
#### What I could not resolve from the specification
`RTCPeerConnectionState` `"failed"` is defined in terms of the aggregate state of the `RTCIceTransport`s and `RTCDtlsTransport`s. What I could not establish is whether an `RTCIceTransport` in `"failed"` is required, permitted, or forbidden to re-enter `"checking"` as a result of an ICE restart on the same `RTCPeerConnection`, or whether this is deliberately implementation-defined.
If both behaviours are conformant, saying so explicitly would help application authors, since recovery policy depends on it: waiting through `"failed"` preserves the connection in one engine and only adds latency in the other.
#### Environment
- macOS (ARM64), Node.js 20.16.0, Playwright 1.62.1
- Chrome 151.0.7922.34, Firefox 153.0
- Local coturn relay; relay-only ICE
- Harness and raw data: `<LINK>`
#### Scope and limitations
- Interruption is a paused TURN relay, not a network interface change. Timings and possibly the divergence itself may not transfer to real transitions such as Wi-Fi to cellular handoff.
- The caller uses `createOffer({ iceRestart: true })`, not `restartIce()`. The two have different negotiation semantics, so part of the divergence may be specific to the offer-based path. I am happy to run the `restartIce()`-only variant if that would be informative.
- Loopback and LAN addressing; no representative RTT or packet loss.
- Data channels only; no media, no SFU.
- Chromium and Gecko only; WebKit not tested.
- 5 trials per condition. The outcome split is categorical (15/15 vs 0/15) rather than marginal, but the timing figures should not be read as
distributions.
Please view or discuss this issue at https://github.com/w3c/webrtc-pc/issues/3133 using your GitHub account
--
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Wednesday, 12 August 2026 19:11:47 UTC