Skip to content

Commit d7bd25d

Browse files
committed
HTTPClient: include details about where in the request the client was at point of timeout
This tracks the state of the client request. If the request is canceled (likely through the watchdog / timeout, but possibly externally by the user as well), then state at point of being canceled will be reported in the message of the CancellationException.
1 parent 08dcf6d commit d7bd25d

File tree

2 files changed

+53
-5
lines changed

2 files changed

+53
-5
lines changed

client/src/main/java/org/threadly/litesockets/client/http/HTTPClient.java

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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
/**

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
group = org.threadly
22
version = 0.21-SNAPSHOT
3-
threadlyVersion = 5.30
3+
threadlyVersion = 5.31
44
litesocketsVersion = 4.9
55
org.gradle.parallel=false
66
junitVersion = 4.12

0 commit comments

Comments
 (0)