@@ -97,6 +97,7 @@ public void testReconnectCatchUpRebuildsDictionary() throws Exception {
9797 Assert .assertEquals ("2nd connection dictionary size" , 2 , conn2 .size ());
9898 Assert .assertEquals ("alpha" , conn2 .get (0 ));
9999 Assert .assertEquals ("beta" , conn2 .get (1 ));
100+ assertAckSequencesStartAtZero (handler .ackSequenceStarts ());
100101 }
101102 });
102103 }
@@ -143,6 +144,7 @@ public void testReconnectPreservesMonotonicDeltaBaseline() throws Exception {
143144 + "baseline (deltaStart >= 1); a reset baseline re-ships the whole "
144145 + "dictionary from deltaStart 0" ,
145146 handler .conn2SawDeltaAboveBaseline );
147+ assertAckSequencesStartAtZero (handler .ackSequenceStarts ());
146148 }
147149 });
148150 }
@@ -204,6 +206,7 @@ public void testFixedCapNearBoundarySymbolCatchesUpWithoutTerminal() throws Exce
204206 Assert .assertEquals ("2nd connection dictionary size" , 2 , conn2 .size ());
205207 Assert .assertEquals (nearCapSymbol , conn2 .get (0 ));
206208 Assert .assertEquals ("beta" , conn2 .get (1 ));
209+ assertAckSequencesStartAtZero (handler .ackSequenceStarts ());
207210 }
208211 });
209212 }
@@ -251,6 +254,7 @@ public void testForegroundCatchUpCapGapRetriesPastOrphanBudgetAndRecovers() thro
251254 Assert .assertTrue ("foreground sender must recover and drain after the cap is restored" ,
252255 sender .awaitAckedFsn (targetFsn , 5_000 ));
253256 }
257+ assertAckSequencesStartAtZero (handler .ackSequenceStarts ());
254258 }
255259 });
256260 }
@@ -307,10 +311,19 @@ public void testReconnectCatchUpSplitsLargeDictionaryAcrossFrames() throws Excep
307311 Assert .assertTrue ("catch-up split into multiple frames (saw "
308312 + handler .zeroTableFramesOnConn2 + ")" ,
309313 handler .zeroTableFramesOnConn2 >= 2 );
314+ assertAckSequencesStartAtZero (handler .ackSequenceStarts ());
310315 }
311316 });
312317 }
313318
319+ private static void assertAckSequencesStartAtZero (List <Long > ackSequenceStarts ) {
320+ Assert .assertFalse ("the fixture must ACK at least one connection" , ackSequenceStarts .isEmpty ());
321+ for (int i = 0 ; i < ackSequenceStarts .size (); i ++) {
322+ Assert .assertEquals ("connection " + (i + 1 ) + " must begin ACK sequencing at zero" ,
323+ 0L , ackSequenceStarts .get (i ).longValue ());
324+ }
325+ }
326+
314327 private static String symbolName (int i ) {
315328 // 10-char symbols so 40 of them clearly exceed the advertised 160-byte cap.
316329 return "symbol" + (1000 + i );
@@ -371,10 +384,15 @@ private static class CatchUpHandler implements TestWebSocketServer.WebSocketServ
371384 // post-reconnect frame trips it.
372385 volatile boolean conn2SawDeltaAboveBaseline ;
373386 volatile boolean sawZeroTableFrameOnConn2 ;
387+ private final List <Long > ackSequenceStarts = new CopyOnWriteArrayList <>();
374388 private final List <List <String >> dictsByConn = new CopyOnWriteArrayList <>();
375389 private TestWebSocketServer .ClientHandler currentClient ;
376390 private final AtomicLong nextSeq = new AtomicLong (0 );
377391
392+ List <Long > ackSequenceStarts () {
393+ return new ArrayList <>(ackSequenceStarts );
394+ }
395+
378396 synchronized List <String > dictFor (int connNumber ) {
379397 return connNumber <= dictsByConn .size ()
380398 // Copy under the lock: the caller iterates it unlocked while the
@@ -390,6 +408,7 @@ public synchronized void onBinaryMessage(TestWebSocketServer.ClientHandler clien
390408 currentClient = client ;
391409 connectionsAccepted .incrementAndGet ();
392410 dictsByConn .add (new ArrayList <>()); // fresh dictionary per connection
411+ nextSeq .set (0 );
393412 }
394413 int connNumber = dictsByConn .size ();
395414 List <String > dict = dictsByConn .get (connNumber - 1 );
@@ -414,7 +433,11 @@ public synchronized void onBinaryMessage(TestWebSocketServer.ClientHandler clien
414433 }
415434 }
416435 try {
417- client .sendBinary (buildAck (nextSeq .getAndIncrement ()));
436+ long ackSequence = nextSeq .getAndIncrement ();
437+ if (newConnection ) {
438+ ackSequenceStarts .add (ackSequence );
439+ }
440+ client .sendBinary (buildAck (ackSequence ));
418441 // Drop the first connection right after ACKing its only frame,
419442 // forcing the sender to reconnect onto a fresh dictionary.
420443 if (connNumber == 1 ) {
@@ -474,23 +497,33 @@ private static int tableCount(byte[] frame) {
474497 */
475498 private static class CapShrinkHandler implements TestWebSocketServer .WebSocketServerHandler {
476499 final AtomicInteger connectionsAccepted = new AtomicInteger ();
500+ private final List <Long > ackSequenceStarts = new CopyOnWriteArrayList <>();
477501 private final AtomicLong nextSeq = new AtomicLong (0 );
478502 private TestWebSocketServer .ClientHandler currentClient ;
479503 private volatile TestWebSocketServer server ;
480504
505+ List <Long > ackSequenceStarts () {
506+ return new ArrayList <>(ackSequenceStarts );
507+ }
508+
481509 void setServer (TestWebSocketServer server ) {
482510 this .server = server ;
483511 }
484512
485513 @ Override
486514 public synchronized void onBinaryMessage (TestWebSocketServer .ClientHandler client , byte [] data ) {
487- if (currentClient != client ) {
515+ boolean newConnection = currentClient != client ;
516+ if (newConnection ) {
488517 currentClient = client ;
489518 connectionsAccepted .incrementAndGet ();
490519 nextSeq .set (0 );
491520 }
492521 try {
493- client .sendBinary (CatchUpHandler .buildAck (nextSeq .getAndIncrement ()));
522+ long ackSequence = nextSeq .getAndIncrement ();
523+ if (newConnection ) {
524+ ackSequenceStarts .add (ackSequence );
525+ }
526+ client .sendBinary (CatchUpHandler .buildAck (ackSequence ));
494527 if (connectionsAccepted .get () == 1 ) {
495528 // Connection 1 registered the big symbol. Shrink the cap so the
496529 // reconnect's catch-up budget can't fit it, then drop to force
@@ -518,6 +551,7 @@ private static class SplitCatchUpHandler implements TestWebSocketServer.WebSocke
518551 // Set once the server has closed connection 1 (see CatchUpHandler.conn1Closed).
519552 volatile boolean conn1Closed ;
520553 volatile int zeroTableFramesOnConn2 ;
554+ private final List <Long > ackSequenceStarts = new CopyOnWriteArrayList <>();
521555 private final List <List <String >> dictsByConn = new CopyOnWriteArrayList <>();
522556 private final int dropConn1AtDictSize ;
523557 private final AtomicLong nextSeq = new AtomicLong (0 );
@@ -528,6 +562,10 @@ private static class SplitCatchUpHandler implements TestWebSocketServer.WebSocke
528562 this .dropConn1AtDictSize = dropConn1AtDictSize ;
529563 }
530564
565+ List <Long > ackSequenceStarts () {
566+ return new ArrayList <>(ackSequenceStarts );
567+ }
568+
531569 synchronized List <String > dictFor (int connNumber ) {
532570 return connNumber <= dictsByConn .size ()
533571 // Copy under the lock: the caller iterates it unlocked while the
@@ -543,6 +581,7 @@ public synchronized void onBinaryMessage(TestWebSocketServer.ClientHandler clien
543581 currentClient = client ;
544582 connectionsAccepted .incrementAndGet ();
545583 dictsByConn .add (new ArrayList <>()); // fresh dictionary per connection
584+ nextSeq .set (0 );
546585 }
547586 int connNumber = dictsByConn .size ();
548587 List <String > dict = dictsByConn .get (connNumber - 1 );
@@ -551,7 +590,11 @@ public synchronized void onBinaryMessage(TestWebSocketServer.ClientHandler clien
551590 zeroTableFramesOnConn2 ++;
552591 }
553592 try {
554- client .sendBinary (CatchUpHandler .buildAck (nextSeq .getAndIncrement ()));
593+ long ackSequence = nextSeq .getAndIncrement ();
594+ if (newConnection ) {
595+ ackSequenceStarts .add (ackSequence );
596+ }
597+ client .sendBinary (CatchUpHandler .buildAck (ackSequence ));
555598 // Drop connection 1 only once it has learned the entire batch, so
556599 // the sender's sent-dictionary mirror is complete and the reconnect
557600 // catch-up must replay a dictionary larger than the batch cap.
0 commit comments