Skip to content

Commit

Permalink
Workaround JDK http timeout bug
Browse files Browse the repository at this point in the history
  • Loading branch information
malkusch committed Nov 3, 2023
1 parent a5958c5 commit 547c249
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 10 deletions.
7 changes: 5 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.malkusch.km200</groupId>
<artifactId>km200</artifactId>
Expand All @@ -13,7 +15,8 @@
<url>https://github.com/malkusch/${project.artifactId}</url>
<scm>
<connection>scm:git:git://github.com/malkusch/${project.artifactId}.git</connection>
<developerConnection>scm:git:[email protected]:malkusch/${project.artifactId}.git</developerConnection>
<developerConnection>
scm:git:[email protected]:malkusch/${project.artifactId}.git</developerConnection>
</scm>
<properties>
<github.repository>malkusch/km200</github.repository>
Expand Down
77 changes: 77 additions & 0 deletions src/main/java/de/malkusch/km200/HttpTimeoutWorkaround.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package de.malkusch.km200;

import static java.net.http.HttpClient.newBuilder;
import static java.net.http.HttpClient.Redirect.ALWAYS;
import static java.util.Optional.ofNullable;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

import java.io.IOException;
import java.net.CookieManager;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandler;
import java.net.http.HttpTimeoutException;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

final class HttpTimeoutWorkaround implements AutoCloseable {

private final Duration timeout;
private final HttpClient client;
private final ScheduledExecutorService executor;

HttpTimeoutWorkaround(Duration timeout) {
this.client = newBuilder() //
.connectTimeout(timeout) //
.cookieHandler(new CookieManager()) //
.followRedirects(ALWAYS) //
.build();

this.timeout = timeout.plusSeconds(1);

executor = Executors.newSingleThreadScheduledExecutor(r -> {
var thread = new Thread(r, "KM200");
thread.setUncaughtExceptionHandler((t, e) -> {
e.printStackTrace();
});
thread.setDaemon(true);
return thread;
});
}

public <T> HttpResponse<T> send(HttpRequest request, BodyHandler<T> bodyHandler)
throws IOException, InterruptedException {

try {
return executor //
.submit(() -> client.send(request, bodyHandler)) //
.get(timeout.toMillis(), MILLISECONDS);

} catch (TimeoutException e) {
throw new HttpTimeoutException("KM200 timed out (HttpClient timed out)");

} catch (ExecutionException e) {
if (e.getCause() instanceof HttpTimeoutException timeoutException) {
throw timeoutException;
}
throw new IOException("KM200 failed", ofNullable(e.getCause()).orElse(e));
}
}

@Override
public void close() throws Exception {
executor.shutdown();
if (executor.awaitTermination(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
return;
}
executor.shutdownNow();
if (!executor.awaitTermination(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
throw new IOException("Closing KM200 failed");
}
}
}
16 changes: 8 additions & 8 deletions src/main/java/de/malkusch/km200/KM200.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package de.malkusch.km200;

import static java.net.http.HttpClient.newBuilder;
import static java.net.http.HttpClient.Redirect.ALWAYS;
import static java.util.Objects.requireNonNull;

import java.io.IOException;
import java.math.BigDecimal;
import java.net.CookieManager;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
Expand All @@ -35,12 +31,12 @@
* use it concurrently. Chances are that your KM200 gateway itself is not thread
* safe.
*/
public final class KM200 {
public final class KM200 implements AutoCloseable {

private final KM200Device device;
private final KM200Comm comm;
private final ObjectMapper mapper = new ObjectMapper();
private final HttpClient http;
private final HttpTimeoutWorkaround http;
private final Duration timeout;
private final String uri;
private final FailsafeExecutor<HttpResponse<byte[]>> retry;
Expand Down Expand Up @@ -115,8 +111,7 @@ public KM200(String uri, Duration timeout, String gatewayPassword, String privat
this.comm = new KM200Comm();
this.timeout = timeout;
this.uri = uri.replaceAll("/*$", "");
this.http = newBuilder().connectTimeout(timeout).cookieHandler(new CookieManager()).followRedirects(ALWAYS)
.build();
this.http = new HttpTimeoutWorkaround(timeout);

retry = Failsafe.with( //
RetryPolicy.<HttpResponse<byte[]>> builder() //
Expand Down Expand Up @@ -283,4 +278,9 @@ private static void assertNotBlank(String var, String message) {
throw new IllegalArgumentException(message);
}
}

@Override
public void close() throws Exception {
http.close();
}
}

0 comments on commit 547c249

Please sign in to comment.