Skip to content

Commit 4beb771

Browse files
committedOct 10, 2024
8339538: Wrong timeout computations in DnsClient
8220213: com/sun/jndi/dns/ConfigTests/Timeout.java failed intermittent Reviewed-by: dfuchs, msheppar, djelinski
1 parent a7d2077 commit 4beb771

File tree

4 files changed

+324
-52
lines changed

4 files changed

+324
-52
lines changed
 

‎src/jdk.naming.dns/share/classes/com/sun/jndi/dns/DnsClient.java

+99-33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -46,11 +46,15 @@
4646
import javax.naming.OperationNotSupportedException;
4747
import javax.naming.ServiceUnavailableException;
4848

49+
import java.time.Duration;
4950
import java.util.Arrays;
5051
import java.util.Collections;
5152
import java.util.Map;
5253
import java.util.HashMap;
54+
import java.util.concurrent.TimeUnit;
55+
import java.util.concurrent.atomic.AtomicLong;
5356
import java.util.concurrent.locks.ReentrantLock;
57+
import java.util.stream.IntStream;
5458

5559
import jdk.internal.ref.CleanerFactory;
5660
import sun.security.jca.JCAUtil;
@@ -95,7 +99,7 @@ public class DnsClient {
9599

96100
private static final int DEFAULT_PORT = 53;
97101
private static final int TRANSACTION_ID_BOUND = 0x10000;
98-
private static final int MIN_TIMEOUT = 50; // msec after which there are no retries.
102+
private static final int MIN_TIMEOUT = 0; // msec after which there are no retries.
99103
private static final SecureRandom random = JCAUtil.getSecureRandom();
100104
private InetAddress[] servers;
101105
private int[] serverPorts;
@@ -223,20 +227,28 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
223227

224228
Exception caughtException = null;
225229
boolean[] doNotRetry = new boolean[servers.length];
226-
230+
// Holder for unfulfilled timeouts left for each server
231+
AtomicLong[] unfulfilledUdpTimeouts = IntStream.range(0, servers.length)
232+
.mapToObj(_ -> new AtomicLong())
233+
.toArray(AtomicLong[]::new);
227234
try {
228235
//
229236
// The UDP retry strategy is to try the 1st server, and then
230237
// each server in order. If no answer, double the timeout
231238
// and try each server again.
232239
//
233-
for (int retry = 0; retry < retries; retry++) {
234-
240+
for (int retry = 0; retry <= retries; retry++) {
241+
boolean isLastRetry = retry == retries;
235242
// Try each name server.
236243
for (int i = 0; i < servers.length; i++) {
237244
if (doNotRetry[i]) {
238245
continue;
239246
}
247+
// unfulfilledServerTimeout is always >= 0
248+
AtomicLong unfulfilledServerTimeout = unfulfilledUdpTimeouts[i];
249+
if (isLastRetry && unfulfilledServerTimeout.get() == 0) {
250+
continue;
251+
}
240252

241253
// send the request packet and wait for a response.
242254
try {
@@ -245,7 +257,7 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
245257
}
246258

247259
byte[] msg = doUdpQuery(pkt, servers[i], serverPorts[i],
248-
retry, xid);
260+
retry, xid, unfulfilledServerTimeout, isLastRetry);
249261
assert msg != null;
250262
Header hdr = new Header(msg, msg.length);
251263

@@ -259,7 +271,12 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
259271

260272
// Try each server, starting with the one that just
261273
// provided the truncated message.
262-
int retryTimeout = (timeout * (1 << retry));
274+
long retryTimeout = Math.clamp(
275+
timeout * (1L << (isLastRetry
276+
? retry - 1
277+
: retry)),
278+
0L, Integer.MAX_VALUE);
279+
;
263280
for (int j = 0; j < servers.length; j++) {
264281
int ij = (i + j) % servers.length;
265282
if (doNotRetry[ij]) {
@@ -301,15 +318,23 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
301318
if (debug) {
302319
dprint("Caught Exception:" + ex);
303320
}
304-
if (caughtException == null) {
321+
if (caughtException == null || servers.length == 1) {
322+
// If there are several servers we continue trying with other
323+
// servers, otherwise this exception will be reported
305324
caughtException = ex;
325+
} else {
326+
// Best reporting effort
327+
caughtException.addSuppressed(ex);
306328
}
307329
doNotRetry[i] = true;
308330
} catch (IOException e) {
309331
if (debug) {
310332
dprint("Caught IOException:" + e);
311333
}
312-
if (caughtException == null) {
334+
if (caughtException instanceof CommunicationException ce) {
335+
e.addSuppressed(ce);
336+
caughtException = e;
337+
} else if (caughtException == null) {
313338
caughtException = e;
314339
}
315340
} catch (ClosedSelectorException e) {
@@ -327,8 +352,13 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
327352
caughtException = e;
328353
}
329354
} catch (NamingException e) {
330-
if (caughtException == null) {
355+
if (caughtException == null || servers.length == 1) {
356+
// If there are several servers we continue trying with other
357+
// servers, otherwise this exception will be reported
331358
caughtException = e;
359+
} else {
360+
// Best reporting effort
361+
caughtException.addSuppressed(e);
332362
}
333363
doNotRetry[i] = true;
334364
}
@@ -339,8 +369,8 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
339369
reqs.remove(xid); // cleanup
340370
}
341371

342-
if (caughtException instanceof NamingException) {
343-
throw (NamingException) caughtException;
372+
if (caughtException instanceof NamingException ne) {
373+
throw ne;
344374
}
345375
// A network timeout or other error occurred.
346376
NamingException ne = new CommunicationException("DNS error");
@@ -424,10 +454,32 @@ ResourceRecords queryZone(DnsName zone, int qclass, boolean recursion)
424454
* is enqueued with the corresponding xid in 'resps'.
425455
*/
426456
private byte[] doUdpQuery(Packet pkt, InetAddress server,
427-
int port, int retry, int xid)
457+
int port, int retry, int xid,
458+
AtomicLong unfulfilledTimeout,
459+
boolean unfulfilledOnly)
428460
throws IOException, NamingException {
429461

430462
udpChannelLock.lock();
463+
464+
465+
// use 1L below to ensure conversion to long and avoid potential
466+
// integer overflow (timeout is an int).
467+
// no point in supporting timeout > Integer.MAX_VALUE, clamp if needed
468+
// timeout remaining after successive 'blockingReceive()'.
469+
long thisIterationTimeout = unfulfilledOnly
470+
? 0L
471+
: Math.clamp(timeout * (1L << retry), 0L, Integer.MAX_VALUE);
472+
473+
// Compensate with server's positive unfulfilled timeout.
474+
// Calling method never supplies zero 'unfulfilledTimeout' when
475+
// 'unfulfilledOnly' is 'true', therefore 'thisIterationTimeout'
476+
// will always be a positive number, ie infinite timeout
477+
// is not possible.
478+
thisIterationTimeout += unfulfilledTimeout.get();
479+
480+
// Track left timeout for the current retry
481+
long timeoutLeft = thisIterationTimeout;
482+
long start = 0;
431483
try {
432484
try (DatagramChannel udpChannel = getDatagramChannel()) {
433485
ByteBuffer opkt = ByteBuffer.wrap(pkt.getData(), 0, pkt.length());
@@ -436,13 +488,11 @@ private byte[] doUdpQuery(Packet pkt, InetAddress server,
436488
// Packets may only be sent to or received from this server address
437489
InetSocketAddress target = new InetSocketAddress(server, port);
438490
udpChannel.connect(target);
439-
int pktTimeout = (timeout * (1 << retry));
440491
udpChannel.write(opkt);
441492

442-
// timeout remaining after successive 'blockingReceive()'
443-
int timeoutLeft = pktTimeout;
444493
int cnt = 0;
445494
boolean gotData = false;
495+
start = System.nanoTime();
446496
do {
447497
// prepare for retry
448498
if (gotData) {
@@ -456,9 +506,7 @@ private byte[] doUdpQuery(Packet pkt, InetAddress server,
456506
") for:" + xid + " sock-timeout:" +
457507
timeoutLeft + " ms.");
458508
}
459-
long start = System.currentTimeMillis();
460-
gotData = blockingReceive(udpChannel, ipkt, timeoutLeft);
461-
long end = System.currentTimeMillis();
509+
gotData = blockingReceive(udpChannel, target, ipkt, timeoutLeft);
462510
assert gotData || ipkt.position() == 0;
463511
if (gotData && isMatchResponse(data, xid)) {
464512
return data;
@@ -471,17 +519,23 @@ private byte[] doUdpQuery(Packet pkt, InetAddress server,
471519
return cachedMsg;
472520
}
473521
}
474-
timeoutLeft = pktTimeout - ((int) (end - start));
522+
long elapsedMillis = TimeUnit.NANOSECONDS
523+
.toMillis(System.nanoTime() - start);
524+
timeoutLeft = thisIterationTimeout - elapsedMillis;
475525
} while (timeoutLeft > MIN_TIMEOUT);
476526
// no matching packets received within the timeout
477527
throw new SocketTimeoutException();
478528
}
479529
} finally {
530+
long carryoverTimeout = thisIterationTimeout -
531+
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
532+
unfulfilledTimeout.set(Math.max(0, carryoverTimeout));
480533
udpChannelLock.unlock();
481534
}
482535
}
483536

484-
boolean blockingReceive(DatagramChannel dc, ByteBuffer buffer, long timeout) throws IOException {
537+
boolean blockingReceive(DatagramChannel dc, InetSocketAddress target,
538+
ByteBuffer buffer, long timeout) throws IOException {
485539
boolean dataReceived = false;
486540
// The provided datagram channel will be used by the caller only to receive data after
487541
// it is put to non-blocking mode
@@ -491,10 +545,15 @@ boolean blockingReceive(DatagramChannel dc, ByteBuffer buffer, long timeout) thr
491545
udpChannelSelector.select(timeout);
492546
var keys = udpChannelSelector.selectedKeys();
493547
if (keys.contains(selectionKey) && selectionKey.isReadable()) {
494-
dc.receive(buffer);
495-
dataReceived = true;
548+
int before = buffer.position();
549+
var senderAddress = dc.receive(buffer);
550+
// Empty packets are ignored
551+
dataReceived = target.equals(senderAddress) && buffer.position() > before;
552+
}
553+
// Avoid contention with Selector.close() if called by a clean-up thread
554+
synchronized (keys) {
555+
keys.clear();
496556
}
497-
keys.clear();
498557
} finally {
499558
selectionKey.cancel();
500559
// Flush the canceled key out of the selected key set
@@ -750,14 +809,19 @@ class Tcp {
750809
private final Socket sock;
751810
private final java.io.InputStream in;
752811
final java.io.OutputStream out;
753-
private int timeoutLeft;
812+
private long timeoutLeft;
754813

755-
Tcp(InetAddress server, int port, int timeout) throws IOException {
814+
Tcp(InetAddress server, int port, long timeout) throws IOException {
756815
sock = new Socket();
757816
try {
758-
long start = System.currentTimeMillis();
759-
sock.connect(new InetSocketAddress(server, port), timeout);
760-
timeoutLeft = (int) (timeout - (System.currentTimeMillis() - start));
817+
long start = System.nanoTime();
818+
// It is safe to cast to int since the value is
819+
// clamped by the caller
820+
int intTimeout = (int) timeout;
821+
sock.connect(new InetSocketAddress(server, port), intTimeout);
822+
timeoutLeft = Duration.ofMillis(timeout)
823+
.minus(Duration.ofNanos((System.nanoTime() - start)))
824+
.toMillis();
761825
if (timeoutLeft <= 0)
762826
throw new SocketTimeoutException();
763827

@@ -785,14 +849,16 @@ private interface SocketReadOp {
785849
private int readWithTimeout(SocketReadOp reader) throws IOException {
786850
if (timeoutLeft <= 0)
787851
throw new SocketTimeoutException();
788-
789-
sock.setSoTimeout(timeoutLeft);
790-
long start = System.currentTimeMillis();
852+
// It is safe to cast to int since the value is clamped
853+
int intTimeout = (int) timeoutLeft;
854+
sock.setSoTimeout(intTimeout);
855+
long start = System.nanoTime();
791856
try {
792857
return reader.read();
793858
}
794859
finally {
795-
timeoutLeft -= (int) (System.currentTimeMillis() - start);
860+
timeoutLeft -= TimeUnit.NANOSECONDS.toMillis(
861+
System.nanoTime() - start);
796862
}
797863
}
798864

‎src/jdk.naming.dns/share/classes/com/sun/jndi/dns/DnsContext.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,13 @@ public Object addToEnvironment(String propName, Object propVal)
176176
} else if (propName.equals(INIT_TIMEOUT)) {
177177
int val = Integer.parseInt((String) propVal);
178178
if (timeout != val) {
179-
timeout = val;
179+
timeout = Math.max(val, 0);
180180
resolver = null;
181181
}
182182
} else if (propName.equals(RETRIES)) {
183183
int val = Integer.parseInt((String) propVal);
184184
if (retries != val) {
185-
retries = val;
185+
retries = Math.clamp(val, 1, 30);
186186
resolver = null;
187187
}
188188
}
@@ -257,11 +257,11 @@ private void initFromEnvironment()
257257
val = (String) environment.get(INIT_TIMEOUT);
258258
timeout = (val == null)
259259
? DEFAULT_INIT_TIMEOUT
260-
: Integer.parseInt(val);
260+
: Math.max(Integer.parseInt(val), 0);
261261
val = (String) environment.get(RETRIES);
262262
retries = (val == null)
263263
? DEFAULT_RETRIES
264-
: Integer.parseInt(val);
264+
: Math.clamp(Integer.parseInt(val), 1, 30);
265265
}
266266

267267
private CT getLookupCT(String attrId)

‎test/jdk/com/sun/jndi/dns/ConfigTests/Timeout.java

+25-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -29,7 +29,6 @@
2929
import java.net.InetSocketAddress;
3030
import java.net.SocketTimeoutException;
3131
import java.time.Duration;
32-
import java.time.Instant;
3332

3433
import jdk.test.lib.net.URIBuilder;
3534

@@ -40,16 +39,20 @@
4039
* number of retries.
4140
* @library ../lib/ /test/lib
4241
* @modules java.base/sun.security.util
43-
* @run main Timeout
42+
* @run main/othervm Timeout
4443
*/
4544

4645
public class Timeout extends DNSTestBase {
4746
// initial timeout = 1/4 sec
4847
private static final int TIMEOUT = 250;
4948
// try 5 times per server
5049
private static final int RETRIES = 5;
50+
// DnsClient retries again with increased timeout if left
51+
// timeout is less than this value, and max retry attempts
52+
// is not reached
53+
private static final int DNS_CLIENT_MIN_TIMEOUT = 0;
5154

52-
private Instant startTime;
55+
private long startTime;
5356

5457
public Timeout() {
5558
setLocalServer(false);
@@ -81,7 +84,7 @@ public void runTest() throws Exception {
8184
setContext(new InitialDirContext(env()));
8285

8386
// Any request should fail after timeouts have expired.
84-
startTime = Instant.now();
87+
startTime = System.nanoTime();
8588
context().getAttributes("");
8689

8790
throw new RuntimeException(
@@ -92,28 +95,35 @@ public void runTest() throws Exception {
9295
@Override
9396
public boolean handleException(Exception e) {
9497
if (e instanceof CommunicationException) {
95-
Duration elapsedTime = Duration.between(startTime, Instant.now());
98+
Duration elapsedTime = Duration.ofNanos(System.nanoTime() - startTime);
9699
if (!(((CommunicationException) e)
97100
.getRootCause() instanceof SocketTimeoutException)) {
98101
return false;
99102
}
100103

101-
Duration expectedTime = Duration.ofMillis(TIMEOUT)
102-
.multipliedBy((1 << RETRIES) - 1);
104+
Duration minAllowedTime = Duration.ofMillis(TIMEOUT)
105+
.multipliedBy((1 << RETRIES) - 1)
106+
.minus(Duration.ofMillis(DNS_CLIENT_MIN_TIMEOUT * RETRIES));
107+
Duration maxAllowedTime = Duration.ofMillis(TIMEOUT)
108+
.multipliedBy((1 << RETRIES) - 1)
109+
// max allowed timeout value is set to 2 * expected timeout
110+
.multipliedBy(2);
111+
103112
DNSTestUtils.debug("Elapsed (ms): " + elapsedTime.toMillis());
104-
DNSTestUtils.debug("Expected (ms): " + expectedTime.toMillis());
113+
String expectedRangeMsg = "%s - %s"
114+
.formatted(minAllowedTime.toMillis(), maxAllowedTime.toMillis());
115+
DNSTestUtils.debug("Expected range (ms): " + expectedRangeMsg);
105116

106117
// Check that elapsed time is as long as expected, and
107-
// not more than 50% greater.
108-
if (elapsedTime.compareTo(expectedTime) >= 0 &&
109-
elapsedTime.multipliedBy(2)
110-
.compareTo(expectedTime.multipliedBy(3)) <= 0) {
118+
// not more than 2 times greater.
119+
if (elapsedTime.compareTo(minAllowedTime) >= 0 &&
120+
elapsedTime.compareTo(maxAllowedTime) <= 0) {
111121
System.out.println("elapsed time is as long as expected.");
112122
return true;
113123
}
114124
throw new RuntimeException(
115-
"Failed: timeout in " + elapsedTime.toMillis()
116-
+ " ms, expected" + expectedTime.toMillis() + "ms");
125+
"Failed: timeout in " + elapsedTime.toMillis() +
126+
" ms, expected to be in a range (ms): " + expectedRangeMsg);
117127
}
118128

119129
return super.handleException(e);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import jdk.test.lib.net.URIBuilder;
25+
26+
import javax.naming.CommunicationException;
27+
import javax.naming.Context;
28+
import javax.naming.directory.InitialDirContext;
29+
import java.io.IOException;
30+
import java.net.DatagramPacket;
31+
import java.net.DatagramSocket;
32+
import java.net.InetAddress;
33+
import java.net.InetSocketAddress;
34+
import java.net.SocketAddress;
35+
import java.net.SocketTimeoutException;
36+
import java.time.Duration;
37+
import java.util.concurrent.CountDownLatch;
38+
import java.util.concurrent.TimeUnit;
39+
import java.util.concurrent.atomic.AtomicBoolean;
40+
import java.util.concurrent.atomic.AtomicReference;
41+
42+
/*
43+
* @test
44+
* @bug 8339538
45+
* @summary Tests that DnsClient correctly calculates left timeout in
46+
* presence of empty datagram packets.
47+
* @library ../lib /test/lib
48+
* @modules java.base/sun.security.util
49+
* @run main/othervm TimeoutWithEmptyDatagrams
50+
*/
51+
52+
public class TimeoutWithEmptyDatagrams extends DNSTestBase {
53+
// initial timeout = 1/4 sec
54+
private static final int TIMEOUT = 250;
55+
// try 5 times per server
56+
private static final int RETRIES = 5;
57+
// DnsClient retries again with increased timeout if left
58+
// timeout is less than this value, and max retry attempts
59+
// is not reached
60+
private static final int DNS_CLIENT_MIN_TIMEOUT = 0;
61+
62+
public TimeoutWithEmptyDatagrams() {
63+
setLocalServer(false);
64+
}
65+
66+
public static void main(String[] args) throws Exception {
67+
new TimeoutWithEmptyDatagrams().run(args);
68+
}
69+
70+
/*
71+
* Tests that we can set the initial UDP timeout interval and the
72+
* number of retries.
73+
*/
74+
@Override
75+
public void runTest() throws Exception {
76+
// Create a DatagramSocket and bind it to the loopback address to simulate
77+
// UDP DNS server that doesn't respond
78+
try (DatagramSocket ds = new DatagramSocket(new InetSocketAddress(
79+
InetAddress.getLoopbackAddress(), 0))) {
80+
CountDownLatch gotClientAddress = new CountDownLatch(1);
81+
AtomicReference<SocketAddress> clientAddress = new AtomicReference<>();
82+
AtomicBoolean stopTestThreads = new AtomicBoolean();
83+
84+
String allQuietUrl = URIBuilder.newBuilder()
85+
.scheme("dns")
86+
.loopback()
87+
.port(ds.getLocalPort())
88+
.build()
89+
.toString();
90+
91+
// Run a virtual thread that receives client request packets and extracts
92+
// sender address from them.
93+
Thread receiverThread = Thread.ofVirtual().start(() -> {
94+
while (!stopTestThreads.get()) {
95+
try {
96+
DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
97+
ds.receive(packet);
98+
System.err.println("Got packet from " + packet.getSocketAddress());
99+
boolean hasClientAddress = clientAddress.get() != null;
100+
clientAddress.set(packet.getSocketAddress());
101+
if (!hasClientAddress) {
102+
gotClientAddress.countDown();
103+
}
104+
} catch (IOException e) {
105+
if (!stopTestThreads.get()) {
106+
throw new RuntimeException(e);
107+
} else {
108+
return;
109+
}
110+
}
111+
}
112+
});
113+
114+
// Run a virtual thread that will send an empty packets via server socket
115+
// that should wake up the selector on a client side.
116+
Thread wakeupThread = Thread.ofVirtual().start(() -> {
117+
try {
118+
long timeout = Math.max(1, TIMEOUT / 4);
119+
// wait for a first packet on a server socket
120+
gotClientAddress.await();
121+
122+
// Now start sending empty packets until we get a notification
123+
// from client part to stop sending
124+
while (!stopTestThreads.get()) {
125+
System.err.println("Server timeout = " + timeout);
126+
TimeUnit.MILLISECONDS.sleep(timeout);
127+
System.err.println("Sending wakeup packet to " + clientAddress.get());
128+
var wakeupPacket = new DatagramPacket(new byte[0], 0);
129+
wakeupPacket.setSocketAddress(clientAddress.get());
130+
ds.send(wakeupPacket);
131+
timeout += Math.max(1, timeout / 2);
132+
}
133+
} catch (IOException ioe) {
134+
throw new RuntimeException("Test machinery failure", ioe);
135+
} catch (InterruptedException e) {
136+
throw new RuntimeException("Interrupted during wakeup packets sending");
137+
} finally {
138+
System.err.println("Server thread exiting");
139+
}
140+
});
141+
142+
long startTime = 0;
143+
try {
144+
env().put(Context.PROVIDER_URL, allQuietUrl);
145+
env().put("com.sun.jndi.dns.timeout.initial", String.valueOf(TIMEOUT));
146+
env().put("com.sun.jndi.dns.timeout.retries", String.valueOf(RETRIES));
147+
setContext(new InitialDirContext(env()));
148+
149+
startTime = System.nanoTime();
150+
context().getAttributes("");
151+
152+
// Any request should fail after timeouts have expired.
153+
throw new RuntimeException("Failed: getAttributes succeeded unexpectedly");
154+
} catch (CommunicationException ce) {
155+
// We need to catch CommunicationException outside the test framework
156+
// flow because wakeupThread.join() can take some time that could
157+
// increase measured timeout
158+
long endTime = System.nanoTime();
159+
Duration elapsedTime = Duration.ofNanos(endTime - startTime);
160+
if (ce.getRootCause() instanceof SocketTimeoutException) {
161+
162+
Duration minAllowedTime = Duration.ofMillis(TIMEOUT)
163+
.multipliedBy((1 << RETRIES) - 1)
164+
.minus(Duration.ofMillis(DNS_CLIENT_MIN_TIMEOUT * RETRIES));
165+
Duration maxAllowedTime = Duration.ofMillis(TIMEOUT)
166+
.multipliedBy((1 << RETRIES) - 1)
167+
// max allowed timeout value is set to 2 * expected timeout
168+
.multipliedBy(2);
169+
170+
DNSTestUtils.debug("Elapsed (ms): " + elapsedTime.toMillis());
171+
String expectedRangeMsg = "%s - %s"
172+
.formatted(minAllowedTime.toMillis(), maxAllowedTime.toMillis());
173+
DNSTestUtils.debug("Expected range (ms): " + expectedRangeMsg);
174+
175+
// Check that elapsed time is as long as expected, and
176+
// not more than 2 times greater.
177+
if (elapsedTime.compareTo(minAllowedTime) >= 0 &&
178+
elapsedTime.compareTo(maxAllowedTime) <= 0) {
179+
System.out.println("elapsed time is as long as expected.");
180+
} else {
181+
throw new RuntimeException(
182+
"Failed: timeout in " + elapsedTime.toMillis() +
183+
" ms, expected to be in a range (ms): " + expectedRangeMsg);
184+
}
185+
} else {
186+
throw ce;
187+
}
188+
} finally {
189+
stopTestThreads.set(true);
190+
wakeupThread.join();
191+
ds.close();
192+
receiverThread.join();
193+
}
194+
}
195+
}
196+
}

0 commit comments

Comments
 (0)
Please sign in to comment.