Skip to content

feat: Support in-place ICE restart - #1299

Merged
bgrozev merged 4 commits into
masterfrom
in-place-ice-restart
Aug 13, 2026
Merged

feat: Support in-place ICE restart#1299
bgrozev merged 4 commits into
masterfrom
in-place-ice-restart

Conversation

@bgrozev

@bgrozev bgrozev commented Aug 6, 2026

Copy link
Copy Markdown
Member

Implements the jicofo side of an in-place ICE restart, in which the bridge creates a new ICE agent with fresh credentials while the existing one keeps carrying media until the new one connects (make-before-break). Jicofo receives the client's request, asks the bridge, and relays the bridge's new transport back to the client.

Intake

The client asks for a restart with a Jingle session-info carrying <bridge-session ice-restart="true"/>. session-info is the action for in-session informational messages and implies no state change, which is right here: the session is explicitly not being torn down. The existing modes on the neighbouring paths are unchanged and still trigger a full re-invite — <ice-state>failed</ice-state> on session-info, and restart="true" on session-terminate, which remains the client's fallback when an in-place restart does not work out.

The request is validated against the bridge-session ID and rate limited. The rate limiter is a separate instance reusing the existing jicofo.conference.restart-request-rate-limits.* keys, so ICE restarts and full-session restarts do not share a budget.

Relay

Colibri2Session.restartIce() sends a conference-modify with <transport ice-restart="true"/>. The bridge answers with its new transport, which ColibriV2SessionManager.endpointIceRestarted() picks up and forwards to the participant as a plain Jingle transport-info (not transport-replace, so the client stays the answerer).

Colibri2 responses are handled on TaskPools.ioPool and can reorder, so a latest-wins guard on the transport's ice-generation (tracked per participant) drops anything not newer than what was already relayed.

The reverse leg needs no new code: the client's own new credentials come back through the existing onTransportInfo to updateTransport to updateParticipant path, and the ice-generation attribute survives it unchanged.

Config and metrics

jicofo.conference.enable-ice-restart (default true). When disabled, the request is answered with feature-not-implemented and the client falls back on its own.

IceRestartMetrics counts requests received, transports relayed, and failures. Failures currently share one counter across several causes (disabled, rate limited, stale bridge-session ID, unknown endpoint, no transport in the response, stale generation, no Jingle session); the log lines distinguish them.

Bridge.endpointRequestedRestart() is deliberately not called here, unlike the iceFailed and terminateSession paths. An in-place restart is normally a client network change rather than evidence the bridge is failing ICE, so counting it would feed the bridge-selection penalty unfairly.

Part of a multi-repo change; all PRs share the in-place-ice-restart branch name so CI tests them together:

Adds the machinery to ask a bridge to restart ICE in place and to handle
its answer. Colibri2Session.restartIce sends a conference-modify with
<transport ice-restart="true"/> for the endpoint; the bridge creates a new
ICE agent with fresh credentials while the existing one keeps carrying
media (make-before-break) and answers with the new transport.

ColibriV2SessionManager.endpointIceRestarted applies a latest-wins guard
keyed on the ice-generation attribute before firing a new
ColibriSessionManager.Listener event: colibri2 responses are handled on an
IO pool, so if a participant restarts twice in quick succession the two
responses can arrive in either order. JingleSession.sendTransportInfo
signals the transport on to the client as a plain Jingle transport-info.

Also adds the ice_restarts_requested/relayed/failed metrics, named to
match jitsi-videobridge's metrics for the other half of the flow.
A participant asks for an in-place ICE restart with a Jingle session-info
carrying <bridge-session ice-restart="true"/>. Unlike the existing
session-terminate + restart="true" flow, which stays as the fallback, this
does not tear the session down: jicofo validates the bridge-session ID,
rate-limits the request, asks the bridge to rotate its ICE credentials in
place, and relays the resulting transport back to the participant in a
transport-info. The participant then signals its own new credentials in a
transport-info tagged with the same ice-generation, which the existing
onTransportInfo path already forwards to the bridge unmodified.

ICE restarts get their own rate-limit budget (with the same configured
limits) so that a burst of them can not exhaust the budget for full
session restarts, and vice versa. Requests are gated by
jicofo.conference.enable-ice-restart, default true; when disabled they are
rejected with feature-not-implemented so the client can fall back.
The bridge declines an ICE restart by answering with no transport rather
than with an error, so that one endpoint cannot fail a conference-modify
carrying updates for others. Nothing was propagated back to the client,
which meant the participant waited for a restart that would never arrive
and only recovered via its own 15s timeout - slower than if the in-place
restart had never been attempted.

The bridge declines for reasons that are not exotic: ICE restarts disabled
in its configuration, a transport that is not established yet, or a
transport that is shutting down. The first of those covers the whole of a
rollout, when clients have the feature enabled and only some bridges do.

Escalate instead: on a declined restart, re-invite the participant, which
is the recovery it would have got had it never asked for an in-place
restart, and now happens immediately rather than after a client timeout.

As in iceRestart(), Bridge.endpointRequestedRestart() is intentionally not
called - a bridge declining a restart is not evidence that it is failing
ICE, and counting it would feed the bridge-selection penalty.
Picks up the ice-generation and ice-restart attributes used by the in-place
ICE restart.
@bgrozev
bgrozev force-pushed the in-place-ice-restart branch from 0946409 to 72d937f Compare August 10, 2026 20:03
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.67647% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.53%. Comparing base (b64b530) to head (72d937f).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...tsi/jicofo/conference/JitsiMeetConferenceImpl.java 62.50% 9 Missing and 3 partials ⚠️
...i/jicofo/bridge/colibri/ColibriV2SessionManager.kt 78.57% 5 Missing and 1 partial ⚠️
...org/jitsi/jicofo/bridge/colibri/Colibri2Session.kt 75.00% 2 Missing and 2 partials ⚠️
.../kotlin/org/jitsi/jicofo/conference/Participant.kt 90.00% 2 Missing and 1 partial ⚠️
...tsi/jicofo/bridge/colibri/ColibriSessionManager.kt 0.00% 2 Missing ⚠️
...tlin/org/jitsi/jicofo/metrics/IceRestartMetrics.kt 90.00% 1 Missing ⚠️
...tlin/org/jitsi/jicofo/xmpp/jingle/JingleSession.kt 93.33% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1299      +/-   ##
==========================================
+ Coverage   50.47%   51.53%   +1.05%     
==========================================
  Files         131      132       +1     
  Lines        8251     8419     +168     
  Branches     1261     1285      +24     
==========================================
+ Hits         4165     4339     +174     
+ Misses       3493     3471      -22     
- Partials      593      609      +16     
Files with missing lines Coverage Δ
...org/jitsi/jicofo/bridge/colibri/ParticipantInfo.kt 100.00% <100.00%> (ø)
...c/main/kotlin/org/jitsi/jicofo/ConferenceConfig.kt 98.14% <100.00%> (+0.07%) ⬆️
...tlin/org/jitsi/jicofo/metrics/IceRestartMetrics.kt 90.00% <90.00%> (ø)
...tlin/org/jitsi/jicofo/xmpp/jingle/JingleSession.kt 57.41% <93.33%> (+3.29%) ⬆️
...tsi/jicofo/bridge/colibri/ColibriSessionManager.kt 64.70% <0.00%> (-4.05%) ⬇️
.../kotlin/org/jitsi/jicofo/conference/Participant.kt 57.75% <90.00%> (+6.77%) ⬆️
...org/jitsi/jicofo/bridge/colibri/Colibri2Session.kt 69.57% <75.00%> (+4.56%) ⬆️
...i/jicofo/bridge/colibri/ColibriV2SessionManager.kt 59.07% <78.57%> (+1.96%) ⬆️
...tsi/jicofo/conference/JitsiMeetConferenceImpl.java 38.15% <62.50%> (+1.03%) ⬆️

... and 4 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 076a4c7...72d937f. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

jitsi-ci Bot pushed a commit to jitsi/jitsi-pr-tests-pages that referenced this pull request Aug 10, 2026
@bgrozev
bgrozev merged commit d4d1458 into master Aug 13, 2026
6 checks passed
@bgrozev
bgrozev deleted the in-place-ice-restart branch August 13, 2026 21:06
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.

2 participants