Skip to content

Ignore a duplicated server answer - #1047

Open
xianshijing-lk wants to merge 1 commit into
mainfrom
port/js-2114-duplicate-answer
Open

xianshijing-lk wants to merge 1 commit into
mainfrom
port/js-2114-duplicate-answer

Conversation

@xianshijing-lk

Copy link
Copy Markdown
Contributor

Port of livekit/client-sdk-js#2114.

The bug

PeerConnectionTransport.setRemoteDescription already rejected answers carrying an older offer id:

if (sd.type == ANSWER && currentOfferId > 0 && offerId > 0 && currentOfferId > offerId) {
    return Either.Right("Old offer, ignoring. ...")
}

But a second copy of the answer for the current offer satisfies currentOfferId == offerId, so it fell straight through to peerConnection.setRemoteDescription. A proxy duplicating the signal message, or a server retry, therefore applied the same answer twice. The second apply happens with no local offer left to answer — an invalid transition — so a renegotiation that had actually succeeded came back reporting an error.

Repro

Added duplicateAnswerForTheSameOfferIsIgnored to RTCEngineMockE2ETest: complete one publisher renegotiation, then deliver a second answer carrying the same offer id. The payload differs only so the test can tell whether it reached the peer connection.

Against the unfixed code:

RTCEngineMockE2ETest > duplicateAnswerForTheSameOfferIsIgnored FAILED
  org.junit.ComparisonFailure: duplicate answer was applied to the peer connection
    expected:<remote_answer[]> but was:<remote_answer[_duplicate]>

The fix

Track two things, mirroring upstream:

  • answeredOfferId — the offer whose answer was applied. Set only after a successful apply, so a retry after a genuine failure still goes through.
  • answerInFlight — the offer whose answer is being applied right now. Claimed before the apply suspends, so a copy arriving mid-apply is caught too, and released in a finally however the apply ends.

The decision itself is a pure isDuplicateAnswer function, matching the JS SDK's exported helper, with the legacy unnumbered-answer path falling back to the signaling state (in STABLE there is no local offer pending).

Verification

:livekit-android-test:testReleaseUnitTest green. The new behavioural test fails before the change and passes after; IsDuplicateAnswerTest covers the eight edge cases (in-flight, already-applied, retry-after-failure, new offer, and the two unnumbered-answer paths). The pre-existing offerIdMismatchIsIgnored and offerIdMismatchButZeroIsAccepted tests still pass, so the stale-offer and legacy behaviour are unchanged.

Note

This SDK has no deferred-initial-offer (offer-with-join), so the upstream helper's hasPendingInitialOffer parameter has no analogue here and is omitted — the local offer is always set immediately after it is created.

🤖 Generated with Claude Code

setRemoteDescription rejected answers carrying an older offer id, but a
second copy of the answer for the current offer passed straight through.
A proxy duplicating the signal message, or a server retry, applied the
same answer twice - the second time with no local offer left to answer,
an invalid transition that reported an error for a renegotiation that
had actually succeeded.

Track the offer whose answer was applied and the offer whose answer is
being applied, and drop a repeat of either. The in-flight claim is taken
before the apply suspends, so a copy arriving mid-apply is caught too,
and released however the apply ends so a genuine retry still works.

Port of livekit/client-sdk-js#2114.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f31dff8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
client-sdk-android Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@github-actions

Copy link
Copy Markdown
Contributor

Diffuse output:

OLD: diffuse-source-file
NEW: livekit-android-sdk-release.aar

 AAR      │ old      │ new      │ diff     
──────────┼──────────┼──────────┼──────────
      jar │    3 MiB │    3 MiB │ +1.3 KiB 
 manifest │  1.5 KiB │  1.5 KiB │      0 B 
 lint-jar │ 12.7 KiB │ 12.7 KiB │      0 B 
    other │  2.4 KiB │  2.4 KiB │      0 B 
──────────┼──────────┼──────────┼──────────
    total │    3 MiB │    3 MiB │ +1.3 KiB 

 JAR     │ old   │ new   │ diff       
─────────┼───────┼───────┼────────────
 classes │  1707 │  1707 │  0 (+0 -0) 
 methods │ 21992 │ 21997 │ +5 (+5 -0) 
  fields │  5708 │  5711 │ +3 (+3 -0)
AAR
 size  │ diff     │ path          
───────┼──────────┼───────────────
 3 MiB │ +1.3 KiB │ ∆ classes.jar 
───────┼──────────┼───────────────
 3 MiB │ +1.3 KiB │ (total)
JAR
METHODS:

   old   │ new   │ diff       
  ───────┼───────┼────────────
   21992 │ 21997 │ +5 (+5 -0) 
  
  + io.livekit.android.room.PeerConnectionTransport access_getAnswerInFlight_p(PeerConnectionTransport) → Integer
  + io.livekit.android.room.PeerConnectionTransport access_getAnsweredOfferId_p(PeerConnectionTransport) → AtomicInteger
  + io.livekit.android.room.PeerConnectionTransport access_setAnswerInFlight_p(PeerConnectionTransport, Integer)
  + io.livekit.android.room.PeerConnectionTransportKt isDuplicateAnswer(int, Integer, int, PeerConnection_SignalingState) → boolean
  + java.util.concurrent.atomic.AtomicInteger set(int)
  

FIELDS:

   old  │ new  │ diff       
  ──────┼──────┼────────────
   5708 │ 5711 │ +3 (+3 -0) 
  
  + io.livekit.android.room.PeerConnectionTransport answerInFlight: Integer
  + io.livekit.android.room.PeerConnectionTransport answeredOfferId: AtomicInteger
  + livekit.org.webrtc.PeerConnection_SignalingState STABLE: PeerConnection_SignalingState

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.

1 participant