From 751637bb9447550d0d55c0cd51a841df4e35ac37 Mon Sep 17 00:00:00 2001 From: Vlad Ilyushchenko Date: Tue, 11 Aug 2026 10:01:16 +0100 Subject: [PATCH] Fix timing race in error dispatcher overflow test testFullInboxDropsOldestAndCounts slept a fixed 50 ms after the first offer, assuming the lazily-started dispatcher thread had taken error 0 out of the capacity-4 inbox by then. On a loaded Windows CI agent (questdb/questdb macwin build 260325) the thread started late, the inbox still held error 0 when the fill began, and offers 4-6 each evicted the head: getDroppedNotifications() returned 3 and the test failed with expected:<2> but was:<3>. The handler now counts down a handlerEntered latch on entry, and the test awaits the latch instead of sleeping. The dispatcher thread only invokes the handler after it removes the head from the inbox, so the latch orders "error 0 taken" before the fill: the queue provably starts empty and exactly two overflow drops follow on any scheduler. The delivered-tail assertion depends on the same handoff, so the latch deflakes both assertions. --- .../sf/cursor/SenderErrorDispatcherTest.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SenderErrorDispatcherTest.java b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SenderErrorDispatcherTest.java index 957e36d4e..0def4ef12 100644 --- a/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SenderErrorDispatcherTest.java +++ b/core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SenderErrorDispatcherTest.java @@ -102,10 +102,12 @@ public void testFullInboxDropsOldestAndCounts() throws Exception { // and admit the new one. The latest entry is always the most // informative, so the FIFO head loses, not the new arrival. CountDownLatch unblock = new CountDownLatch(1); + CountDownLatch handlerEntered = new CountDownLatch(1); List received = new ArrayList<>(); Object lock = new Object(); CountDownLatch allDelivered = new CountDownLatch(5); try (SenderErrorDispatcher d = new SenderErrorDispatcher(err -> { + handlerEntered.countDown(); try { unblock.await(); } catch (InterruptedException ignored) { @@ -116,13 +118,15 @@ public void testFullInboxDropsOldestAndCounts() throws Exception { } allDelivered.countDown(); }, /*capacity=*/ 4)) { - // First offer starts the dispatcher and lands in the handler - // immediately (and blocks there). Now we can fill the bounded - // inbox to capacity, then overflow. + // The first offer lazily starts the dispatcher thread, which + // takes the head into the handler and blocks there. Wait for + // that handoff: once the handler has entered, error 0 is out + // of the inbox, so the fill below starts from an empty queue. + // A fixed sleep here raced the lazy thread start on loaded CI + // agents and produced a third drop (expected 2, got 3). Assert.assertTrue(d.offer(buildError(0))); - // Give the dispatcher a moment to take the head into the - // handler so subsequent offers don't get an extra slot. - TimeUnit.MILLISECONDS.sleep(50); + Assert.assertTrue("handler should take the head within 5s", + handlerEntered.await(5, TimeUnit.SECONDS)); for (int i = 1; i <= 4; i++) { Assert.assertTrue("inbox should accept offer " + i, d.offer(buildError(i)));