Skip to content

Commit b67d4f4

Browse files
committed
test(qwp): deflake close-lifecycle interrupt tests on saturated CI agents
facadeCloseIsBoundedUnderRepeatedInterruptsDuring{Query,Sender}Creation raced the 100ms close budget against thread scheduling: the post-join assert needs the interrupter to land at least two interrupts inside the budget window, but the budget clock starts before the interrupter thread even exists, and a freshly started, yielding thread is not guaranteed two scheduler quanta within 100ms on a saturated agent. On a hosted 3-core mac agent (questdb macwin build 254645) the count read <= 1 and the positive control failed while the product honored its bounded deadline exactly. - widen the creation-wait budget to 1s in the two interrupt tests - hold the test until two interrupts have demonstrably landed while the closer is still inside its bounded wait (awaitRepeatedInterrupts), so the deadline-restart property is exercised on every run and a starved interrupter is reported as such instead of as a count-assert failure Verified: whole class green 3/3 on an idle machine; both interrupt tests green 3/3 with 3 visible cores under full external CPU load. Test-only change.
1 parent 5bbdfb8 commit b67d4f4

1 file changed

Lines changed: 42 additions & 2 deletions

File tree

core/src/test/java/io/questdb/client/test/impl/QuestDBImplCloseLifecycleTest.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.concurrent.atomic.AtomicBoolean;
4444
import java.util.concurrent.atomic.AtomicInteger;
4545
import java.util.concurrent.atomic.AtomicReference;
46+
import java.util.function.BooleanSupplier;
4647
import java.util.function.Consumer;
4748
import java.util.function.IntFunction;
4849

@@ -185,8 +186,13 @@ public void facadeCloseIsBoundedUnderRepeatedInterruptsDuringQueryCreation() thr
185186
inCreation.countDown();
186187
awaitOrFail(releaseCreation, "test never released query creation");
187188
};
189+
// A 1s creation-wait budget, not 100ms: the interrupt storm below must land at least
190+
// twice inside this window for the deadline-restart property to be exercised at all,
191+
// and a freshly started, yielding interrupter thread is not guaranteed two scheduler
192+
// quanta within 100ms on a saturated CI agent (observed on hosted 3-core mac agents,
193+
// where the post-join count assert failed with the product deadline honored exactly).
188194
QuestDBImpl db = newQuestDB(
189-
SENDER_CFG, 0, 0, 100, slotIndex -> fakeSender(null, null, null), connectHook);
195+
SENDER_CFG, 0, 0, 1000, slotIndex -> fakeSender(null, null, null), connectHook);
190196
QueryClientPool pool = db.getQueryPoolForTesting();
191197
AtomicReference<Throwable> borrowOutcome = new AtomicReference<>();
192198
AtomicBoolean closeReturnedInterrupted = new AtomicBoolean();
@@ -221,6 +227,8 @@ public void facadeCloseIsBoundedUnderRepeatedInterruptsDuringQueryCreation() thr
221227
awaitCreationWaiter(pool,
222228
"facade close did not wait while query construction was internally owned");
223229
interrupter.start();
230+
awaitRepeatedInterrupts(interruptCount, pool::hasCreationWaiterForTesting,
231+
"query close left its creation wait before the interrupt storm landed twice");
224232
closer.join(TimeUnit.SECONDS.toMillis(5));
225233
Assert.assertFalse(
226234
"repeated interrupts restarted the query creation-wait deadline",
@@ -267,7 +275,9 @@ public void facadeCloseIsBoundedUnderRepeatedInterruptsDuringSenderCreation() th
267275
};
268276
String senderConfig = "ws::addr=localhost:1;sf_dir="
269277
+ System.getProperty("java.io.tmpdir") + "/qdb-interrupted-pool-" + System.nanoTime() + ";";
270-
QuestDBImpl db = newQuestDB(senderConfig, 0, 0, 100, senderFactory, client -> {
278+
// 1s creation-wait budget for the same reason as the query-interrupt test above: the
279+
// interrupt storm must land at least twice inside the window even on a saturated agent.
280+
QuestDBImpl db = newQuestDB(senderConfig, 0, 0, 1000, senderFactory, client -> {
271281
});
272282
SenderPool pool = db.getSenderPoolForTesting();
273283
AtomicReference<Throwable> borrowOutcome = new AtomicReference<>();
@@ -304,6 +314,8 @@ public void facadeCloseIsBoundedUnderRepeatedInterruptsDuringSenderCreation() th
304314
awaitCreationWaiter(pool,
305315
"facade close did not wait while sender construction was internally owned");
306316
interrupter.start();
317+
awaitRepeatedInterrupts(interruptCount, pool::hasCreationWaiterForTesting,
318+
"sender close left its creation wait before the interrupt storm landed twice");
307319
closer.join(TimeUnit.SECONDS.toMillis(5));
308320
Assert.assertFalse(
309321
"repeated interrupts restarted the sender creation-wait deadline",
@@ -517,6 +529,34 @@ private static void awaitCreationWaiter(SenderPool pool, String message) {
517529
Assert.fail(message);
518530
}
519531

532+
/**
533+
* Holds the test until the interrupt storm has landed at least twice while the facade close is
534+
* still inside its bounded creation wait. The deadline-restart property is only exercised by
535+
* interrupts that arrive during that wait, and the scheduler owes the interrupter thread
536+
* nothing: with a post-join count assert alone, the run races the close budget against thread
537+
* scheduling and can fail with the product invariant intact. Failing here instead separates
538+
* "interrupter starved before the budget expired" from a genuine deadline bug.
539+
*/
540+
private static void awaitRepeatedInterrupts(
541+
AtomicInteger interruptCount,
542+
BooleanSupplier closerStillWaiting,
543+
String message
544+
) {
545+
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
546+
while (System.nanoTime() < deadline) {
547+
// Count first: two interrupts observed while polling means the storm landed no matter
548+
// how quickly the wait ends afterwards, so a budget expiry seen next is not a failure.
549+
if (interruptCount.get() > 1) {
550+
return;
551+
}
552+
if (!closerStillWaiting.getAsBoolean()) {
553+
Assert.fail(message + "; interrupts landed: " + interruptCount.get());
554+
}
555+
Thread.yield();
556+
}
557+
Assert.fail(message + "; interrupts landed: " + interruptCount.get());
558+
}
559+
520560
private static void awaitOrFail(CountDownLatch latch, String message) {
521561
try {
522562
if (!latch.await(10, TimeUnit.SECONDS)) {

0 commit comments

Comments
 (0)