Skip to content

Commit bc0d40b

Browse files
committed
ZOOKEEPER-5010: purge orphaned ephemerals after DIFF sync
1 parent 321f84d commit bc0d40b

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/Learner.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.nio.ByteBuffer;
3232
import java.util.ArrayDeque;
3333
import java.util.Deque;
34+
import java.util.HashSet;
3435
import java.util.Map;
3536
import java.util.Map.Entry;
3637
import java.util.Set;
@@ -54,7 +55,9 @@
5455
import org.apache.zookeeper.server.Request;
5556
import org.apache.zookeeper.server.ServerCnxn;
5657
import org.apache.zookeeper.server.ServerMetrics;
58+
import org.apache.zookeeper.server.SessionTracker;
5759
import org.apache.zookeeper.server.TxnLogEntry;
60+
import org.apache.zookeeper.server.ZKDatabase;
5861
import org.apache.zookeeper.server.ZooTrace;
5962
import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer;
6063
import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier;
@@ -579,6 +582,7 @@ protected void syncWithLeader(long newLeaderZxid) throws Exception {
579582
boolean snapshotNeeded = true;
580583
boolean syncSnapshot = false;
581584
readPacket(qp);
585+
boolean diffSync = qp.getType() == Leader.DIFF;
582586
Deque<Long> packetsCommitted = new ArrayDeque<>();
583587
Deque<PacketInFlight> packetsNotLogged = new ArrayDeque<>();
584588

@@ -633,6 +637,10 @@ protected void syncWithLeader(long newLeaderZxid) throws Exception {
633637
}
634638
zk.getZKDatabase().initConfigInZKDatabase(self.getQuorumVerifier());
635639
zk.createSessionTracker();
640+
// DIFF keeps the local tree; clear ephemerals without sessions before applying new transactions.
641+
if (diffSync) {
642+
purgeOrphanedEphemerals();
643+
}
636644

637645
// TODO: Ideally, this should be lastProcessZxid(a.k.a. QuorumPacket::zxid from above), but currently
638646
// LearnerHandler does not guarantee this. So, let's be conservative and keep it unchange for now.
@@ -869,6 +877,43 @@ protected void syncWithLeader(long newLeaderZxid) throws Exception {
869877
// New server type need to handle in-flight packets
870878
throw new UnsupportedOperationException("Unknown server type");
871879
}
880+
881+
}
882+
883+
void purgeOrphanedEphemerals() {
884+
if (zk == null) {
885+
return;
886+
}
887+
SessionTracker sessionTracker = zk.getSessionTracker();
888+
if (sessionTracker == null) {
889+
return;
890+
}
891+
ZKDatabase zkDatabase = zk.getZKDatabase();
892+
if (zkDatabase == null) {
893+
return;
894+
}
895+
896+
Set<Long> globalSessions = sessionTracker.globalSessions();
897+
Set<Long> localSessions = sessionTracker.localSessions();
898+
Set<Long> sessionsWithEphemerals = new HashSet<>(zkDatabase.getSessions());
899+
if (sessionsWithEphemerals.isEmpty()) {
900+
return;
901+
}
902+
903+
long zxid = zkDatabase.getDataTreeLastProcessedZxid();
904+
for (Long sessionId : sessionsWithEphemerals) {
905+
if (globalSessions.contains(sessionId)
906+
|| localSessions.contains(sessionId)
907+
|| (sessionTracker instanceof UpgradeableSessionTracker
908+
&& ((UpgradeableSessionTracker) sessionTracker).isUpgradingSession(sessionId))) {
909+
continue;
910+
}
911+
LOG.warn(
912+
"Removing ephemeral nodes for unknown session 0x{} after DIFF sync",
913+
Long.toHexString(sessionId));
914+
zkDatabase.killSession(sessionId, zxid);
915+
sessionTracker.removeSession(sessionId);
916+
}
872917
}
873918

874919
protected void revalidate(QuorumPacket qp) throws IOException {

zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/LearnerTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import static org.hamcrest.CoreMatchers.is;
2525
import static org.hamcrest.MatcherAssert.assertThat;
2626
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
import static org.junit.jupiter.api.Assertions.assertNotNull;
28+
import static org.junit.jupiter.api.Assertions.assertNull;
2729
import static org.junit.jupiter.api.Assertions.assertThrows;
2830
import static org.junit.jupiter.api.Assertions.assertTrue;
2931
import static org.junit.jupiter.api.Assertions.fail;
@@ -50,6 +52,7 @@
5052
import org.apache.zookeeper.server.ExitCode;
5153
import org.apache.zookeeper.server.ZKDatabase;
5254
import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
55+
import org.apache.zookeeper.test.TestUtils;
5356
import org.apache.zookeeper.txn.CreateTxn;
5457
import org.apache.zookeeper.txn.TxnHeader;
5558
import org.apache.zookeeper.util.ServiceUtils;
@@ -313,6 +316,39 @@ public void syncTest(@TempDir File tmpDir) throws Exception {
313316
assertEquals(startZxid, sl.zk.getLastProcessedZxid());
314317
}
315318

319+
@Test
320+
public void testPurgeOrphanedEphemerals() throws Exception {
321+
File tmpFile = File.createTempFile("test", ".dir", testData);
322+
tmpFile.delete();
323+
SimpleLearner sl = null;
324+
try {
325+
FileTxnSnapLog ftsl = new FileTxnSnapLog(tmpFile, tmpFile);
326+
sl = new SimpleLearner(ftsl);
327+
328+
long sessionId = 0x1234L;
329+
TxnHeader hdr = new TxnHeader(sessionId, 1, 1L, 1L, ZooDefs.OpCode.create);
330+
CreateTxn txn = new CreateTxn(
331+
"/eph",
332+
new byte[0],
333+
new ArrayList<ACL>(),
334+
true,
335+
sl.zk.getZKDatabase().getNode("/").stat.getCversion());
336+
sl.zk.getZKDatabase().processTxn(hdr, txn, null);
337+
338+
assertNotNull(sl.zk.getZKDatabase().getNode("/eph"), "Ephemeral node should exist before cleanup");
339+
340+
sl.zk.startupWithoutServing();
341+
sl.purgeOrphanedEphemerals();
342+
343+
assertNull(sl.zk.getZKDatabase().getNode("/eph"), "Ephemeral node should be removed for unknown session");
344+
} finally {
345+
if (sl != null) {
346+
sl.zk.shutdown();
347+
}
348+
TestUtils.deleteFileRecursively(tmpFile);
349+
}
350+
}
351+
316352
@Test
317353
public void truncFailTest(@TempDir File tmpDir) throws Exception {
318354
final boolean[] exitProcCalled = {false};

0 commit comments

Comments
 (0)