@@ -331,8 +331,12 @@ private void processQueue() {
331331 }
332332
333333 private void process (HTTPRequestWrapper hrw ) {
334+ if (hrw .slf .isDone ()) {
335+ // don't process, request either timed out while queued or otherwise completed in error
336+ return ;
337+ }
334338 try {
335- hrw .updateReadTime ();
339+ hrw .requestStarting ();
336340 hrw .client = getTCPClient (hrw .chr .getHTTPAddress ());
337341 inProcess .put (hrw .client , hrw );
338342 SimpleMergedByteBuffers writeBuffer ;
@@ -504,21 +508,29 @@ public void onClose(Client client) {
504508 public void onRead (Client client ) {
505509 HTTPRequestWrapper hrw = inProcess .get (client );
506510 if (hrw != null ) {
507- hrw .hrp . processData (client .getRead ());
511+ hrw .processIncomingData (client .getRead ());
508512 } else {
509513 client .close ();
510514 }
511515 }
512516 }
513517
518+ /**
519+ * State the request is currently in, state should transition in order of enum.
520+ */
521+ private enum RequestState { Queued , SendingRequest ,
522+ ReadingResponseHeader , ReadingResponseBody ,
523+ Finished }
524+
514525 /**
515526 * Wrapper for the request that will handle the incoming response data (as well as notifying
516527 * back to the client once the response is complete).
517528 */
518529 private class HTTPRequestWrapper implements HTTPResponseCallback {
519- private final SettableListenableFuture <HTTPResponseData > slf = new SettableListenableFuture <>(false );
530+ private final SettableListenableFuture <HTTPResponseData > slf = new TimeoutTrackingSettableListenableFuture <>();
520531 private final HTTPResponseProcessor hrp ;
521532 private final ClientHTTPRequest chr ;
533+ private RequestState currentState = RequestState .Queued ;
522534 private HTTPResponse response ;
523535 private ReuseableMergedByteBuffers responseMBB = new ReuseableMergedByteBuffers ();
524536 private TCPClient client ;
@@ -534,8 +546,17 @@ private HTTPRequestWrapper(ClientHTTPRequest chr) {
534546 sei .watchFuture (slf , chr .getTimeoutMS ());
535547 }
536548
537- public void updateReadTime () {
549+ public void requestStarting () {
550+ currentState = RequestState .SendingRequest ;
551+ lastRead = Clock .lastKnownForwardProgressingMillis ();
552+ }
553+
554+ public void processIncomingData (ReuseableMergedByteBuffers read ) {
555+ if (currentState == RequestState .SendingRequest ) {
556+ currentState = RequestState .ReadingResponseHeader ;
557+ }
538558 lastRead = Clock .lastKnownForwardProgressingMillis ();
559+ hrp .processData (read );
539560 }
540561
541562 public long timeTillExpired () {
@@ -544,6 +565,7 @@ public long timeTillExpired() {
544565
545566 @ Override
546567 public void headersFinished (HTTPResponse hr ) {
568+ currentState = RequestState .ReadingResponseBody ;
547569 response = hr ;
548570 }
549571
@@ -558,6 +580,7 @@ public void bodyData(ByteBuffer bb) {
558580
559581 @ Override
560582 public void finished () {
583+ currentState = RequestState .Finished ;
561584 slf .setResult (new HTTPResponseData (HTTPClient .this , chr .getHTTPRequest (), response ,
562585 responseMBB .duplicateAndClean ()));
563586 hrp .removeHTTPResponseCallback (this );
@@ -579,6 +602,31 @@ public void websocketData(WSFrame wsf, ByteBuffer bb) {
579602 slf .setFailure (new Exception ("HTTPClient does not currently support websockets!" ));
580603 client .close ();
581604 }
605+
606+ /**
607+ * Implementation of {@link SettableListenableFuture} which records the RequestState at the time
608+ * of cancel / timeout so that it can later be reported for debugging.
609+ *
610+ * @param <T> The type of result returned by the future
611+ */
612+ protected class TimeoutTrackingSettableListenableFuture <T > extends SettableListenableFuture <T > {
613+ private RequestState stateBeforeCancel = null ;
614+
615+ protected TimeoutTrackingSettableListenableFuture () {
616+ super (false );
617+ }
618+
619+ @ Override
620+ protected void setCanceled () {
621+ stateBeforeCancel = currentState ;
622+ super .setCanceled ();
623+ }
624+
625+ @ Override
626+ protected String getCancellationExceptionMessage () {
627+ return "Request timed out at point: " + stateBeforeCancel .name ();
628+ }
629+ }
582630 }
583631
584632 /**
0 commit comments