Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/main/java/org/ice4j/ice/Agent.java
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,75 @@ public void startConnectivityEstablishment()
}
}

/**
* Performs an in-place ICE restart on this already-established agent: re-runs
* connectivity checks against (possibly new) remote credentials/candidates
* while keeping the same local credentials and, crucially, keeping the
* currently selected pair in use for sending media until a new pair is
* nominated (make-before-break).
* <p>
* This is the peer-driven counterpart to creating a brand new agent for an
* ICE restart. The caller is expected to have already applied the new remote
* ufrag/password (via {@link IceMediaStream#setRemoteUfrag(String)} /
* {@link IceMediaStream#setRemotePassword(String)}) and any signalled remote
* candidates before calling this method; new peer-reflexive remote addresses
* are also discovered from the incoming checks as usual.
* <p>
* Concretely this: cancels any pending (or already fired) termination so the
* agent is not torn down; re-arms the connectivity check client (whose
* {@code stop()} on termination had disabled it); resets each stream (clears
* the valid list so the stale nominee no longer blocks a fresh nomination,
* moves the check list back to RUNNING, and flags each component so the first
* pair nominated during the restart replaces the selected pair); rebuilds the
* check lists; moves the agent back to {@link IceProcessingState#RUNNING};
* and starts the checks.
*/
public void restartIce()
{
synchronized (startLock)
{
logger.info("Restarting ICE (in-place) on the existing agent.");

// Cancel any pending/completed termination so the agent (and its
// check client, which terminate() stops) is not torn down.
synchronized (terminationFutureSyncRoot)
{
if (terminationFuture != null)
{
terminationFuture.cancel(true);
terminationFuture = null;
}
}

shutdown = false;

// Re-enable the check client (terminate() had stopped it).
connCheckClient.restart();

// Reset each stream for the restart (keeps the selected pair).
for (IceMediaStream stream : getStreams())
{
stream.restart();
}

try
{
initCheckLists();
}
catch (ArithmeticException e)
{
setState(IceProcessingState.FAILED);
return;
}

//change state before we actually send checks so that we don't
//miss responses and hence the possibility to nominate a pair.
setState(IceProcessingState.RUNNING);

connCheckClient.startChecks();
}
}

/**
* <tt>Free()</tt>s and removes from this agent components or entire streams
* if they do not contain remote candidates. A possible reason for this
Expand Down
41 changes: 36 additions & 5 deletions src/main/java/org/ice4j/ice/CheckList.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,26 @@ protected void setState(CheckListState newState)
fireStateChange(oldState, newState);
}

/**
* Resets this check list so that connectivity checks can be run again on it
* as part of an in-place ICE restart (see {@link Agent#restartIce()}). Moves
* the state back to {@link CheckListState#RUNNING}, clears the
* {@code paceMakerStarted} latch so a new {@code PaceMaker} can be scheduled
* and empties the triggered-check queue. The pairs themselves are rebuilt by
* {@link IceMediaStream#initCheckList()}; this method does not touch them.
*/
protected void restart()
{
paceMakerStarted.set(false);

synchronized (triggeredCheckQueue)
{
triggeredCheckQueue.clear();
}

setState(CheckListState.RUNNING);
}

/**
* Adds <tt>pair</tt> to the local triggered check queue unless it's already
* there. Additionally, the method sets the pair's state to {@link
Expand Down Expand Up @@ -406,13 +426,24 @@ protected synchronized void handleNominationConfirmed(

if (cmp.getSelectedPair() != null)
{
return;
// Normally nomination is set-once. During an in-place ICE restart we
// deliberately keep the old selected pair in use for sending until a
// new pair is nominated (make-before-break), and then swap to it here.
if (!cmp.isIceRestarting())
{
return;
}
logger.info("Swapping selected pair for stream " + cmp.toShortString()
+ " after ICE restart: " + nominatedPair.toRedactedShortString());
cmp.setIceRestarting(false);
}
else
{
logger.info(
"Selected pair for stream " + cmp.toShortString() + ": "
+ nominatedPair.toRedactedShortString());
}

logger.info(
"Selected pair for stream " + cmp.toShortString() + ": "
+ nominatedPair.toRedactedShortString());

cmp.setSelectedPair(nominatedPair);

Iterator<CandidatePair> pairsIter = iterator();
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/org/ice4j/ice/Component.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,17 @@ public class Component
*/
private CandidatePair selectedPair;

/**
* Whether an in-place ICE restart (see {@link Agent#restartIce()}) is
* currently in progress for this component. While {@code true} the existing
* {@link #selectedPair} is kept in use for sending media (make-before-break),
* and the normal set-once nomination guard in
* {@link CheckList#handleNominationConfirmed(CandidatePair)} is relaxed so
* that the first pair nominated during the restart replaces the selected
* pair. Reset to {@code false} as soon as that swap happens.
*/
private volatile boolean iceRestarting = false;

/**
* The default <tt>RemoteCandidate</tt> for this component or in other
* words, the candidate that we would have used to communicate with the
Expand Down Expand Up @@ -1055,6 +1066,26 @@ public CandidatePair getSelectedPair()
return selectedPair;
}

/**
* @return whether an in-place ICE restart is currently in progress for this
* component. See {@link #iceRestarting}.
*/
protected boolean isIceRestarting()
{
return iceRestarting;
}

/**
* Sets whether an in-place ICE restart is in progress for this component.
* See {@link #iceRestarting}.
*
* @param iceRestarting the new value.
*/
protected void setIceRestarting(boolean iceRestarting)
{
this.iceRestarting = iceRestarting;
}

/**
* Returns a human readable name that can be used in debug logs associated
* with this component.
Expand Down
20 changes: 19 additions & 1 deletion src/main/java/org/ice4j/ice/ConnectivityCheckClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,12 @@ private void processSuccessResponse(StunResponseEvent ev)
if (parentAgent.isControlling()
&& request.containsAttribute(Attribute.USE_CANDIDATE))
{
if (validPair.getParentComponent().getSelectedPair() == null)
// Normally nomination is confirmed only once, while there is no selected pair yet. During an
// in-place ICE restart we keep the old selected pair in use (make-before-break), so we must also
// confirm the nomination of the new pair while a selected pair still exists;
// handleNominationConfirmed() then swaps the selected pair over.
if (validPair.getParentComponent().getSelectedPair() == null
|| validPair.getParentComponent().isIceRestarting())
{
logger.info("Nomination confirmed for pair: "
+ validPair.toRedactedShortString()
Expand Down Expand Up @@ -1042,4 +1047,17 @@ public boolean isStopped() {
return stopped;
}
}

/**
* Clears the {@code stopped} flag so that checks can be started again after a
* previous {@link #stop()}, as part of an in-place ICE restart (see
* {@link Agent#restartIce()}). {@link #stop()} left {@code stopped == true}
* and removed all {@link PaceMaker}s; a subsequent {@link #startChecks()}
* recreates them. Must be called before {@code startChecks()}.
*/
void restart() {
synchronized (paceMakers) {
stopped = false;
}
}
}
30 changes: 30 additions & 0 deletions src/main/java/org/ice4j/ice/IceMediaStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,36 @@ protected void initCheckList()
}
}

/**
* Prepares this stream for an in-place ICE restart (see
* {@link Agent#restartIce()}). Clears the valid list (so the stale nominee
* from the previous run no longer blocks a fresh nomination) and resets the
* check list state back to {@link CheckListState#RUNNING}, and marks each
* component as ICE-restarting so that the first pair nominated during the
* restart replaces the currently selected pair. The currently selected pair
* is intentionally left in place so media keeps flowing on it until the new
* pair is nominated (make-before-break). The caller is expected to have set
* the new remote credentials/candidates, and to rebuild the check list pairs
* via {@link #initCheckList()} afterwards.
*/
protected void restart()
{
synchronized (validList)
{
validList.clear();
}

for (Component component : getComponents())
{
if (component.getSelectedPair() != null)
{
component.setIceRestarting(true);
}
}

checkList.restart();
}

/**
* Creates and adds to <tt>checkList</tt> all the <tt>CandidatePair</tt>s
* in all <tt>Component</tt>s of this stream.
Expand Down
Loading
Loading