|
43 | 43 | import java.util.concurrent.atomic.AtomicBoolean; |
44 | 44 | import java.util.concurrent.atomic.AtomicInteger; |
45 | 45 | import java.util.concurrent.atomic.AtomicReference; |
| 46 | +import java.util.function.BooleanSupplier; |
46 | 47 | import java.util.function.Consumer; |
47 | 48 | import java.util.function.IntFunction; |
48 | 49 |
|
@@ -185,8 +186,13 @@ public void facadeCloseIsBoundedUnderRepeatedInterruptsDuringQueryCreation() thr |
185 | 186 | inCreation.countDown(); |
186 | 187 | awaitOrFail(releaseCreation, "test never released query creation"); |
187 | 188 | }; |
| 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). |
188 | 194 | 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); |
190 | 196 | QueryClientPool pool = db.getQueryPoolForTesting(); |
191 | 197 | AtomicReference<Throwable> borrowOutcome = new AtomicReference<>(); |
192 | 198 | AtomicBoolean closeReturnedInterrupted = new AtomicBoolean(); |
@@ -221,6 +227,8 @@ public void facadeCloseIsBoundedUnderRepeatedInterruptsDuringQueryCreation() thr |
221 | 227 | awaitCreationWaiter(pool, |
222 | 228 | "facade close did not wait while query construction was internally owned"); |
223 | 229 | interrupter.start(); |
| 230 | + awaitRepeatedInterrupts(interruptCount, pool::hasCreationWaiterForTesting, |
| 231 | + "query close left its creation wait before the interrupt storm landed twice"); |
224 | 232 | closer.join(TimeUnit.SECONDS.toMillis(5)); |
225 | 233 | Assert.assertFalse( |
226 | 234 | "repeated interrupts restarted the query creation-wait deadline", |
@@ -267,7 +275,9 @@ public void facadeCloseIsBoundedUnderRepeatedInterruptsDuringSenderCreation() th |
267 | 275 | }; |
268 | 276 | String senderConfig = "ws::addr=localhost:1;sf_dir=" |
269 | 277 | + 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 -> { |
271 | 281 | }); |
272 | 282 | SenderPool pool = db.getSenderPoolForTesting(); |
273 | 283 | AtomicReference<Throwable> borrowOutcome = new AtomicReference<>(); |
@@ -304,6 +314,8 @@ public void facadeCloseIsBoundedUnderRepeatedInterruptsDuringSenderCreation() th |
304 | 314 | awaitCreationWaiter(pool, |
305 | 315 | "facade close did not wait while sender construction was internally owned"); |
306 | 316 | interrupter.start(); |
| 317 | + awaitRepeatedInterrupts(interruptCount, pool::hasCreationWaiterForTesting, |
| 318 | + "sender close left its creation wait before the interrupt storm landed twice"); |
307 | 319 | closer.join(TimeUnit.SECONDS.toMillis(5)); |
308 | 320 | Assert.assertFalse( |
309 | 321 | "repeated interrupts restarted the sender creation-wait deadline", |
@@ -517,6 +529,34 @@ private static void awaitCreationWaiter(SenderPool pool, String message) { |
517 | 529 | Assert.fail(message); |
518 | 530 | } |
519 | 531 |
|
| 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 | + |
520 | 560 | private static void awaitOrFail(CountDownLatch latch, String message) { |
521 | 561 | try { |
522 | 562 | if (!latch.await(10, TimeUnit.SECONDS)) { |
|
0 commit comments