2323import java .util .concurrent .Executor ;
2424import java .util .concurrent .ForkJoinPool ;
2525import java .util .concurrent .TimeUnit ;
26- import java .util .concurrent .atomic .AtomicBoolean ;
2726import java .util .concurrent .atomic .AtomicLong ;
2827import java .util .function .Function ;
2928import javax .net .ssl .HttpsURLConnection ;
@@ -92,8 +91,6 @@ public final class DohResolver implements Resolver {
9291 private final AsyncSemaphore maxConcurrentRequests ;
9392
9493 private final AtomicLong lastRequest = new AtomicLong (0 );
95-
96- private final AtomicBoolean initialRequestSentMark = new AtomicBoolean (false );
9794 private final AsyncSemaphore initialRequestLock = new AsyncSemaphore (1 );
9895
9996 private static final String APPLICATION_DNS_MESSAGE = "application/dns-message" ;
@@ -177,6 +174,11 @@ public final class DohResolver implements Resolver {
177174 USE_HTTP_CLIENT = initSuccess ;
178175 }
179176
177+ // package-visible for testing
178+ long getNanoTime () {
179+ return System .nanoTime ();
180+ }
181+
180182 /**
181183 * Creates a new DoH resolver that performs lookups with HTTP GET and the default timeout (5s).
182184 *
@@ -318,7 +320,7 @@ public CompletionStage<Message> sendAsync(Message query, Executor executor) {
318320 private CompletionStage <Message > sendAsync8 (final Message query , Executor executor ) {
319321 byte [] queryBytes = prepareQuery (query ).toWire ();
320322 String url = getUrl (queryBytes );
321- long startTime = System . nanoTime ();
323+ long startTime = getNanoTime ();
322324 return maxConcurrentRequests
323325 .acquire (timeout )
324326 .handleAsync (
@@ -366,7 +368,7 @@ private SendAndGetMessageBytesResponse sendAndGetMessageBytes(
366368 ((HttpsURLConnection ) conn ).setSSLSocketFactory (sslSocketFactory );
367369 }
368370
369- Duration remainingTimeout = timeout .minus (System . nanoTime () - startTime , ChronoUnit .NANOS );
371+ Duration remainingTimeout = timeout .minus (getNanoTime () - startTime , ChronoUnit .NANOS );
370372 conn .setConnectTimeout ((int ) remainingTimeout .toMillis ());
371373 conn .setReadTimeout ((int ) remainingTimeout .toMillis ());
372374 conn .setRequestMethod (usePost ? "POST" : "GET" );
@@ -392,7 +394,7 @@ private SendAndGetMessageBytesResponse sendAndGetMessageBytes(
392394 int offset = 0 ;
393395 while ((r = is .read (responseBytes , offset , responseBytes .length - offset )) > 0 ) {
394396 offset += r ;
395- remainingTimeout = timeout .minus (System . nanoTime () - startTime , ChronoUnit .NANOS );
397+ remainingTimeout = timeout .minus (getNanoTime () - startTime , ChronoUnit .NANOS );
396398 if (remainingTimeout .isNegative ()) {
397399 throw new SocketTimeoutException ();
398400 }
@@ -406,7 +408,7 @@ private SendAndGetMessageBytesResponse sendAndGetMessageBytes(
406408 byte [] buffer = new byte [4096 ];
407409 int r ;
408410 while ((r = is .read (buffer , 0 , buffer .length )) > 0 ) {
409- remainingTimeout = timeout .minus (System . nanoTime () - startTime , ChronoUnit .NANOS );
411+ remainingTimeout = timeout .minus (getNanoTime () - startTime , ChronoUnit .NANOS );
410412 if (remainingTimeout .isNegative ()) {
411413 throw new SocketTimeoutException ();
412414 }
@@ -435,7 +437,7 @@ private void discardStream(InputStream es) throws IOException {
435437 }
436438
437439 private CompletionStage <Message > sendAsync11 (final Message query , Executor executor ) {
438- long startTime = System . nanoTime ();
440+ long startTime = getNanoTime ();
439441 byte [] queryBytes = prepareQuery (query ).toWire ();
440442 String url = getUrl (queryBytes );
441443
@@ -457,7 +459,7 @@ private CompletionStage<Message> sendAsync11(final Message query, Executor execu
457459 // check if this request needs to be done synchronously because of HttpClient's stupidity to
458460 // not use the connection pool for HTTP/2 until one connection is successfully established,
459461 // which could lead to hundreds of connections (and threads with the default executor)
460- Duration remainingTimeout = timeout .minus (System . nanoTime () - startTime , ChronoUnit .NANOS );
462+ Duration remainingTimeout = timeout .minus (getNanoTime () - startTime , ChronoUnit .NANOS );
461463 return initialRequestLock
462464 .acquire (remainingTimeout )
463465 .handle (
@@ -472,34 +474,20 @@ private CompletionStage<Message> sendAsync11(final Message query, Executor execu
472474 .thenCompose (Function .identity ());
473475 }
474476
475- /**
476- * Check whether current initiating DoH request is initial request of this {@link DohResolver}.
477- */
478- private boolean checkInitialRequest () {
479- // If initial request haven't been completed successfully yet, just return true.
480- if (!initialRequestSentMark .get ()) {
481- return true ;
482- }
483-
484- // Otherwise, check whether such request is happened
485- // after last successful request plus idle connection timeout.
486- long lastRequestTime = lastRequest .get ();
487- return (lastRequestTime + idleConnectionTimeout .toNanos () < System .nanoTime ());
488- }
489-
490477 private CompletionStage <Message > sendAsync11WithInitialRequestPermit (
491478 Message query ,
492479 Executor executor ,
493480 long startTime ,
494481 Object requestBuilder ,
495482 Permit initialRequestPermit ) {
496- boolean isInitialRequest = checkInitialRequest ();
483+ long lastRequestTime = lastRequest .get ();
484+ boolean isInitialRequest = idleConnectionTimeout .toNanos () > getNanoTime () - lastRequestTime ;
497485 if (!isInitialRequest ) {
498486 initialRequestPermit .release ();
499487 }
500488
501489 // check if we already exceeded the query timeout while checking the initial connection
502- Duration remainingTimeout = timeout .minus (System . nanoTime () - startTime , ChronoUnit .NANOS );
490+ Duration remainingTimeout = timeout .minus (getNanoTime () - startTime , ChronoUnit .NANOS );
503491 if (remainingTimeout .isNegative ()) {
504492 if (isInitialRequest ) {
505493 initialRequestPermit .release ();
@@ -532,25 +520,6 @@ private CompletionStage<Message> sendAsync11WithInitialRequestPermit(
532520 .thenCompose (Function .identity ());
533521 }
534522
535- /**
536- * Set last request time to {@link DohResolver#lastRequest}, which ensures only the largest timestamp could be accepted.
537- *
538- * @param startTime start time in nanos of a Doh request.
539- */
540- private void setLastRequestTime (long startTime ) {
541- long current = lastRequest .get ();
542- // Only update value of 'lastRequest' if timestamp in 'lastRequest' is smaller than incoming 'startTime' value.
543- if (current < startTime ) {
544- while (!lastRequest .compareAndSet (current , startTime )) {
545- // CAS failed, re-verify the eligibility of timestamp in 'lastRequest' to be updated to the incoming 'startTime' value.
546- current = lastRequest .get ();
547- if (current > startTime ) {
548- return ;
549- }
550- }
551- }
552- }
553-
554523 private CompletionStage <Message > sendAsync11WithConcurrentRequestPermit (
555524 Message query ,
556525 Executor executor ,
@@ -560,7 +529,7 @@ private CompletionStage<Message> sendAsync11WithConcurrentRequestPermit(
560529 boolean isInitialRequest ,
561530 Permit maxConcurrentRequestPermit ) {
562531 // check if the stream lock acquisition took too long
563- Duration remainingTimeout = timeout .minus (System . nanoTime () - startTime , ChronoUnit .NANOS );
532+ Duration remainingTimeout = timeout .minus (getNanoTime () - startTime , ChronoUnit .NANOS );
564533 if (remainingTimeout .isNegative ()) {
565534 if (isInitialRequest ) {
566535 initialRequestPermit .release ();
@@ -583,12 +552,7 @@ private CompletionStage<Message> sendAsync11WithConcurrentRequestPermit(
583552 .whenComplete (
584553 (result , ex ) -> {
585554 if (ex == null ) {
586- setLastRequestTime (startTime );
587- if (isInitialRequest ) {
588- // initial request was completed successfully, so toggle initialRequestSentMark to true.
589- // it's very safe to toggle initialRequestSentMark here, since this code had been guarded by initialRequestLock and its permit outside.
590- initialRequestSentMark .compareAndSet (false , true );
591- }
555+ lastRequest .set (startTime );
592556 }
593557 maxConcurrentRequestPermit .release ();
594558 if (isInitialRequest ) {
0 commit comments