From a40218baf9ea643362711be038cc6eb151db1829 Mon Sep 17 00:00:00 2001 From: Marko Topolnik Date: Thu, 14 May 2026 15:29:16 +0200 Subject: [PATCH 1/5] Split openCleanRW and allocate responsibilities openCleanRW(path, size) and allocate(fd, size) both advanced the logical EOF, which became a problem once allocate gained a never-shrinks contract: callers that did openCleanRW(path, sz) then allocate(fd, sz) (the only production pattern) hit the target == currentSize short-circuit and got no block reservation. SIGBUS protection on mmap stores was silently disabled. Clean split of responsibilities: - openCleanRW(path): sole owner of file lifecycle -- create or truncate to empty, open RW. The size parameter is gone from the JNI, Java, and FilesFacade API. - allocate(fd, size): sole owner of "extend EOF and reserve real blocks for [0, target)." Cross-platform contract: * Linux: posix_fallocate(fd, currentSize, target - currentSize), followed by ftruncate only on the sparse-fallback path. * macOS: fcntl(F_PREALLOCATE) passes newBytes (not the full target) to fst_length, fixing a long-standing over-allocation on non-empty files; ftruncate(fd, target) advances EOF. * Windows: FILE_ALLOCATION_INFO + FILE_END_OF_FILE_INFO, short-circuiting when the file is already at target. Production callers updated: - MmapSegment.create: openCleanRW(ptr) + allocate(fd, sizeBytes). Restores ENOSPC-at-create semantics for SF buffers. - AckWatermark.open: openCleanRW(path) + allocate(fd, FILE_SIZE) on the wrong-/missing-size branch; the correct-size branch still uses openRW to preserve the previous session's watermark. Tests updated for the new signatures; the full client test suite passes (2219 / 2219). Co-Authored-By: Claude Opus 4.6 --- core/src/main/c/share/files.c | 68 +++++++++++---- core/src/main/c/windows/files.c | 72 +++++++--------- .../qwp/client/sf/cursor/AckWatermark.java | 25 ++++-- .../qwp/client/sf/cursor/MmapSegment.java | 14 ++- .../qwp/client/sf/cursor/SegmentManager.java | 2 +- .../client/std/DefaultFilesFacade.java | 8 +- .../java/io/questdb/client/std/Files.java | 85 ++++++++++++++----- .../io/questdb/client/std/FilesFacade.java | 32 ++++--- .../client/sf/cursor/AckWatermarkTest.java | 3 +- .../qwp/client/sf/cursor/MmapSegmentTest.java | 10 +-- .../qwp/client/sf/cursor/SegmentRingTest.java | 2 +- .../io/questdb/client/test/std/FilesTest.java | 65 ++++++++++++-- 12 files changed, 259 insertions(+), 127 deletions(-) diff --git a/core/src/main/c/share/files.c b/core/src/main/c/share/files.c index 02fb93a1..75f228fa 100644 --- a/core/src/main/c/share/files.c +++ b/core/src/main/c/share/files.c @@ -73,22 +73,9 @@ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_openAppend0 } JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_openCleanRW0 - (JNIEnv *e, jclass cl, jlong lpszName, jlong size) { + (JNIEnv *e, jclass cl, jlong lpszName) { int fd; RESTARTABLE(open((const char *) (uintptr_t) lpszName, O_CREAT | O_TRUNC | O_RDWR, 0644), fd); - if (fd < 0) { - return -1; - } - if (size > 0) { - int rc; - RESTARTABLE(ftruncate(fd, (off_t) size), rc); - if (rc != 0) { - int saved = errno; - close(fd); - errno = saved; - return -1; - } - } return (jint) fd; } @@ -146,8 +133,44 @@ JNIEXPORT jboolean JNICALL Java_io_questdb_client_std_Files_truncate JNIEXPORT jboolean JNICALL Java_io_questdb_client_std_Files_allocate (JNIEnv *e, jclass cl, jint fd, jlong size) { + /* Cross-platform contract — full version lives on + * Files.allocate's javadoc; key invariants restated here so the + * implementation reads on its own: + * - Never shrinks: target = max(size, currentSize); if size <= + * currentSize, return success without touching the file. + * - Reserves real disk blocks for [currentSize, target). The + * pre-existing range [0, currentSize) is left untouched so the + * three platforms agree on what the call does — anchoring the + * reservation at currentSize matches macOS's F_PEOFPOSMODE + * semantics (which can only allocate beyond EOF without writes + * that would corrupt mmap'd content). + * - Real errors (ENOSPC, EFBIG, EIO, ...) surface as JNI_FALSE. + * Filesystem-doesn't-support errnos degrade to a sparse + * ftruncate fallback per sf-client.md §6. */ + struct stat st; + if (fstat((int) fd, &st) != 0) { + return JNI_FALSE; + } + off_t target = (off_t) size; + if (st.st_size > target) { + target = st.st_size; + } + if (target == st.st_size) { + /* Nothing to extend, nothing to reserve. Returning here is what + * makes the never-shrinks property hold across the + * ftruncate-fallback path below. */ + return JNI_TRUE; + } + off_t newBytes = target - st.st_size; + #if defined(__linux__) - int res = posix_fallocate((int) fd, 0, (off_t) size); + /* posix_fallocate at offset=currentSize reserves only the + * newly-extended range [currentSize, target), matching macOS's + * F_PEOFPOSMODE behaviour and keeping the cross-platform contract + * consistent on whether pre-existing sparse holes get filled (they + * do not). On success the file's logical size is already target — + * we return early to skip the unnecessary ftruncate. */ + int res = posix_fallocate((int) fd, st.st_size, newBytes); if (res == 0) { return JNI_TRUE; } @@ -155,13 +178,20 @@ JNIEXPORT jboolean JNICALL Java_io_questdb_client_std_Files_allocate errno = res; return JNI_FALSE; } - /* fall through to ftruncate */ + /* Filesystem doesn't support fallocate; fall through to ftruncate. + * That is the sparse-fallback path — extends to target but blocks + * remain sparse, so a later store past an unallocated page may + * still raise SIGBUS. Per the contract, ftruncate here only ever + * grows (target > st.st_size) so "never shrinks" still holds. */ #elif defined(__APPLE__) fstore_t fst; fst.fst_flags = F_ALLOCATECONTIG | F_ALLOCATEALL; fst.fst_posmode = F_PEOFPOSMODE; fst.fst_offset = 0; - fst.fst_length = (off_t) size; + /* fst_length is the number of bytes to allocate BEYOND EOF — not the + * target total. Passing the full target would over-allocate by + * currentSize on a non-empty file. */ + fst.fst_length = newBytes; fst.fst_bytesalloc = 0; if (fcntl((int) fd, F_PREALLOCATE, &fst) == -1) { /* Contiguous allocation failed (e.g. fragmented filesystem); retry @@ -175,9 +205,11 @@ JNIEXPORT jboolean JNICALL Java_io_questdb_client_std_Files_allocate return JNI_FALSE; } } + /* F_PREALLOCATE never advances EOF, so ftruncate below is part of + * the normal path on macOS — it's NOT just a sparse-fallback. */ #endif int res2; - RESTARTABLE(ftruncate((int) fd, (off_t) size), res2); + RESTARTABLE(ftruncate((int) fd, target), res2); return res2 == 0 ? JNI_TRUE : JNI_FALSE; } diff --git a/core/src/main/c/windows/files.c b/core/src/main/c/windows/files.c index 042aa9c9..119f6315 100644 --- a/core/src/main/c/windows/files.c +++ b/core/src/main/c/windows/files.c @@ -124,25 +124,12 @@ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_openAppend0 } JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_openCleanRW0 - (JNIEnv *e, jclass cl, jlong lpszName, jlong size) { - jint fd = open_file((const char *) (uintptr_t) lpszName, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL); - if (fd < 0) { - return fd; - } - if (size > 0) { - FILE_END_OF_FILE_INFO eof; - eof.EndOfFile.QuadPart = size; - if (!SetFileInformationByHandle(FD_TO_HANDLE(fd), FileEndOfFileInfo, &eof, sizeof(eof))) { - SaveLastError(); - CloseHandle(FD_TO_HANDLE(fd)); - return -1; - } - } - return fd; + (JNIEnv *e, jclass cl, jlong lpszName) { + return open_file((const char *) (uintptr_t) lpszName, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL); } /* ReadFile/WriteFile take a DWORD (uint32) byte count, but the JNI signature @@ -241,18 +228,20 @@ JNIEXPORT jboolean JNICALL Java_io_questdb_client_std_Files_truncate JNIEXPORT jboolean JNICALL Java_io_questdb_client_std_Files_allocate (JNIEnv *e, jclass cl, jint fd, jlong size) { - /* SetEndOfFile alone leaves the file sparse on NTFS: clusters are - * allocated lazily as writes occur. If the disk fills up between - * create and write, the cache manager raises an in-page exception - * on the writing thread when it flushes a mapped page — a - * SIGBUS-class failure that tears down the JVM. FILE_ALLOCATION_INFO - * instructs NTFS to physically reserve clusters now and returns - * ERROR_DISK_FULL synchronously on the call site, matching the - * posix_fallocate contract. - * - * Match the POSIX behaviour of posix_fallocate(fd, 0, size): round - * the request up to the existing logical size so an allocate call - * never shrinks a file that the caller already extended. */ + /* Cross-platform contract — full version lives on + * Files.allocate's javadoc; key invariants restated here so the + * implementation reads on its own: + * - Never shrinks: target = max(size, currentSize); if size <= + * currentSize, return success without touching the file. + * - Reserves real disk clusters for [currentSize, target). On NTFS + * FILE_ALLOCATION_INFO is file-scope (no per-range API), so it + * implicitly re-reserves [0, currentSize) as well — visible only + * to a caller who deliberately created sparse holes inside that + * range, and that caller should treat hole-filling as + * non-portable behaviour. + * - ERROR_DISK_FULL surfaces as JNI_FALSE. There is no + * sparse-fallback equivalent — Windows always reserves or + * fails; spec-compliant fallback only applies on Linux/macOS. */ HANDLE handle = FD_TO_HANDLE(fd); LARGE_INTEGER current; @@ -261,6 +250,12 @@ JNIEXPORT jboolean JNICALL Java_io_questdb_client_std_Files_allocate return JNI_FALSE; } jlong target = size > current.QuadPart ? size : (jlong) current.QuadPart; + if (target == current.QuadPart) { + /* Nothing to extend, nothing to reserve. The early-return is + * what makes "never shrinks" hold and keeps behaviour aligned + * with the Linux/macOS short-circuit. */ + return JNI_TRUE; + } FILE_ALLOCATION_INFO alloc; alloc.AllocationSize.QuadPart = target; @@ -270,14 +265,13 @@ JNIEXPORT jboolean JNICALL Java_io_questdb_client_std_Files_allocate } /* FILE_ALLOCATION_INFO reserves clusters but does not advance EOF. - * Extend the logical size separately when growing the file. */ - if (size > current.QuadPart) { - FILE_END_OF_FILE_INFO eof; - eof.EndOfFile.QuadPart = size; - if (!SetFileInformationByHandle(handle, FileEndOfFileInfo, &eof, sizeof(eof))) { - SaveLastError(); - return JNI_FALSE; - } + * We've already ruled out target == current above, so the file + * always needs its logical size pushed out to target. */ + FILE_END_OF_FILE_INFO eof; + eof.EndOfFile.QuadPart = target; + if (!SetFileInformationByHandle(handle, FileEndOfFileInfo, &eof, sizeof(eof))) { + SaveLastError(); + return JNI_FALSE; } return JNI_TRUE; } diff --git a/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/AckWatermark.java b/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/AckWatermark.java index baea9bd7..efc75edc 100644 --- a/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/AckWatermark.java +++ b/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/AckWatermark.java @@ -137,16 +137,23 @@ public void close() { */ public static AckWatermark open(String slotDir) { String filePath = slotDir + "/" + FILE_NAME; - // openCleanRW truncates, which would discard the previous - // session's watermark on every recovery and defeat the whole - // point. Decide by size: existing-and-correct -> openRW - // preserves the bytes; missing or wrong-sized -> openCleanRW - // creates/resizes (the resulting file has zero magic, which - // read() correctly reports as INVALID until the first write). + // Decide by size: existing-and-correct -> openRW preserves the + // previous session's watermark (defeating which is the whole + // point of NOT calling openCleanRW unconditionally); missing or + // wrong-sized -> openCleanRW + allocate creates a fresh + // FILE_SIZE-byte file (zero magic, read() reports INVALID until + // the first write). long existing = Files.exists(filePath) ? Files.length(filePath) : -1L; - int fd = existing == FILE_SIZE - ? Files.openRW(filePath) - : Files.openCleanRW(filePath, FILE_SIZE); + int fd; + if (existing == FILE_SIZE) { + fd = Files.openRW(filePath); + } else { + fd = Files.openCleanRW(filePath); + if (fd >= 0 && !Files.allocate(fd, FILE_SIZE)) { + Files.close(fd); + fd = -1; + } + } if (fd < 0) { LOG.warn("ack watermark {} could not be opened (rc={}); proceeding without it", filePath, fd); diff --git a/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/MmapSegment.java b/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/MmapSegment.java index 6584c085..162490c6 100644 --- a/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/MmapSegment.java +++ b/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/MmapSegment.java @@ -165,17 +165,15 @@ public static MmapSegment create(FilesFacade ff, long pathPtr, String displayPat throw new IllegalArgumentException( "sizeBytes too small for header + one minimal frame: " + sizeBytes); } - int fd = ff.openCleanRW(pathPtr, sizeBytes); + int fd = ff.openCleanRW(pathPtr); if (fd < 0) { throw new MmapSegmentException("openCleanRW failed for " + displayPath); } - // Reserve real disk blocks so ENOSPC surfaces here, before the - // producer thread starts writing frames into the mapping. The - // openCleanRW call above only sets the logical file size via - // ftruncate; the blocks remain sparse until something writes them. - // Calling allocate immediately after promotes ENOSPC from a - // SIGBUS-on-mmap-store (which aborts the JVM) to a clean failure - // path the caller can recover from. + // Reserve real disk blocks and advance EOF to sizeBytes in one + // call. ENOSPC surfaces here, before the producer thread starts + // writing frames into the mapping — a clean false return + // instead of a SIGBUS-on-mmap-store later (which would abort + // the JVM). if (!ff.allocate(fd, sizeBytes)) { ff.close(fd); // Unlink the partially-created file so a sf_max_bytes-sized diff --git a/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/SegmentManager.java b/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/SegmentManager.java index 82b81cbf..b817fa84 100644 --- a/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/SegmentManager.java +++ b/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/SegmentManager.java @@ -40,7 +40,7 @@ * Background worker that keeps every registered {@link SegmentRing} supplied * with a hot-spare segment and trims segments after their frames have been * ACK'd by the server. Off the user-thread / I/O-thread hot path entirely: - * the expensive {@code openCleanRW + truncate + mmap} for spare creation and + * the expensive {@code openCleanRW + allocate + mmap} for spare creation and * {@code munmap + unlink} for trim happen on this thread, never on the * latency-sensitive paths. *

diff --git a/core/src/main/java/io/questdb/client/std/DefaultFilesFacade.java b/core/src/main/java/io/questdb/client/std/DefaultFilesFacade.java index e8b06075..d9b4b614 100644 --- a/core/src/main/java/io/questdb/client/std/DefaultFilesFacade.java +++ b/core/src/main/java/io/questdb/client/std/DefaultFilesFacade.java @@ -102,13 +102,13 @@ public int mkdir(String path, int mode) { } @Override - public int openCleanRW(String path, long size) { - return Files.openCleanRW(path, size); + public int openCleanRW(String path) { + return Files.openCleanRW(path); } @Override - public int openCleanRW(long pathPtr, long size) { - return Files.openCleanRW(pathPtr, size); + public int openCleanRW(long pathPtr) { + return Files.openCleanRW(pathPtr); } @Override diff --git a/core/src/main/java/io/questdb/client/std/Files.java b/core/src/main/java/io/questdb/client/std/Files.java index 681d68b5..02756c37 100644 --- a/core/src/main/java/io/questdb/client/std/Files.java +++ b/core/src/main/java/io/questdb/client/std/Files.java @@ -171,28 +171,29 @@ public static int openAppend(String path) { /** * Opens {@code path} for read-write access, truncating any existing - * content (mode 0644). When {@code size > 0} the new file is extended - * to exactly {@code size} bytes via {@code ftruncate}; when {@code size} - * is 0 the file is left empty. Returns a non-negative fd on success or - * -1 on failure (e.g. truncate failed due to ENOSPC). + * content (mode 0644). The new file is empty; callers that need a + * specific size should follow this with {@link #allocate(int, long)} + * (reserves real disk blocks, fails synchronously on ENOSPC) or + * {@link #truncate(int, long)} (sets logical size only, leaves blocks + * sparse). Returns a non-negative fd on success or -1 on failure. */ - public static int openCleanRW(String path, long size) { + public static int openCleanRW(String path) { long ptr = pathPtr(path); try { - return openCleanRW0(ptr, size); + return openCleanRW0(ptr); } finally { freePathPtr(ptr); } } /** - * Variant of {@link #openCleanRW(String, long)} taking a pre-encoded + * Variant of {@link #openCleanRW(String)} taking a pre-encoded * native UTF-8 path pointer; lets callers cache the encoded path and * skip the per-call {@code byte[]} + native-malloc that * {@link #pathPtr(String)} incurs. */ - public static int openCleanRW(long pathPtr, long size) { - return openCleanRW0(pathPtr, size); + public static int openCleanRW(long pathPtr) { + return openCleanRW0(pathPtr); } /** @@ -415,16 +416,60 @@ public static String utf8ToString(long nameZ) { public static native boolean truncate(int fd, long size); /** - * Reserves disk blocks for the file up to {@code size} bytes. On Linux - * uses {@code posix_fallocate}; on macOS uses {@code F_PREALLOCATE} - * with {@code F_ALLOCATEALL}; on Windows uses - * {@code SetFileInformationByHandle(FileAllocationInfo)}, which on - * NTFS reserves clusters synchronously and fails with - * {@code ERROR_DISK_FULL} when free space is insufficient. Never - * shrinks the file: requests smaller than the current logical size - * are rounded up. Falls back to {@code ftruncate} on Linux/macOS if - * pre-allocation isn't supported by the underlying filesystem (in - * which case the logical size is set but blocks remain sparse). + * Extends the file to at least {@code size} bytes and reserves real + * disk blocks for the newly-extended range. Lets the caller catch + * {@code ENOSPC} as a clean {@code false} return at this call site + * rather than as a runtime SIGBUS (POSIX) or in-page exception + * (Windows) on a later store into an mmap'd region. + * + *

Cross-platform contract — identical observable behaviour on + * Linux, macOS, and Windows for any caller that does not + * deliberately produce sparse files: + *

+ * + *

Per-platform primitives (provided for the curious; not part + * of the observable contract above): + *

*/ public static native boolean allocate(int fd, long size); @@ -516,7 +561,7 @@ public static void munmap(long address, long len, int memoryTag) { static native int openAppend0(long lpszName); - static native int openCleanRW0(long lpszName, long size); + static native int openCleanRW0(long lpszName); static native long length0(long lpszName); diff --git a/core/src/main/java/io/questdb/client/std/FilesFacade.java b/core/src/main/java/io/questdb/client/std/FilesFacade.java index b64a9f7d..e6d90f95 100644 --- a/core/src/main/java/io/questdb/client/std/FilesFacade.java +++ b/core/src/main/java/io/questdb/client/std/FilesFacade.java @@ -36,17 +36,21 @@ public interface FilesFacade { FilesFacade INSTANCE = new DefaultFilesFacade(); /** - * Reserves disk blocks for {@code fd} up to {@code size} bytes. On Linux - * uses {@code posix_fallocate}; on macOS uses {@code F_PREALLOCATE} with - * {@code F_ALLOCATEALL}; on Windows uses - * {@code SetFileInformationByHandle(FileAllocationInfo)}. Returns - * {@code true} on success, {@code false} on failure (most importantly - * ENOSPC). Test injection point: a wrapping facade can return - * {@code false} to simulate disk-full at create time so the caller's - * recovery path is exercised. Callers that observe {@code false} MUST - * close the fd and unlink the file — the partially created file would - * otherwise occupy its full logical size on disk. Default delegates to - * {@link Files#allocate(int, long)}. + * Extends the file to at least {@code size} bytes and reserves real + * disk blocks for the newly-extended range. Returns {@code true} on + * success, {@code false} on failure (most importantly {@code ENOSPC} + * / {@code ERROR_DISK_FULL}). Never shrinks; requests where + * {@code size <= currentSize} short-circuit as a no-op success. See + * {@link Files#allocate(int, long)} for the full cross-platform + * contract — sparse-fallback rules on Linux/macOS, the "pre-existing + * sparse holes are not filled" caveat, per-platform primitives. + * + *

Test injection point: a wrapping facade can return {@code false} + * to simulate disk-full at create time so the caller's recovery + * path is exercised. Callers that observe {@code false} MUST close + * the fd and unlink the partial file — the partially-extended file + * would otherwise occupy up to {@code max(size, currentSize)} bytes + * on disk. Default delegates to {@link Files#allocate(int, long)}. */ boolean allocate(int fd, long size); @@ -87,15 +91,15 @@ public interface FilesFacade { int mkdir(String path, int mode); - int openCleanRW(String path, long size); + int openCleanRW(String path); /** - * Variant of {@link #openCleanRW(String, long)} taking a pre-encoded + * Variant of {@link #openCleanRW(String)} taking a pre-encoded * native UTF-8 path pointer; lets callers in hot paths cache the encoded * path (e.g. via a reused {@code DirectUtf8Sink}) and skip the per-call * {@code byte[]} + native-malloc allocation. */ - int openCleanRW(long pathPtr, long size); + int openCleanRW(long pathPtr); int openRW(String path); diff --git a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/AckWatermarkTest.java b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/AckWatermarkTest.java index b4a0e141..001f62ce 100644 --- a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/AckWatermarkTest.java +++ b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/AckWatermarkTest.java @@ -142,8 +142,9 @@ public void testStaleFileWithWrongSizeIsResetOnOpen() throws Exception { // write. TestUtils.assertMemoryLeak(() -> { String path = slotDir + "/" + AckWatermark.FILE_NAME; - int fd = Files.openCleanRW(path, 4); + int fd = Files.openCleanRW(path); assertTrue(fd >= 0); + assertTrue(Files.truncate(fd, 4)); Files.close(fd); assertEquals("precondition: file exists at wrong size", 4L, Files.length(path)); diff --git a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/MmapSegmentTest.java b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/MmapSegmentTest.java index 34be6745..42da6526 100644 --- a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/MmapSegmentTest.java +++ b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/MmapSegmentTest.java @@ -326,7 +326,7 @@ public void testOpenExistingRejectsCorruptHeader() throws Exception { TestUtils.assertMemoryLeak(() -> { String path = tmpDir + "/seg-bad-magic.sfa"; // Build a file with the right size but the wrong magic. - int fd = Files.openCleanRW(path, MmapSegment.HEADER_SIZE); + int fd = Files.openCleanRW(path); long bufHdr = Unsafe.malloc(MmapSegment.HEADER_SIZE, MemoryTag.NATIVE_DEFAULT); try { Unsafe.getUnsafe().putInt(bufHdr, 0xBAD0FACE); @@ -596,21 +596,21 @@ public int mkdir(String path, int mode) { } @Override - public int openCleanRW(String path, long size) { + public int openCleanRW(String path) { openCleanRWCalls++; if (failOnOpenCleanRW) { return -1; } - return INSTANCE.openCleanRW(path, size); + return INSTANCE.openCleanRW(path); } @Override - public int openCleanRW(long pathPtr, long size) { + public int openCleanRW(long pathPtr) { openCleanRWCalls++; if (failOnOpenCleanRW) { return -1; } - return INSTANCE.openCleanRW(pathPtr, size); + return INSTANCE.openCleanRW(pathPtr); } @Override diff --git a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentRingTest.java b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentRingTest.java index cec666a2..0043e521 100644 --- a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentRingTest.java +++ b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentRingTest.java @@ -370,7 +370,7 @@ public void testOpenExistingSkipsBadMagicFile() throws Exception { s0.tryAppend(buf, 16); s0.close(); // One stray .sfa with no proper header — must be ignored. - int fd = Files.openCleanRW(tmpDir + "/stray.sfa", 64); + int fd = Files.openCleanRW(tmpDir + "/stray.sfa"); long hdr = Unsafe.malloc(8, MemoryTag.NATIVE_DEFAULT); try { Unsafe.getUnsafe().putLong(hdr, 0xBADBADBADBADBADBL); diff --git a/core/src/test/java/io/questdb/client/test/std/FilesTest.java b/core/src/test/java/io/questdb/client/test/std/FilesTest.java index 2c69c362..ebef927a 100644 --- a/core/src/test/java/io/questdb/client/test/std/FilesTest.java +++ b/core/src/test/java/io/questdb/client/test/std/FilesTest.java @@ -77,7 +77,7 @@ public void tearDown() { public void testWriteReadRoundtrip() throws Exception { TestUtils.assertMemoryLeak(() -> { String path = tmpDir + "/test.bin"; - int fd = Files.openCleanRW(path, 0); + int fd = Files.openCleanRW(path); assertTrue("expected fd >= 0, got " + fd, fd >= 0); try { long buf = Unsafe.malloc(8, MemoryTag.NATIVE_DEFAULT); @@ -109,8 +109,9 @@ public void testWriteReadRoundtrip() throws Exception { public void testTruncate() throws Exception { TestUtils.assertMemoryLeak(() -> { String path = tmpDir + "/trunc.bin"; - int fd = Files.openCleanRW(path, 1024); + int fd = Files.openCleanRW(path); try { + assertTrue(Files.truncate(fd, 1024)); assertEquals(1024, Files.length(fd)); assertTrue(Files.truncate(fd, 0)); assertEquals(0, Files.length(fd)); @@ -136,6 +137,55 @@ public void testAllocate() throws Exception { }); } + /** Pins the cross-platform contract on `Files.allocate`: + * never-shrinks, short-circuits on size <= currentSize, extends on + * size > currentSize. All four assertions must hold identically on + * Linux, macOS, and Windows — see Files.allocate javadoc. */ + @Test + public void testAllocateNeverShrinks() throws Exception { + TestUtils.assertMemoryLeak(() -> { + String path = tmpDir + "/alloc-shrink.bin"; + int fd = Files.openRW(path); + try { + // Grow to 64 KiB. + assertTrue(Files.allocate(fd, 65536)); + assertEquals(65536, Files.length(fd)); + + // Smaller request: must not shrink the file. + assertTrue(Files.allocate(fd, 4096)); + assertEquals(65536, Files.length(fd)); + + // Equal request: no-op success, size unchanged. + assertTrue(Files.allocate(fd, 65536)); + assertEquals(65536, Files.length(fd)); + + // Larger request: extends to the new target. + assertTrue(Files.allocate(fd, 131072)); + assertEquals(131072, Files.length(fd)); + } finally { + Files.close(fd); + } + }); + } + + /** A size=0 allocate on a fresh file is a no-op success — exercises + * the same short-circuit as testAllocateNeverShrinks but with the + * edge case of an empty file (no fallocate / F_PREALLOCATE syscall + * should reach the kernel). */ + @Test + public void testAllocateZeroOnFreshFile() throws Exception { + TestUtils.assertMemoryLeak(() -> { + String path = tmpDir + "/alloc-zero.bin"; + int fd = Files.openRW(path); + try { + assertTrue(Files.allocate(fd, 0)); + assertEquals(0, Files.length(fd)); + } finally { + Files.close(fd); + } + }); + } + @Test public void testAppend() throws Exception { TestUtils.assertMemoryLeak(() -> { @@ -162,7 +212,7 @@ public void testRename() throws Exception { TestUtils.assertMemoryLeak(() -> { String a = tmpDir + "/a"; String b = tmpDir + "/b"; - int fd = Files.openCleanRW(a, 0); + int fd = Files.openCleanRW(a); Files.close(fd); assertTrue(Files.exists(a)); assertEquals(0, Files.rename(a, b)); @@ -176,7 +226,7 @@ public void testFindFirstIteratesAllEntries() throws Exception { TestUtils.assertMemoryLeak(() -> { String[] names = {"alpha", "beta", "gamma"}; for (String n : names) { - int fd = Files.openCleanRW(tmpDir + "/" + n, 0); + int fd = Files.openCleanRW(tmpDir + "/" + n); Files.close(fd); } long find = Files.findFirst(tmpDir); @@ -207,7 +257,7 @@ public void testFindFirstIteratesAllEntries() throws Exception { public void testLockExclusive() throws Exception { TestUtils.assertMemoryLeak(() -> { String path = tmpDir + "/lock.bin"; - int fd1 = Files.openCleanRW(path, 0); + int fd1 = Files.openCleanRW(path); int fd2 = Files.openRW(path); try { assertEquals(0, Files.lock(fd1)); @@ -224,7 +274,7 @@ public void testExistsAndRemove() throws Exception { TestUtils.assertMemoryLeak(() -> { String path = tmpDir + "/x"; assertFalse(Files.exists(path)); - int fd = Files.openCleanRW(path, 0); + int fd = Files.openCleanRW(path); Files.close(fd); assertTrue(Files.exists(path)); assertTrue(Files.remove(path)); @@ -243,8 +293,9 @@ public void testPageSizeIsSane() { public void testMmapRoundtrip() throws Exception { TestUtils.assertMemoryLeak(() -> { String path = tmpDir + "/mmap.bin"; - int fd = Files.openCleanRW(path, 8192); + int fd = Files.openCleanRW(path); try { + assertTrue(Files.allocate(fd, 8192)); long addr = Files.mmap(fd, 8192, 0, Files.MAP_RW, MemoryTag.MMAP_DEFAULT); assertNotEquals("mmap returned FAILED", Files.FAILED_MMAP_ADDRESS, addr); try { From db3ab374032b7b6d9e1019ee8861aa19ff9a4f44 Mon Sep 17 00:00:00 2001 From: GitHub Actions - Rebuild Native Libraries Date: Mon, 18 May 2026 10:56:41 +0000 Subject: [PATCH 2/5] Rebuild CXX libraries --- .../bin/darwin-aarch64/libquestdb.dylib | Bin 132144 -> 132144 bytes .../client/bin/darwin-x86-64/libquestdb.dylib | Bin 208984 -> 208984 bytes .../client/bin/linux-aarch64/libquestdb.so | Bin 290424 -> 290424 bytes .../client/bin/linux-x86-64/libquestdb.so | Bin 258776 -> 258776 bytes .../client/bin/windows-x86-64/libquestdb.dll | Bin 227328 -> 227328 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/core/src/main/resources/io/questdb/client/bin/darwin-aarch64/libquestdb.dylib b/core/src/main/resources/io/questdb/client/bin/darwin-aarch64/libquestdb.dylib index 4b53fe79637aa5242099b50f23a60a1fe5f01853..82d21e5953216d80c567f3ec6c54afbb4ed85ff6 100644 GIT binary patch delta 6525 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 delta 6616 zcmZvgdtA)f`@qjLGu5PfO_WMSt;#LiovvH0EMe0wyRns|Wg}!SZaWd*9oLPCBcfWN z!itGpT0$t)H#V1b+gx@xHVhTjmVW2VdF*e0`}&m-)<;s^q0Ad6vpj zt01~o?G3t5t>WO;Hxl#Z{fAsXo9H$CMBzQ@=FeQtXo{p!7HV^AW9PxqL~&m#EoqcW zN*+jMB_2AKAxleK)j`&M9A-q5>=r30$p=yUq{);H}Y7O1c+^6}OH*Q{<|?Z@mUaX=e9Mk-#eTHJdo_)eN`YBZ2>DezAL3W>QZs z58F^FjlZ5xNWI3!$=e+Es-r!VV59n==N~XxlQaBRDY$4B`i%S>#;Ff)p9CY-COb}n zTwS}v%kZv;81J^)FfXOoX{1vg>SrCj)meG1uwAn<-%AEr>SKi(>zVE(Th*{WCP5-! zvuxH^{{2aQ%yjj#U5@avreK#J^p-PsC*mmW`I1$@5XWk3FqVKMGw%gPIx;5SV z`%9o+<5VgG_(tP;Z~(wC&FDkna_FaCcl>8~tVui(E`iDFT|a#f-Ws2iKLean8&-bD zve;0m(Fd(M^1?$XSG!l2!z#`F>Om5Cqc*ve1`jk#FKv{WYMYUqgP^D?Roi2fa>Y4ZA!Aw(g@1Y@V zR_||$1Et3BF_pk|O~Id5GQEc0B>S-1`Bfiv{c~?wB_p+YzKzC7+oi|Dz8VkjXlB1k z0m*pC~qs}tJIAx|@}y$`U8uj&|N=&9hfwk{c}{;6Xa^jAOau!BI2VdqGIr|QVR z2Es$l?7wzOp;Kego^264kl){{HA6v)-FeyLQ7Y53$rM*aYs0Ar&WgF6o zWW+#dMocwr>(53zK%)o1pYSyeZ~;3|(T`j}199bsuF#?fJ>}gVunk~KdCp)MC52t| z{xBE_$EcwvT!7W(S3Kdl1nSC9d%<@go1sS(8-H}7sXh<^_Vl0+INFBkk?eLhswMk$ zG$6z8%Sdb|;~x6h2gXAu4e$j!6DO{fF)bmn_i3Upq(daV=F27+MB99s-7p&I2Ls?I zI+K6&q3isZZYSOE$4ar5{^kc>GAbhqnjZuT>gf;mtX$Fl5N=T`BiXOmc)#kC;RP)E zhqS<-&0rL*@rQ{PJD8RqQYC7K(jfuh1O2xgLLUDZL{%ffNg|Wcl9A9?zLEt zY@|JOUI;kS)F3wbXu2v0LfC571hMH@a|0`C9t_4{OC5sQE}BZ01%s8uKuR|UgD=$3 z+F(}DNcxn0NE#$`y&v>rdu&JugtPs}0(*f4-4z0U5+bGdLckSDsVo$xuv8O6L2Xek zA=z!Lkf*Sa{!}lF1&pPxVQej}XigXmG&E*L{L-|iHDO?9@hL;TA^F1drRkz=VKB-f z;$Pz5Y4m$A-eT~-#C2ReQrXHb;x-m@vv)=eTW7Wk)+J$;88KGOoCiM50v`dAEhnMJ z94*Ma#y35&y;=SEs%J>r{qpq5i!UT3mapD@b~WE-wr~B4{^3V@dSWHakFa(4fyost z9`ms%DPyohCm_eHNQULczig;?ICwiZF(Hd|FbqiSO7{FVgG!cxjISW=4rlxNXgIhy z*Zsrx^Z&;-iQA5GZv2N?^gqn_qO=S#URL*HMN}lSL;okwp8LetQ<& z4EE`SMRfmJ7+_M=%DpQ8;(ZaldlrUxwdz3ooE1$HHhud1(Q{^`d6F#2#I&R@ID3-K z5*b<1#a)i-Y%6F`6?i0MXt}8CCGYEB@V>f-_qjbXn3$BfU_tVH(!*SU#M>#_bOU3v~|p^(yZ@DaObo}Pn+iiQq$I(E^ONnY2H}YhimOrhfc^4qe zsA>><&gZVV^v?^hHDc|5d72eRbjcp&^GlrmRpOkaoS_NTU%?p@o}Y=4$Ol-d9rN8u-MZNHa?W4_HpP~9Y`XMtO{U}patmLXvT6Yl^SQS0xp}S7= z+^cnY7156_fv-XC8!_g_OWOxSc&xHBT$FwA@48T5vb0 z*9}`mZ`Hy;lcr`qdX_HvYHC^s4*Iz~#X>r~4ifdd*hyyJKc|0ZZvY4C_<47yx9Z@f zvH}(?NR61>1fHJ8#x73Am*I0KdrucnV;g^O8yRzA&7WQS{eX}J8y%8p|8(QYJ`3UNBlZ3tj_4|;!kjJhRdKXqHUa{O9 z!QqT~0_|G`^Gb64H_BWg_yz?T$m@_*$a|0@3WWU$#ysF!GUhX|FJE;P#!HlPA0hW+vCP<`4RQ*y6Y@6X zM*Pvr=f~}vkoDO!=XT@(WP<*4k>I^0Er!DwK_UVeB$H{^h~Jc0>Xn~z)+ zD0l~QWsu;j$gRjSsfefb6MA1AvyChZ7J?KsIEoHikpq#dk%_;sXE&iP!6kgEto3E5 zo+n(7`f%h3@#egf`z$L@Pu7FY-S2e#%=1ax3zHUc!DZ{p2PLvykJUH7I^Wb8bS2^6j4|`9{K; zSnGn38f0_i7sx7h#qj1}jP~q=^EMh;i~4lrwa7)tRT%GqU?p)vK|d1_Fc&!vdF?w(_2f^!*1MLKtB752kzK*PN7W@Rco}X6MdUVY8YZC@^Fck?ba1sVXk&WLG9F9EK zRd76VDC)mPcEg0$AbTU{BR}fUjZY46RRpj#wj#j*WY4~WKSnOa3Cw1!S#BcO&zybn@&s~w3Bg!280#)L33&zHX)BTaF~I_4f|qp#a)m_r zUq>!_B@%jy+?6j_&w{N#PoM}Zzn517u*wPl_;2w0q-Jv7YK*f$k{exM&_0x!AfKg;BMqHEV-qXNVpz1sT=YZu}PIA6a`gM5g-QH z(@SKSfZUA7W)89&Cb$y$j`FIs1E= z?~y3n6AsAsUP3<{*#$Wgc}cnZJ$T}*tQKa3q|Tf+@5|)$1=FVuoxYF(F*41)S>15K z$=$`IYfQ1f!}vOnG5#gn`|mm%w=*Osr@X>-Lg%cZ2lv{}KK1RiC56ueXZMHp*Y8I( z+ttKMNcq`Tt29ybZ}dwk$Q>su}?nzE#u+3l+bGh%Zf8n z|A_v5QNgH``063`&eAmwheO@7FQqJ0Jv4Z*YT}2#AGXb~k=67Bsa&>g8k`<;=9k4s zi|fW^JowH3*3Ki%zI_*&8vpXX>#`foSI-|0RrdF(ySVYepvo;HM)tA3`$G_Ic27LA zXqzHp^v`+U)^D~C|4P4icIOVb6CaF-QH$3|FV$E_?KQc=+CI z$J-u`K*7kRl@iy%f?Oqp}c+5*Xt{fuKC(+rlryoJlw~edhmtf zi=mCy!E@WFm8!Bj%57M1%r9Q%%O?j#t={c%@x(CAs}Vudrq9xTk@&$L<1v;q`ku{7 z1n)WFvkMHpU;A2z?d)qAad*q>Gl{|N&-2p~4xZHS-md4J60_9r)&rNl!>o?1`Cx;> zv?1a3ts!3XEB0?R+rPUlx--Z+s3hk`a3Ms&o4`(*sir-C8bqoP8(#$ec>oGfyvi zte8@uy=L{}rPbd%CQR97wOnubv`0UD>%Ct8-hf_q=g(bwoVE6y!gbNl=~JmB{C@!J CG;CG? diff --git a/core/src/main/resources/io/questdb/client/bin/darwin-x86-64/libquestdb.dylib b/core/src/main/resources/io/questdb/client/bin/darwin-x86-64/libquestdb.dylib index bea672c78eba83d86608a9dbe0846a0a10575bea..647a12cbb93880b7a5e968389ba788216a55218b 100644 GIT binary patch delta 9019 zcmZYEd0bW1+Q9MWEI~o6!)rO9Bu$gWK;k;FjoR)R=|@4(`lwR7qR`&cs)RTFDu z7uCM$TRP#f!!3f}cNSTk7FKxbQdBT#t?QyZP4j-suKithh4n)hl^HYcw`?leuk^MZ zI$JqOD&h7aI`-=^FI&$gsjj!XomhqG$1B#cBo#k;(dC_@JK;N&;rql>yd$l)w$^t% zxT}-M(m-Lo#K48^c6#NI=gVB4ZYMqNVJF?i8=WmqcgpO~bxKh!+{^SnMtXc_J-*|K*MnEA+!S?}>qYB{ z6xHVbh2%0uXN=0p))m3NI^xkk$|D|M`QB)Kq~NXlOk{}1*K~mIK=5(x3cJJl7dRDc zYn@6_joW4EV84}Vc4UhFI@ot3I9Ip9&UW0Fb}7Qn47QqgRc+nBbog}#D|-ZYSVOz2 z)~*k%SzXmE_Y$4crJlw6B67!Hc-H#1tBP^;x1v&2KbOzSPF1n4`PTGQ)ykD^txi=f zT|Zi7sj9QAL-+ZOMzjf#AQyza|vkpvg$Mp;OD%G@ z{5rmxa(m;Pitz2$$TXFdT%;5F9+y4#$Bp;&N0@s_t!^s9C+n25Q})O8cU|r!=biWv z>+Lkvta}%owr`0&_Q#EPMIyw#Uq$$4E3cdC;rffUK{vP6wAWeZZ)JFQ>r6NGQ={H< z^o?Tw7Qg=0#_zJe>#o+iI{H`iP>o%##{R$eRI$qSqyO#RYMrb3#91QqZui_cIk-4{ zSb@G1Yug5l-t|A8uCBY951;v4#h&TEuPE~`=&NGxY}7c{zJ@0<#^g;H`(SQuwP$5` zRIasUlxp7b%2<(dkME|w!{#*5PpL1pgBAMUfc}2e$*l~3&u*nQ{(b*Mh=`B^5v{H5QTdD9;-B!0&Qq@Mgtx|!u zN+e#X1pR<5Xs1-Qe&|NGS9PO@scjp>J4E{Hma1ma-hy;pCeiY@`%N3w&$LTui~1V> zg6C!$U!^@kerkUcKTSI}%Q%7keuM{mOnkp-y%Orc#DONUk2Zdgaryw$F2m<(J%der zBl%Zx)DRPYoK~^F4{@iVCcoNgz0p!bU|6NA@$>?T zEO%u$s=Xz(0cdgCks)MV_ z|4F$jRS~7?Q#wdfeQ=;#~!v*eQfS>${#y=-^pq3M(5a^1=E3*}nfFe$Yv z52!XHO3&G;M`uiuRrO{+F=L`^t`{aX&Q?_y>^+oTh;SvyejP2*dlxl%#v_GO;sIkD(#C*-7ybZ4Z_LzCo8#PpeSCnv~-h#9&&334?; zZ>77SKfkswP3y)ScXDMP+j}Z2vwyrACsJxfRjJs)Z)~-%MaHNxaxx|- zC$o#3jR})?uG`gBH|*o~9WA$F`b$x>Fgd@--aJrihjrz1&2lo6#L|hPo6g>?*;T4T z+mrh0>L2J$d%t?)&ztp^uXN4!HzQpsiCrY9zwh+FSEZ_55$XB)`RVCJ%D?5PTIvc< z&MwFnfAYub=`gRP**C0y9<~%|p&yUY_#xa9FU1LX11{}mwtokg;V*CiODmDv_5MG@ z^^LvqL3gu*XbO7uFm8#faAzz%O}+>2gfnmk&ceg-5Ih;@IQELnC-5KzPvJbg0Z+yO zJPm(@XW^@O9**c`I=lck!HaEsT_WuX_$Wxmt8p4$htu%}oR2r+BK!}$0>6gK@HTC) zeN8?l@D>GU@m_o#@5l9en=?9uo8lw5Ek2GDa5bKQ&$ZIeT)P9a2wbM%ulO2Xhi~HN z@xSm+T&K0U$&TU(d=cM<>-I4x(6F_>{&okN6KF!gy*L*4z^!l=Ziffq4mcYp-~yb2 zOK^8Df#(VI!2vuNU%|t%%NZ(&jKK}@1RRei;r943JQ8~g2$)yD$Q-97j2$Es@hW7N5f%@g>|1*Wf9(_3`Bg0%mw8atkkEgF0=@O;cq-Q8TIcvOoq&&mx%da{#b)3uvIMJ4{S)fHVmt${$7X;mvH?FueiJ^Q z=`}lejer?Ri)_W;v%zkh?~JHL_F*&97OBM3$sffh@n_hK$VIC0IWGkl2n=_I<|5ay z8M^DqB+er*ZOuJ#2uETwwAVR&f_xL)&l&3L5ia(cIe?y>5O{!sPWT<1ip^X>q$hr# zd?xPdOaVlOVl!nB$-$ZA#~FL&KM72xz|0{;9>Xt_pN((fr?Ht+h%CbOoH>TbGdLHo z!DenF^1Nex|4pS}BL#2c&DczWL|(_01S(nK7_%iulaJn=55~&++?h!LP6N$n@$ls~$y#F^6xQhZaeG_So z|4F_BR?hTIBpI7&o=7(wO};Olga=_W9Tdrq554~>c#wkq_+f13iz1KUqvQ*4J7?M` z@&q>XN|A*)iTrZB9GArF>u-0r9R+JC_%9s9|G;LRD)PVB%u7Yyz-i7rQ!kd{ zCAb`$d8)`kFM%}_9KomXr`SwfMNZ*ss z3!60uy_koS$sfd}w(a-dQ37UBLgY9uV}sK;)>*6&xrEK)g-8v)kNhqCJPy0tTytlU zLmw{^zoPT@@#PKzcRGt7B6nf47$VXcFD9Ri*W&?r8y<>x;T(JzkHIygc>hl&&~l8q z$>!k%yciF}D{vt$#xLN%;kU7czr-)%>tlHTZzS*&1ux^e51I~b!I8KOH^Vz{2fPb+ z!h3NdK7naD6~R@n4_0-F^% zkentCldwh>G3LDji19;@Opd;Z@|&x%o&&BR`?~{ z5pULZ-v4O?-lSj>-i=Fe0B^wi@fQ3(uEK}$m-u6>*Djq)a;`n^|4!pg;1UHHSne@5 z;dmU5C*t~eK6c}Bd?&8NG59cUagVf!F0F_z6+<~c-#|r z#2I)3&hiqNM_>?MhKJ!|JOT&sIQ#*A7@xpX@u&E4`~~(F5I94i5GUoCGkg+f;Dvwk zUvYo(%kfye3K!t#@H}I$tS9gk1ux-ccr*S8zmAXN?f5i)3tz;0@Fn~qZjx`Gp#HzB zqXgodfPVerzW8%I8lT2v@Flz&*WefZr@v9J*YQ^K)^qE_x%ItP&4Onv&gO}#H#2$a zlR~Dx*PrUMa1r-rHom}>%rkB%q1SXF1sPVWYbs7J=Vx3~ot3vV$z17y^~~`DU5sbr zngrt)ae9jJflz%{<7>Etd|ZUtUlI9%xFw!r?3GdirJ({kbP(5&x0lK7AFwpp9I$1i zaTOkjqv^mboPb{lW9hB%i=V3bL(5Kd50|;fJ_f-yA2O{WOl^#Be&!r{B{; zX8Sd`gv0E^0sK9#!7&Y-{aeGXtBr}fN14vval1L_q|lQIKg-qbg%ka)Zm7LsuH)9} zpH!STG=h4xk=a}ix%6G2XG%CU;#z@21C+Hmv>Lw&hgRda;n1?)UL0Dt{J?m)gw}qK iQ4m_bKaE4ls6EJk{~*^3Wx#{K%rw0A_`&9A@5e*f0Xg{_tCe0=bT!)s#10D z<#gAAvxAZq*X!q}c-DsfxuX(~ zFRbqtCBfQQC!S}=&*^0yzsHVWxFx>V#9QikcB!u0Z2U`+uG??w@=J7lklF4_>$m_IMK&go8K@v&|XRo z%{x^!d{xO}k7w11rJfmuslJ20GLP@;;A0fJgVCli%~zdwrmEhm5__SQuQce=@w9?C z-|1iry%fCCP16^*r&fQmQX?j3m%3Z0iEQkyx-^@m3q1C2TLri3GJChDs~QBKveLUN zPc!##*|b}-$=0^+Dm}X4Z`li7&AMmxx+l6?Aw5)Ff@MiMtZ#}**;;#k2wuU9DyIgCmS&6FMpn2p{$ES|V%+M9V?mFUGRg(9R z$5*;PN^d;)-908U!sBZ;#CI?_NxRJ1uSZ|Q+pSL$RnrbhI+#|FY)8iFMSc4OXXrNA z+K&5@t0L@7U#me+)!zMm=U+Ea&?mUf>fckfb^Wh3wWoU6ou_lURR7k9tnA9A)|s9v z+SSwgxu;5VEwNIPR7=-PD?3TGaiv(^Bo*VTw2G2cH`g31kfdTWI_lG1p#KugN%=Or za+40saBe936l7O!{GFHC*T?x3B)+>yWYx9BA-?KTyEwBfY^#-?ta>Em>V*C?kbU-7 z&3pPQ)V;D+H)UaCbxPSO`|H{PmwV*}C*IF`Em<}1-BzdVyJ4UGRdYZjLfk8NI2HAD zDoii=%4-7q?1fiE3dOx@os+HH*h|H?@-Gyrr@hbq+GBskA96o`tu!Vttt@PlHL92D z<9fg<&`qO?PCDy+3c~KTKJTS|zN2ftNS1vne*M(OZnw_$R_k4N`4{(5Of33gT;A)Ziu*jmuo|-R*uMHcJt8d5J_9aF;{F74DHCKy)^M9*&WA3dLMgF-1 zRrKw5glE~8@ObKkoXHdK&#JBP6omae(b_ytwdnHoM3GXD@4CLh@*C>M**~>|W%>_a zFTdyH3c_~Ut<=W8>pwS6jSXorZM?|D$vL%We*G0@p*Hrcl{rxjc0F%xo~U-Za;%YA zYO!mfRhFfSTt}^$lhmPMA!GmFv0NYH+|Kz;<7;%BBt?8-r*!Jp!~0k} zc*q>y0so`fYN4y;eVO_Ks$S|aSGc#hbMVV z{HSTYQg;9ghML3?+QebT8AD9F5!cX87;fS_$iIVIj4<&>X`8UUQ@H;~lYh=>z54Mp z$^>#UOnZWM{AlAe@}J{T_nG)%+ULj*&NT5-+5uyXZ;;=Mlg66(F1z*WQ{Qcz9f&cv zoc?Yy@jsoLWmZ^DTS)sD?L69NXxGv{LOX|c6YXDU7wJ~N)1qW10gHAuZ657(+MAQq zx-DUe{$)GW;i#PI*CX|Fr}}Dyemc#~mB`#B;trLHw<5J4lsgto&y&_V|DRRebXDSS zRekKL^iV0>Yj5jc|FFMbbk_LP+Fq(UyDI$0N>!1HsJ;-PQ-$ZPr3Y23E`jfC1-`ex zi>jQAvs`@9KA5R5KE^N_eOgTZay+c4Ml~+o@UerpcOm3zp1#P`1|#m1URg zbt@|E@8Ze`-D~(jM~n2ATyOq^bL6OwmN@$?a@MFe>scvfYI=O&8+-L? zCtauyMna}elXDSs=P#TdCzTQN^x?$G#UUSzYt~?nqZ+ ze0K@z<#PXf0af9OEObqoG9@LY#O2@qnOfxvZJ&{w;ZHcC9u4(ICEGW&ejdh%w9=2q z7(5ln;Z-;VZ^p&F%=-VpCHNE$U}+=LSZ}`y*Ejac$=+rIQ51~mV;qAka5o&)*W~-) z{x}tn#)I(#cm&SJnU1|8j}y3`f&!d_U&YgL0MEha@WZ$kFT&0GnFD?dcf>1fdtD;Q z1bh^v;lE)I-heakW}J(+;6nToehR;iOYkmjuYFCvA@B|bSMh!v-rxL!5AdD%Fz$pu z#R>QeoPsOxJbb>5e&*T-u$;g}3ZBGQ@HTuMzl?9dkMTuU;qx_;rJ%L5C6gGDu_(L zZSiEBh^OIXJR48P-dqCa)i08d-=g3VTvoRNK89D~3)qK$#DB##coQCwsziUh@*IJQ zCLmk!9Q+D?6u*Jj;$8Ufcn^LC@56iX`}m)Z_4WUdz-bBs_zFITui}%qh11&-IfL)P z=W#z=g@@p3ywJAZzg#6?x_2Ts@OoAVX=iTI3fvHz?w-gW@I~^?akA6h6KRJ{_fMn~ z_UOF5e~Blsi4}Td(+?FHfL|i-!M8d6QjyWv^jAg3;TZBccnzM3b-&ixzsx1@cM2Bb zuylQj^v8=$$5&(}ZbE)7UWzwj(?J&5j0?zb#Wm?(vxCL&Z zJ#CRg_%ZUI;Yxf0n;yC7fq}WC*Sr)|5_rJrnu}b)rt7Z9l6XFOX>T@g0!L!gwbwZu zB!4F!<8<})YaV;e7(gVJz*Gvl;*W6>He&^mzW6Kh>G)n}2p}>Nn<0ZpCLT+ElCf7_ zAuyc+GlmeEjdzn@fa^PB2$4sz8B^$Y2)>j2U+^5f4x6!!$WxB>{kNEcEfjo&w_`I1 z5_uDUNq!IR;tYaB4q!7_5;=%_lm7(&6(6^4zyHl>O5_v;n<+StFXPMDjJiaA!oQIJ zH_mWIUn1eL<{mMlGm!>3oBZwC&ij7{fx9R$!#9z(cpv%BxQR1-6G^~km?zQ;$B-Y0 z^YAcihJzv*v32i%3ht-iD1HE&@uJ8>_$>Kc+|wC9iY&oqTq&{?4o~Dj7vp!V2?A-)YGYWJuby&oGMc8CGY|TpW@5-D{Ka? zBB!x(2F7~20*}EzU^9T$Q&e~wdFfz&Z~%Mj6EGuf5jU=&AQ}%JZJw0vuo;o-mkFLk zzB?|*eX$v_>$wzs+~mD7j6j+*gx6D-*bMFUTo4{Zei|;p^RSr$5Lti&fLlQxJIvn>hzPnTONJm*ZmF_WSQM0%lS| zW1V>3~vXTq^Pvu1yCu$hSydC;-G z|IO5$ez{O!ruIbUU^4?JG9Q~MJN=-+W{OYGl4CO^DDr1)X7co(IyO^(`agwi+wXrf zSE!#%6uih0uff&$349Z8#PuhcU$_~^;36E4x8Z(xySDTG_Yl}kK_1?Vi*N~k6@P&D z;tz2J{sdpbN3fo|bS}yHPQ3s7XPZD31*5V2(cFY{aTs2J>*L3<8-I>($6w-Td>Xg< zqrU$3`~O=4?I@_io$zJc1;Ku8qdaS@G|@aUTy4^jRXoP z*oHUa?f4vi6JNk@;UDlj_y*pGf5soTkGXmY5fPVer`|wHp5I%!v;wt$7n7AF|TL>gD%p*V&Eco?VPO?7#;_nzb1 zL~;pyN5K?p`BgQ_o6+6u=VE=cpIo-nv;jX1r{H3adrqBMe;wBUajma_$op9T54Pi< zup}EtH*~gdjk>0`#2+4Kg10p`JI$+mGT~>r+Wl~{ z*Ih1WpG$CEkLz(<*Fjm2>t^G(;=0-RUAS&qZ$GY^xBSTXKB=4gJxW2{^!*uJ*I&4Z Y>-rbJ;JW@rgQn&Oc>JIKqE`3#Kk-G0j{pDw diff --git a/core/src/main/resources/io/questdb/client/bin/linux-aarch64/libquestdb.so b/core/src/main/resources/io/questdb/client/bin/linux-aarch64/libquestdb.so index f3ddceddb3a5f1052b3731ad8d3d5c2431272dad..94ad41c1e3e2e837b3d1e263164d2804894124ff 100644 GIT binary patch delta 2536 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 delta 12587 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 Y62nMN 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 BW6%Hq delta 9336 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 From db7675524dcbbecb11ef5da2a2e874fba98bb1d1 Mon Sep 17 00:00:00 2001 From: Marko Topolnik Date: Mon, 18 May 2026 13:15:45 +0200 Subject: [PATCH 3/5] Relax a timing-critical test --- .../qwp/client/sf/cursor/SegmentManagerTest.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentManagerTest.java b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentManagerTest.java index f5d6a1b1..190bf71a 100644 --- a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentManagerTest.java +++ b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentManagerTest.java @@ -236,13 +236,18 @@ public void testProducerWakeupBeatsThePollInterval() throws Exception { // crosses the threshold easily on this tiny segment.) long t0 = System.nanoTime(); ring.appendOrFsn(buf, 16); // crosses high-water → wakeup → manager creates spare - // 200 ms is generous for an open + truncate + mmap on a - // healthy machine; if we're still waiting, the wakeup didn't - // fire and we're stuck on the 5s poll. + // The discriminating fact is wakeup-vs-poll, not an absolute + // latency: a fired wakeup installs the spare in tens of ms, + // whereas the poll path would take ~5000ms. A 2000ms budget + // stays 2.5x below the poll interval (so a pass still proves + // the wakeup fired) while tolerating cross-thread scheduling + // jitter on a loaded CI agent — open + allocate + mmap plus + // the unpark hop can blow a 200ms budget there even when the + // wakeup did fire. assertTrue("manager must install spare via producer wakeup, not the 5s poll tick", - waitFor(() -> !ring.needsHotSpare(), 200)); + waitFor(() -> !ring.needsHotSpare(), 2_000)); long elapsedMs = (System.nanoTime() - t0) / 1_000_000L; - assertTrue("spare arrived in " + elapsedMs + "ms — should be <<5000ms", elapsedMs < 1000); + assertTrue("spare arrived in " + elapsedMs + "ms — should be <<5000ms", elapsedMs < 4_000); } finally { Unsafe.free(buf, 16, MemoryTag.NATIVE_DEFAULT); } From b3bc1d81248cfe10c2c01f51c66048cfb0b080eb Mon Sep 17 00:00:00 2001 From: Marko Topolnik Date: Mon, 18 May 2026 13:19:07 +0200 Subject: [PATCH 4/5] Relax the rotation-wakeup timing test too testRotationWakeupTriggersImmediateSparePrep used the same tight timing budgets as testProducerWakeupBeatsThePollInterval, which was already relaxed: two 500ms waitFor deadlines plus a 1000ms secondary bound. On a loaded CI agent the producer-to-manager unpark hop plus open + allocate + mmap can exceed 500ms even when the wakeup did fire, making the test flaky for the same reason. Raise both waitFor budgets to 2000ms and the secondary bound to 4000ms. These stay well below the 5s manager poll interval, so a pass still proves the rotation-time wakeup fired rather than the poll tick, while tolerating cross-thread scheduling jitter. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../cutlass/qwp/client/sf/cursor/SegmentManagerTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentManagerTest.java b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentManagerTest.java index 190bf71a..5474c1be 100644 --- a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentManagerTest.java +++ b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentManagerTest.java @@ -269,8 +269,11 @@ public void testRotationWakeupTriggersImmediateSparePrep() throws Exception { mgr.start(); mgr.register(ring, tmpDir); // First spare via high-water signal on the very first append. + // 2000ms budget: same wakeup-vs-poll rationale as + // testProducerWakeupBeatsThePollInterval -- still 2.5x below + // the 5s poll, but tolerant of CI scheduling jitter. ring.appendOrFsn(buf, 16); - assertTrue(waitFor(() -> !ring.needsHotSpare(), 500)); + assertTrue(waitFor(() -> !ring.needsHotSpare(), 2_000)); // Now active is full → next append rotates → consumes the spare → // hotSpare goes back to null → rotation-time wakeup runs → // manager promptly provisions the next spare. @@ -278,10 +281,10 @@ public void testRotationWakeupTriggersImmediateSparePrep() throws Exception { long fsn = ring.appendOrFsn(buf, 16); assertEquals(1, fsn); assertTrue("rotation-time wakeup must trigger spare 2 well before 5s poll", - waitFor(() -> !ring.needsHotSpare(), 500)); + waitFor(() -> !ring.needsHotSpare(), 2_000)); long elapsedMs = (System.nanoTime() - beforeRotate) / 1_000_000L; assertTrue("spare 2 arrived in " + elapsedMs + "ms — should be <<5000ms", - elapsedMs < 1000); + elapsedMs < 4_000); } finally { Unsafe.free(buf, 16, MemoryTag.NATIVE_DEFAULT); } From dcc84969295e5628e06036eb61b67edc9ea8e04a Mon Sep 17 00:00:00 2001 From: Vlad Ilyushchenko Date: Tue, 19 May 2026 13:46:42 +0100 Subject: [PATCH 5/5] Unlink partial watermark on allocate failure AckWatermark.open() closed the fd but left the truncated file on disk when Files.allocate() returned false. The FilesFacade.allocate javadoc explicitly states callers must close the fd AND unlink the partial file -- MmapSegment.create already follows that contract, AckWatermark did not. Self-healing on the next session via the size-mismatch branch masked the leak in practice (FILE_SIZE is 16 bytes), but the contract is documented in this same change set and the second caller should honour it consistently. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../client/cutlass/qwp/client/sf/cursor/AckWatermark.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/AckWatermark.java b/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/AckWatermark.java index efc75edc..469c91ea 100644 --- a/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/AckWatermark.java +++ b/core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/AckWatermark.java @@ -150,7 +150,10 @@ public static AckWatermark open(String slotDir) { } else { fd = Files.openCleanRW(filePath); if (fd >= 0 && !Files.allocate(fd, FILE_SIZE)) { + // FilesFacade.allocate contract on a false return: + // close the fd AND unlink the partial file. Files.close(fd); + Files.remove(filePath); fd = -1; } }