Skip to content

Commit 9e298e7

Browse files
committed
test fix
1 parent 16a3eb6 commit 9e298e7

2 files changed

Lines changed: 45 additions & 34 deletions

File tree

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,16 @@ private void dispatchLoop() {
215215
// Closed-check at the loop head will catch the rest.
216216
continue;
217217
}
218+
// Increment before invoking the handler: observers using a
219+
// CountDownLatch in the handler must be able to read the
220+
// updated counter once their latch fires. With the increment
221+
// after, the handler-released observer races the dispatcher
222+
// and can see totalDelivered short by one.
223+
totalDelivered.incrementAndGet();
218224
try {
219225
handler.onError(err);
220226
} catch (Throwable t) {
221227
LOG.error("SenderErrorHandler threw on {}: {}", err, t.getMessage(), t);
222-
} finally {
223-
totalDelivered.incrementAndGet();
224228
}
225229
}
226230
}

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

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import java.io.IOException;
3333
import java.nio.ByteBuffer;
3434
import java.nio.ByteOrder;
35-
import java.util.concurrent.TimeUnit;
3635
import java.util.concurrent.atomic.AtomicLong;
3736

3837
/**
@@ -45,22 +44,30 @@ public class InitialConnectRetryTest {
4544
private static final int TEST_PORT = 19_700 + (int) (System.nanoTime() % 100);
4645

4746
@Test
48-
public void testWithoutRetryFailsImmediately() {
49-
// No server on this port. With initial_connect_retry off (default),
50-
// fromConfig must throw without sitting around for the cap.
51-
int port = TEST_PORT + 1;
52-
long t0 = System.nanoTime();
53-
try (Sender ignored = Sender.fromConfig("ws::addr=localhost:" + port + ";")) {
54-
Assert.fail("expected immediate connect failure");
47+
public void testWithRetryGivesUpAfterCap() {
48+
// No server. With retry on, fromConfig must run the retry loop and
49+
// ultimately throw with the connectWithRetry-shaped message that
50+
// names the elapsed budget and attempt count. The actual budget
51+
// honoring is observable through that message — we don't need a
52+
// wall-clock check.
53+
int port = TEST_PORT + 3;
54+
String cfg = "ws::addr=127.0.0.1:" + port
55+
+ ";initial_connect_retry=true"
56+
+ ";reconnect_max_duration_millis=400"
57+
+ ";reconnect_initial_backoff_millis=10"
58+
+ ";reconnect_max_backoff_millis=50;";
59+
try (Sender ignored = Sender.fromConfig(cfg)) {
60+
Assert.fail("expected give-up after cap");
5561
} catch (Exception expected) {
56-
long elapsedMs = (System.nanoTime() - t0) / 1_000_000L;
57-
Assert.assertTrue("must fail fast (took " + elapsedMs + " ms)",
58-
elapsedMs < 2_000L);
62+
String msg = expected.getMessage();
63+
Assert.assertNotNull("error must have a message", msg);
64+
Assert.assertTrue("error must come from the retry loop: " + msg,
65+
msg.contains("initial connect") && msg.contains("attempts"));
5966
}
6067
}
6168

6269
@Test
63-
public void testWithRetrySucceedsWhenServerComesUpInTime() throws Exception {
70+
public void testWithRetrySucceedsWhenServerComesUpInTime() {
6471
// initial_connect_retry=true; we open the sender BEFORE starting
6572
// the server, then start the server in a background thread after
6673
// a short delay. The retry loop should see the server come up and
@@ -79,7 +86,7 @@ public void testWithRetrySucceedsWhenServerComesUpInTime() throws Exception {
7986
starter.setDaemon(true);
8087
starter.start();
8188
try {
82-
String cfg = "ws::addr=localhost:" + port
89+
String cfg = "ws::addr=127.0.0.1:" + port
8390
+ ";initial_connect_retry=true"
8491
+ ";reconnect_max_duration_millis=5000"
8592
+ ";reconnect_initial_backoff_millis=50"
@@ -99,30 +106,30 @@ public void testWithRetrySucceedsWhenServerComesUpInTime() throws Exception {
99106
}
100107

101108
@Test
102-
public void testWithRetryGivesUpAfterCap() {
103-
// No server. With retry on but a tight cap, fromConfig should
104-
// throw within the cap window (with some slack).
105-
int port = TEST_PORT + 3;
106-
long t0 = System.nanoTime();
107-
String cfg = "ws::addr=localhost:" + port
108-
+ ";initial_connect_retry=true"
109-
+ ";reconnect_max_duration_millis=400"
110-
+ ";reconnect_initial_backoff_millis=10"
111-
+ ";reconnect_max_backoff_millis=50;";
112-
try (Sender ignored = Sender.fromConfig(cfg)) {
113-
Assert.fail("expected give-up after cap");
109+
public void testWithoutRetryFailsImmediately() {
110+
// No server on this port. With initial_connect_retry off (default),
111+
// fromConfig must throw on the first connect failure rather than enter
112+
// the retry loop. We assert the structural shape of the error: the
113+
// raw "Failed to connect" message from buildAndConnect, NOT the
114+
// "initial connect ... attempts" message connectWithRetry produces.
115+
int port = TEST_PORT + 1;
116+
// Use the IPv4 literal so the test doesn't pay first-call
117+
// getaddrinfo("localhost") cost on Windows (1-2 s cold lookup).
118+
try (Sender ignored = Sender.fromConfig("ws::addr=127.0.0.1:" + port + ";")) {
119+
Assert.fail("expected immediate connect failure");
114120
} catch (Exception expected) {
115-
long elapsedMs = (System.nanoTime() - t0) / 1_000_000L;
116-
Assert.assertTrue("must give up around the cap (took " + elapsedMs + " ms)",
117-
elapsedMs >= 300L && elapsedMs < 3_000L);
118121
String msg = expected.getMessage();
119-
Assert.assertTrue("error must mention startup retry: " + msg,
120-
msg != null && (msg.contains("initial connect")
121-
|| msg.contains("Failed to connect")));
122+
Assert.assertNotNull("error must have a message", msg);
123+
Assert.assertTrue("error must be the raw connect-refused: " + msg,
124+
msg.contains("Failed to connect"));
125+
Assert.assertFalse("error must NOT mention the retry loop: " + msg,
126+
msg.contains("attempts"));
122127
}
123128
}
124129

125-
/** Acks every binary frame so the sender's flush completes. */
130+
/**
131+
* Acks every binary frame so the sender's flush completes.
132+
*/
126133
private static class AckHandler implements TestWebSocketServer.WebSocketServerHandler {
127134
private final AtomicLong nextSeq = new AtomicLong(0);
128135

0 commit comments

Comments
 (0)