Skip to content

Commit 78a1d32

Browse files
committed
Fix Windows QWP client test failures
1 parent f12c934 commit 78a1d32

5 files changed

Lines changed: 63 additions & 38 deletions

File tree

core/src/main/java/io/questdb/client/cutlass/qwp/client/QwpWebSocketSender.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,6 +1796,38 @@ public long getPendingBytes() {
17961796
return pendingBytes;
17971797
}
17981798

1799+
/**
1800+
* Snapshot of the producer's symbol prefix whose persisted-dictionary chunks
1801+
* have committed. The persisted size advances only after the chunk CRC and
1802+
* payload have been written, so this observes the write-ahead boundary without
1803+
* reopening the live mmap-backed dictionary (which would attempt recovery-tail
1804+
* truncation and is not supported while the producer owns the file on Windows).
1805+
* Returns {@code null} in memory mode or when the persisted dictionary is
1806+
* unavailable.
1807+
*/
1808+
@TestOnly
1809+
public ObjList<String> getPersistedSymbolsForTest() {
1810+
CursorSendEngine engine = cursorEngine;
1811+
if (engine == null) {
1812+
return null;
1813+
}
1814+
PersistedSymbolDict persisted = engine.getPersistedSymbolDict();
1815+
if (persisted == null) {
1816+
return null;
1817+
}
1818+
int persistedSize = persisted.size();
1819+
int globalSize = globalSymbolDictionary.size();
1820+
if (persistedSize > globalSize) {
1821+
throw new IllegalStateException("persisted symbol dictionary exceeds producer dictionary"
1822+
+ " [persisted=" + persistedSize + ", producer=" + globalSize + ']');
1823+
}
1824+
ObjList<String> snapshot = new ObjList<>(persistedSize);
1825+
for (int i = 0; i < persistedSize; i++) {
1826+
snapshot.add(globalSymbolDictionary.getSymbol(i));
1827+
}
1828+
return snapshot;
1829+
}
1830+
17991831
/**
18001832
* Server-advertised cap on the per-batch raw byte size. Zero before the
18011833
* first connect; updated by every successful reconnect via

core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/SlotLock.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import io.questdb.client.std.Unsafe;
3232

3333
import java.nio.charset.StandardCharsets;
34+
import java.nio.file.Path;
35+
import java.nio.file.Paths;
3436

3537
/**
3638
* Advisory exclusive locks for a single SF slot.
@@ -101,13 +103,15 @@ public static SlotLock acquire(String slotDir) {
101103
*/
102104
public static SlotLock acquireLogical(String slotDir) {
103105
validateSlotDir(slotDir);
104-
int separator = slotDir.lastIndexOf('/');
105-
if (separator < 1 || separator == slotDir.length() - 1) {
106+
Path slotPath = Paths.get(slotDir);
107+
Path parentPath = slotPath.getParent();
108+
Path slotNamePath = slotPath.getFileName();
109+
if (parentPath == null || slotNamePath == null || slotNamePath.toString().isEmpty()) {
106110
throw new IllegalArgumentException(
107111
"slotDir must contain a parent and slot name: " + slotDir);
108112
}
109-
String parentDir = slotDir.substring(0, separator);
110-
String slotName = slotDir.substring(separator + 1);
113+
String parentDir = parentPath.toString();
114+
String slotName = slotNamePath.toString();
111115
String logicalLockDir = parentDir + "/" + LOGICAL_LOCK_DIR_NAME;
112116
ensureDirectory(logicalLockDir, "logical slot lock dir");
113117
String lockPath = logicalLockDir + "/" + slotName + ".lock";

core/src/test/java/io/questdb/client/test/cutlass/qwp/client/DeltaDictRecoveryTest.java

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -877,15 +877,11 @@ public void testFailedPublishDoesNotDuplicatePersistedSymbols() throws Exception
877877

878878
// The persisted dictionary must hold "s0" EXACTLY ONCE.
879879
// Pre-fix, the retry duplicated it (size == 2).
880-
PersistedSymbolDict pd = PersistedSymbolDict.open(Paths.get(sfDir, "default").toString());
881-
Assert.assertNotNull(pd);
882-
try {
883-
Assert.assertEquals("failed-publish retry must not duplicate the persisted symbol",
884-
1, pd.size());
885-
Assert.assertEquals("s0", pd.readLoadedSymbols().getQuick(0));
886-
} finally {
887-
pd.close();
888-
}
880+
ObjList<String> persisted = ((QwpWebSocketSender) sender).getPersistedSymbolsForTest();
881+
Assert.assertNotNull(persisted);
882+
Assert.assertEquals("failed-publish retry must not duplicate the persisted symbol",
883+
1, persisted.size());
884+
Assert.assertEquals("s0", persisted.getQuick(0));
889885
} finally {
890886
try {
891887
sender.close();
@@ -942,16 +938,12 @@ public void testFailedPublishThenNewSymbolPersistsSuffixWithoutDuplicating() thr
942938
// The else branch persisted ONLY s1 (the suffix). The dictionary holds
943939
// s0, s1 exactly once each. Pre-fix (appendSymbols from
944940
// sentMaxSymbolId+1) re-appended s0, giving size 3.
945-
PersistedSymbolDict pd = PersistedSymbolDict.open(Paths.get(sfDir, "default").toString());
946-
Assert.assertNotNull(pd);
947-
try {
948-
Assert.assertEquals("re-encode suffix must not duplicate the persisted prefix",
949-
2, pd.size());
950-
Assert.assertEquals("s0", pd.readLoadedSymbols().getQuick(0));
951-
Assert.assertEquals("s1", pd.readLoadedSymbols().getQuick(1));
952-
} finally {
953-
pd.close();
954-
}
941+
ObjList<String> persisted = ((QwpWebSocketSender) sender).getPersistedSymbolsForTest();
942+
Assert.assertNotNull(persisted);
943+
Assert.assertEquals("re-encode suffix must not duplicate the persisted prefix",
944+
2, persisted.size());
945+
Assert.assertEquals("s0", persisted.getQuick(0));
946+
Assert.assertEquals("s1", persisted.getQuick(1));
955947
} finally {
956948
try {
957949
sender.close();

core/src/test/java/io/questdb/client/test/cutlass/qwp/client/SelfSufficientFramesTest.java

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626

2727
import io.questdb.client.Sender;
2828
import io.questdb.client.cutlass.line.LineSenderException;
29+
import io.questdb.client.cutlass.qwp.client.QwpWebSocketSender;
2930
import io.questdb.client.std.ObjList;
30-
import io.questdb.client.cutlass.qwp.client.sf.cursor.PersistedSymbolDict;
3131
import io.questdb.client.test.cutlass.qwp.websocket.TestWebSocketServer;
3232
import io.questdb.client.test.tools.TestUtils;
3333
import org.junit.Assert;
@@ -389,18 +389,13 @@ public void testFileModeSplitPersistsDictBeforeAFAILEDPublish() throws Exception
389389
// The write-ahead already ran: both of the batch's new symbols are
390390
// durable even though the frame that references them never
391391
// published. Move the persist after sealAndSwapBuffer and this is 0.
392-
PersistedSymbolDict pd = PersistedSymbolDict.open(
393-
sfDir.resolve("default").toString());
394-
Assert.assertNotNull(pd);
395-
try {
396-
Assert.assertEquals("the split path must persist its new symbols BEFORE "
397-
+ "publishing the frame that references them", 2, pd.size());
398-
ObjList<String> symbols = pd.readLoadedSymbols();
399-
Assert.assertEquals("alpha", symbols.getQuick(0));
400-
Assert.assertEquals("bravo", symbols.getQuick(1));
401-
} finally {
402-
pd.close();
403-
}
392+
ObjList<String> persisted =
393+
((QwpWebSocketSender) sender).getPersistedSymbolsForTest();
394+
Assert.assertNotNull(persisted);
395+
Assert.assertEquals("the split path must persist its new symbols BEFORE "
396+
+ "publishing the frame that references them", 2, persisted.size());
397+
Assert.assertEquals("alpha", persisted.getQuick(0));
398+
Assert.assertEquals("bravo", persisted.getQuick(1));
404399
} finally {
405400
try {
406401
sender.close();

core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SlotLockTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,10 @@ public void testCloseReleasesLock() throws Exception {
106106
@Test
107107
public void testLogicalLockRemainsContendedAcrossSlotRenameAndRecreate() throws Exception {
108108
TestUtils.assertMemoryLeak(() -> {
109-
String slot = parentDir + "/rename";
110-
String moved = parentDir + "/rename.quarantined";
109+
// Use the platform-native separator. In particular, this exercises
110+
// acquireLogical with a backslash-only path on Windows.
111+
String slot = Paths.get(parentDir, "rename").toString();
112+
String moved = Paths.get(parentDir, "rename.quarantined").toString();
111113
assertEquals(0, Files.mkdir(slot, Files.DIR_MODE_DEFAULT));
112114

113115
try (SlotLock ignored = SlotLock.acquireLogical(slot)) {

0 commit comments

Comments
 (0)