Skip to content

Commit 1b871c2

Browse files
test: buffer trickled candidates until both descriptions are applied
Candidate gathering starts inside createOffer and createAnswer via setLocalDescription, so the test forwarded candidates to a peer that often had no remote description yet. The native stack rejects such candidates and nothing retries them, so when both directions lost all candidates during the exchange the connection never formed and the test burned its whole timeout. This was the common cause of the intermittent iceCandidatesRespectPortAllocatorConfig failures on CI. The peers now buffer outgoing candidates and deliver them after both descriptions are set, forwarding directly from then on. The candidate list also became a CopyOnWriteArrayList since it is written on the signaling thread and read on the test thread.
1 parent 81b725c commit 1b871c2

1 file changed

Lines changed: 36 additions & 3 deletions

File tree

webrtc/src/test/java/dev/onvoid/webrtc/PortAllocatorConfigIntegrationTest.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import java.util.ArrayList;
2222
import java.util.List;
23+
import java.util.concurrent.CopyOnWriteArrayList;
2324
import java.util.concurrent.CountDownLatch;
2425
import java.util.concurrent.TimeUnit;
2526

@@ -60,6 +61,11 @@ void iceCandidatesRespectPortAllocatorConfig() throws Exception {
6061
callee.setRemoteDescription(caller.createOffer());
6162
caller.setRemoteDescription(callee.createAnswer());
6263

64+
// Both descriptions are applied now; deliver the candidates gathered
65+
// during the exchange and forward directly from here on.
66+
caller.flushCandidates();
67+
callee.flushCandidates();
68+
6369
// Wait until connected (with a timeout to avoid hanging tests).
6470
assertTrue(caller.awaitConnected(30, TimeUnit.SECONDS), "Caller failed to connect in time");
6571
assertTrue(callee.awaitConnected(30, TimeUnit.SECONDS), "Callee failed to connect in time");
@@ -118,7 +124,9 @@ private static class AllocPeer implements PeerConnectionObserver {
118124
private final RTCPeerConnection pc;
119125
private RTCPeerConnection remote;
120126
private final CountDownLatch connected = new CountDownLatch(1);
121-
final List<String> candidates = new ArrayList<>();
127+
private final List<RTCIceCandidate> pendingCandidates = new ArrayList<>();
128+
private boolean forwardDirectly;
129+
final List<String> candidates = new CopyOnWriteArrayList<>();
122130

123131

124132
AllocPeer(PeerConnectionFactory factory, RTCConfiguration cfg) {
@@ -165,12 +173,37 @@ void close() {
165173
pc.close();
166174
}
167175

176+
/**
177+
* Delivers the candidates buffered during the offer/answer exchange
178+
* and switches to direct forwarding. Trickling starts inside
179+
* createOffer/createAnswer via setLocalDescription, so early
180+
* candidates would reach the remote peer before it has a remote
181+
* description; the native stack rejects such candidates and they
182+
* would be lost for good, leaving ICE without a candidate pair.
183+
*/
184+
void flushCandidates() {
185+
List<RTCIceCandidate> buffered;
186+
synchronized (this) {
187+
forwardDirectly = true;
188+
buffered = new ArrayList<>(pendingCandidates);
189+
pendingCandidates.clear();
190+
}
191+
for (RTCIceCandidate candidate : buffered) {
192+
remote.addIceCandidate(candidate);
193+
}
194+
}
195+
168196
@Override
169197
public void onIceCandidate(RTCIceCandidate candidate) {
170198
candidates.add(candidate.sdp);
171-
if (remote != null) {
172-
remote.addIceCandidate(candidate);
199+
200+
synchronized (this) {
201+
if (!forwardDirectly) {
202+
pendingCandidates.add(candidate);
203+
return;
204+
}
173205
}
206+
remote.addIceCandidate(candidate);
174207
}
175208

176209
@Override

0 commit comments

Comments
 (0)