diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpQuicConnection.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpQuicConnection.java index bbbe1157cdffc..0a22256f6c612 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpQuicConnection.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpQuicConnection.java @@ -499,22 +499,28 @@ public CompletableFuture connectAsync(Exchange exchange) { }, exchange.parentExecutor.safeDelegate()); } - Optional timeout = client().connectTimeout(); CompletableFuture> fxi = handshakeCfCf; // In case of connection timeout, set up a timeout on the handshakeCfCf. // Note: this is a different timeout than the direct connection timeout. - if (timeout.isPresent()) { + Duration timeout = client().connectTimeout().orElse(null); + if (timeout != null) { // In case of timeout we need to close the quic connection - debug.log("setting up quic connect timeout: " + timeout.get().toMillis()); + debug.log("setting up quic connect timeout: " + timeout); + long timeoutMillis; + try { + timeoutMillis = timeout.toMillis(); + } catch (ArithmeticException _) { + timeoutMillis = Long.MAX_VALUE; + } fxi = handshakeCfCf.completeOnTimeout( MinimalFuture.failedFuture(new HttpConnectTimeoutException("quic connect timeout")), - timeout.get().toMillis(), TimeUnit.MILLISECONDS); + timeoutMillis, TimeUnit.MILLISECONDS); } // If we have set up any timeout, arrange to close the quicConnection // if one of the timeout expires - if (timeout.isPresent() || directTimeout.isPresent()) { + if (timeout != null || directTimeout.isPresent()) { fxi = fxi.handleAsync(this::handleTimeout, exchange.parentExecutor.safeDelegate()); } return fxi.thenCompose(Function.identity()); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/Deadline.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/Deadline.java index 3ee334885a371..6a042fd7d0d61 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/Deadline.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Deadline.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,14 +28,22 @@ import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.time.temporal.Temporal; -import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalAmount; import java.time.temporal.TemporalUnit; import java.time.temporal.UnsupportedTemporalTypeException; /** - * A Deadline represents an instant on a {@linkplain TimeLine time line}. + * An instantaneous point on the {@linkplain TimeLine time-line}. + *

+ * This class is immutable and thread-safe. + *

+ * Operations that add or subtract durations to a {@code Deadline}, whether + * represented as a {@link Duration} or as a {@code long} time increment (such + * as seconds or nanoseconds) do not throw on numeric overflow if the resulting + * {@code Deadline} would exceed {@link #MAX} or be less than {@link #MIN}. + * Instead, {@code MAX} or {@code MIN} is returned, respectively. Similarly, + * methods that return a duration as a {@code long} will either return + * {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE} if the returned quantity + * would exceed the capacity of a {@code long}. */ public final class Deadline implements Comparable { @@ -49,17 +57,24 @@ private Deadline(Instant deadline) { /** - * Returns a copy of this deadline with the specified duration in nanoseconds added. + * {@return a deadline with the specified duration in nanoseconds added} *

* This instance is immutable and unaffected by this method call. + *

+ * On {@linkplain ##overflow numeric overflows}, this method will return + * {@link Deadline#MAX} if the provided duration is positive, + * {@link Deadline#MIN} otherwise. * * @param nanosToAdd the nanoseconds to add, positive or negative - * @return a {@code Deadline} based on this deadline with the specified nanoseconds added, not null - * @throws DateTimeException if the result exceeds the maximum or minimum deadline - * @throws ArithmeticException if numeric overflow occurs */ public Deadline plusNanos(long nanosToAdd) { - return new Deadline(deadline.plusNanos(nanosToAdd)); + if (nanosToAdd == 0) return this; + try { + return new Deadline(deadline.plusNanos(nanosToAdd)); + } catch (DateTimeException | // "Instant exceeds minimum or maximum instant" + ArithmeticException _) { // "long overflow" + return nanosToAdd > 0 ? Deadline.MAX : Deadline.MIN; + } } /** @@ -89,92 +104,116 @@ public Deadline truncatedTo(ChronoUnit unit) { } /** - * Returns a copy of this deadline with the specified amount subtracted. - *

- * This returns a {@code Deadline}, based on this one, with the specified amount subtracted. - * The amount is typically {@link Duration} but may be any other type implementing - * the {@link TemporalAmount} interface. + * {@return a deadline with the specified amount subtracted from this deadline} *

* This instance is immutable and unaffected by this method call. + *

+ * On {@linkplain ##overflow numeric overflows}, this method will return + * {@link Deadline#MIN} if the provided duration is positive, + * {@link Deadline#MAX} otherwise. * - * @param amountToSubtract the amount to subtract, not null - * @return a {@code Deadline} based on this deadline with the subtraction made, not null - * @throws DateTimeException if the subtraction cannot be made - * @throws ArithmeticException if numeric overflow occurs + * @param duration the amount to subtract, not null */ - public Deadline minus(TemporalAmount amountToSubtract) { - return Deadline.of(deadline.minus(amountToSubtract)); + public Deadline minus(Duration duration) { + if (duration.isZero()) return this; + try { + return Deadline.of(deadline.minus(duration)); + } catch (DateTimeException | // "Instant exceeds minimum or maximum instant" + ArithmeticException _) { // "long overflow" + return duration.isPositive() ? Deadline.MIN : Deadline.MAX; + } } /** - * Returns a copy of this deadline with the specified amount added. + * {@return a deadline with the specified amount added to this deadline} *

* This returns a {@code Deadline}, based on this one, with the amount * in terms of the unit added. If it is not possible to add the amount, because the * unit is not supported or for some other reason, an exception is thrown. *

* This instance is immutable and unaffected by this method call. + *

+ * On {@linkplain ##overflow numeric overflows}, this method will return + * {@link Deadline#MAX} if the provided amount is positive, + * {@link Deadline#MIN} otherwise. * * @see Instant#plus(long, TemporalUnit) * * @param amountToAdd the amount of the unit to add to the result, may be negative * @param unit the unit of the amount to add, not null - * @return a {@code Deadline} based on this deadline with the specified amount added, not null - * @throws DateTimeException if the addition cannot be made * @throws UnsupportedTemporalTypeException if the unit is not supported - * @throws ArithmeticException if numeric overflow occurs */ public Deadline plus(long amountToAdd, TemporalUnit unit) { if (amountToAdd == 0) return this; - return Deadline.of(deadline.plus(amountToAdd, unit)); + try { + return Deadline.of(deadline.plus(amountToAdd, unit)); + } catch (DateTimeException | // "Instant exceeds minimum or maximum instant" + ArithmeticException _) { // "long overflow" + return amountToAdd > 0 ? Deadline.MAX : Deadline.MIN; + } } /** - * Returns a copy of this deadline with the specified duration in seconds added. + * {@return a deadline with the specified duration in seconds added to this deadline} *

* This instance is immutable and unaffected by this method call. + *

+ * On {@linkplain ##overflow numeric overflows}, this method will return + * {@link Deadline#MAX} if the provided duration is positive, + * {@link Deadline#MIN} otherwise. * * @param secondsToAdd the seconds to add, positive or negative - * @return a {@code Deadline} based on this deadline with the specified seconds added, not null - * @throws DateTimeException if the result exceeds the maximum or minimum deadline - * @throws ArithmeticException if numeric overflow occurs */ public Deadline plusSeconds(long secondsToAdd) { if (secondsToAdd == 0) return this; - return Deadline.of(deadline.plusSeconds(secondsToAdd)); + try { + return Deadline.of(deadline.plusSeconds(secondsToAdd)); + } catch (DateTimeException | // "Instant exceeds minimum or maximum instant" + ArithmeticException _) { // "long overflow" + return secondsToAdd > 0 ? Deadline.MAX : Deadline.MIN; + } } /** - * Returns a copy of this deadline with the specified duration in milliseconds added. + * {@return a deadline with the specified duration in milliseconds added to this deadline} *

* This instance is immutable and unaffected by this method call. + *

+ * On {@linkplain ##overflow numeric overflows}, this method will return + * {@link Deadline#MAX} if the provided duration is positive, + * {@link Deadline#MIN} otherwise. * * @param millisToAdd the milliseconds to add, positive or negative - * @return a {@code Deadline} based on this deadline with the specified milliseconds added, not null - * @throws DateTimeException if the result exceeds the maximum or minimum deadline - * @throws ArithmeticException if numeric overflow occurs */ public Deadline plusMillis(long millisToAdd) { if (millisToAdd == 0) return this; - return Deadline.of(deadline.plusMillis(millisToAdd)); + try { + return Deadline.of(deadline.plusMillis(millisToAdd)); + } catch (DateTimeException | // "Instant exceeds minimum or maximum instant" + ArithmeticException _) { // "long overflow" + return millisToAdd > 0 ? Deadline.MAX : Deadline.MIN; + } } /** - * Returns a copy of this deadline with the specified amount added. - *

- * This returns a {@code Deadline}, based on this one, with the specified amount added. - * The amount is typically {@link Duration} but may be any other type implementing - * the {@link TemporalAmount} interface. + * {@return a deadline with the specified duration added to this deadline} *

* This instance is immutable and unaffected by this method call. + *

+ * On {@linkplain ##overflow numeric overflows}, this method will return + * {@link Deadline#MAX} if the provided duration is positive, + * {@link Deadline#MIN} otherwise. * - * @param amountToAdd the amount to add, not null - * @return a {@code Deadline} based on this deadline with the addition made, not null - * @throws DateTimeException if the addition cannot be made - * @throws ArithmeticException if numeric overflow occurs + * @param duration the duration to add, not null */ - public Deadline plus(TemporalAmount amountToAdd) { - return Deadline.of(deadline.plus(amountToAdd)); + public Deadline plus(Duration duration) { + if (duration.isZero()) return this; + try { + return Deadline.of(deadline.plus(duration)); + } catch (DateTimeException | // "Instant exceeds minimum or maximum instant" + ArithmeticException _) { // "long overflow" + return duration.isPositive() ? Deadline.MAX : Deadline.MIN; + } } /** @@ -188,16 +227,24 @@ public Deadline plus(TemporalAmount amountToAdd) { * complete units between the two deadlines. *

* This instance is immutable and unaffected by this method call. + *

+ * On {@linkplain ##overflow numeric overflows}, this method will return + * {@link Long#MAX_VALUE} if the current deadline is before the provided end + * deadline, {@link Long#MIN_VALUE} otherwise. * * @param endExclusive the end deadline, exclusive * @param unit the unit to measure the amount in, not null * @return the amount of time between this deadline and the end deadline - * @throws DateTimeException if the amount cannot be calculated * @throws UnsupportedTemporalTypeException if the unit is not supported - * @throws ArithmeticException if numeric overflow occurs */ public long until(Deadline endExclusive, TemporalUnit unit) { - return deadline.until(endExclusive.deadline, unit); + try { + return deadline.until(endExclusive.deadline, unit); + } catch (DateTimeException | // "Instant exceeds minimum or maximum instant" + ArithmeticException _) { // "long overflow" + int delta = compareTo(endExclusive); + return delta < 0 ? Long.MAX_VALUE : Long.MIN_VALUE; + } } /** @@ -266,10 +313,13 @@ static Deadline of(Instant instant) { * @param startInclusive the start deadline, inclusive, not null * @param endExclusive the end deadline, exclusive, not null * @return a {@code Duration}, not null - * @throws DateTimeException if the seconds between the deadline cannot be obtained - * @throws ArithmeticException if the calculation exceeds the capacity of {@code Duration} */ public static Duration between(Deadline startInclusive, Deadline endExclusive) { + if (startInclusive.equals(endExclusive)) return Duration.ZERO; + // `Deadline` works with `Instant` under the hood. + // Delta between `Instant.MIN` and `Instant.MAX` fits in a `Duration`. + // Hence, we should never receive a numeric overflow while calculating the delta between two deadlines. return Duration.between(startInclusive.deadline, endExclusive.deadline); } + } diff --git a/test/jdk/java/net/httpclient/DurationOverflowTest.java b/test/jdk/java/net/httpclient/DurationOverflowTest.java new file mode 100644 index 0000000000000..bc836a7c5dda0 --- /dev/null +++ b/test/jdk/java/net/httpclient/DurationOverflowTest.java @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.httpclient.test.lib.common.HttpServerAdapters.HttpTestEchoHandler; +import jdk.httpclient.test.lib.common.HttpServerAdapters.HttpTestServer; +import jdk.internal.net.http.common.Logger; +import jdk.internal.net.http.common.Utils; +import jdk.test.lib.net.SimpleSSLContext; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Version; +import java.net.http.HttpOption; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse.BodyHandlers; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static java.net.http.HttpClient.Builder.NO_PROXY; +import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static jdk.httpclient.test.lib.common.HttpServerAdapters.createClientBuilderFor; + +/* + * @test id=withoutPropertyConfig + * @bug 8368528 + * @summary Verifies that `Duration`-accepting programmatic public APIs, either + * individually, or in combination, work with arbitrarily large values + * + * @library /test/jdk/java/net/httpclient/lib + * /test/lib + * + * @run junit DurationOverflowTest + */ + +/* + * @test id=withPropertyConfig + * @bug 8368528 + * @summary Verifies that `Duration`-accepting programmatic public APIs, either + * individually, or in combination, work with arbitrarily large values + * when combined with duration-accepting property-based public APIs. + * + * @library /test/jdk/java/net/httpclient/lib + * /test/lib + * + * @comment 9223372036854775807 is the value of `Long.MAX_VALUE` + * + * @run junit/othervm + * -Djdk.httpclient.keepalive.timeout=9223372036854775807 + * DurationOverflowTest + * + * @comment `h3` infra is also enabled for this test since `j.h.k.timeout.h3` + * defaults to `j.h.k.timeout.h2` + * @run junit/othervm + * -Djdk.httpclient.keepalive.timeout.h2=9223372036854775807 + * -DallowedInfras=h2,h2s,h3 + * DurationOverflowTest + * + * @run junit/othervm + * -Djdk.httpclient.keepalive.timeout.h3=9223372036854775807 + * -DallowedInfras=h3 + * DurationOverflowTest + */ + +public class DurationOverflowTest { + + private static final String CLASS_NAME = DurationOverflowTest.class.getSimpleName(); + + private static final Logger LOGGER = Utils.getDebugLogger(CLASS_NAME::toString, Utils.DEBUG); + + private static final SSLContext SSL_CONTEXT = createSslContext(); + + private static SSLContext createSslContext() { + try { + return new SimpleSSLContext().get(); + } catch (IOException exception) { + throw new UncheckedIOException(exception); + } + } + + private static final List INFRAS = loadInfras(); + + private static final class Infra implements AutoCloseable { + + private static final AtomicInteger SERVER_COUNTER = new AtomicInteger(); + + private final String serverId; + + private final HttpTestServer server; + + private final Supplier clientBuilderSupplier; + + private final Supplier requestBuilderSupplier; + + private final boolean secure; + + private Infra( + String serverId, + HttpTestServer server, + Supplier clientBuilderSupplier, + Supplier requestBuilderSupplier, + boolean secure) { + this.serverId = serverId; + this.server = server; + this.clientBuilderSupplier = clientBuilderSupplier; + this.requestBuilderSupplier = requestBuilderSupplier; + this.secure = secure; + } + + private static Infra of(Version version, boolean secure) { + + // Create the server and the request URI + var sslContext = secure ? SSL_CONTEXT : null; + var server = createServer(version, sslContext); + server.getVersion(); + var handlerPath = "/%s/".formatted(CLASS_NAME); + var requestUriScheme = secure ? "https" : "http"; + var requestUri = URI.create("%s://%s%s-".formatted(requestUriScheme, server.serverAuthority(), handlerPath)); + + // Register the request handler + var serverId = "" + SERVER_COUNTER.getAndIncrement(); + server.addHandler( + // Intentionally opting for receiving a body to cover code paths associated with its retrieval + new HttpTestEchoHandler(false), + handlerPath); + + // Create client & request builders + Supplier clientBuilderSupplier = + () -> createClientBuilderFor(version) + .version(version) + .sslContext(SSL_CONTEXT) + .proxy(NO_PROXY); + Supplier requestBuilderSupplier = + () -> createRequestBuilder(requestUri, version); + + // Create the pair + var pair = new Infra(serverId, server, clientBuilderSupplier, requestBuilderSupplier, secure); + pair.server.start(); + LOGGER.log("Server[%s] is started at `%s`", serverId, server.serverAuthority()); + return pair; + + } + + private static HttpTestServer createServer(Version version, SSLContext sslContext) { + try { + return switch (version) { + case HTTP_1_1, HTTP_2 -> HttpTestServer.create(version, sslContext, null); + case HTTP_3 -> HttpTestServer.create(HTTP_3_URI_ONLY, sslContext, null); + }; + } catch (IOException exception) { + throw new UncheckedIOException(exception); + } + } + + private static HttpRequest.Builder createRequestBuilder(URI uri, Version version) { + var requestBuilder = HttpRequest.newBuilder(uri).version(version).HEAD(); + if (Version.HTTP_3.equals(version)) { + requestBuilder.setOption(HttpOption.H3_DISCOVERY, HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY); + } + return requestBuilder; + } + + @Override + public void close() { + LOGGER.log("Server[%s] is stopping", serverId); + server.stop(); + } + + @Override + public String toString() { + var version = server.getVersion(); + var versionString = version.toString(); + return switch (version) { + case HTTP_1_1, HTTP_2 -> secure ? versionString.replaceFirst("_", "S_") : versionString; + case HTTP_3 -> versionString; + }; + } + + } + + private static List loadInfras() { + return Stream + .of(System.getProperty("allowedInfras", "h1,h1s,h2,h2s,h3").split(",")) + .map(infra -> { + LOGGER.log("Loading test infrastructure: `%s`", infra); + return switch (infra) { + case "h1" -> Infra.of(Version.HTTP_1_1, false); + case "h1s" -> Infra.of(Version.HTTP_1_1, true); + case "h2" -> Infra.of(Version.HTTP_2, false); + case "h2s" -> Infra.of(Version.HTTP_2, true); + case "h3" -> Infra.of(Version.HTTP_3, true); + default -> throw new IllegalArgumentException("Unknown test infrastructure: " + infra); + }; + }) + .toList(); + } + + @AfterAll + static void tearDownInfras() { + LOGGER.log("Tearing down test infrastructure"); + Exception[] exceptionRef = {null}; + infras().forEach(infra -> { + try { + infra.close(); + } catch (Exception exception) { + if (exceptionRef[0] == null) { + exceptionRef[0] = exception; + } else { + exceptionRef[0].addSuppressed(exception); + } + } + }); + if (exceptionRef[0] != null) { + throw new RuntimeException("Failed tearing down one or more test infrastructures", exceptionRef[0]); + } + } + + private static Stream infras() { + return INFRAS.stream(); + } + + public static final Set EXCESSIVE_DURATIONS = Set.of( + Duration.MAX, + // This triggers different exceptions than the ones triggered by `Duration.MAX` + Duration.ofMillis(Long.MAX_VALUE)); + + private static Stream infraDurationPairs() { + return infras().flatMap(infra -> EXCESSIVE_DURATIONS.stream() + .map(duration -> new InfraDurationPair(infra, duration))); + } + + private record InfraDurationPair(Infra infra, Duration duration) {} + + @ParameterizedTest + @MethodSource("infraDurationPairs") + void testClientConnectTimeout(InfraDurationPair pair) throws Exception { + testConfig(pair.infra, clientBuilder -> clientBuilder.connectTimeout(pair.duration), null); + } + + @ParameterizedTest + @MethodSource("infraDurationPairs") + void testRequestTimeout(InfraDurationPair pair) throws Exception { + testConfig(pair.infra, null, requestBuilder -> requestBuilder.timeout(pair.duration)); + } + + private static Stream infraDurationDurationTriples() { + return infras().flatMap(infra -> EXCESSIVE_DURATIONS.stream() + .flatMap(duration1 -> EXCESSIVE_DURATIONS.stream() + .map(duration2 -> new InfraDurationDurationTriple(infra, duration1, duration2)))); + } + + private record InfraDurationDurationTriple(Infra infra, Duration duration1, Duration duration2) {} + + @ParameterizedTest + @MethodSource("infraDurationDurationTriples") + void testClientConnectTimeoutAndRequestTimeout(InfraDurationDurationTriple triple) throws Exception { + testConfig( + triple.infra, + clientBuilder -> clientBuilder.connectTimeout(triple.duration1), + requestBuilder -> requestBuilder.timeout(triple.duration2)); + } + + private static void testConfig( + Infra infra, + Consumer clientBuilderConsumer, + Consumer requestBuilderConsumer) + throws Exception { + + // Create the client + var clientBuilder = infra.clientBuilderSupplier.get(); + if (clientBuilderConsumer != null) { + clientBuilderConsumer.accept(clientBuilder); + } + try (var client = clientBuilder.build()) { + + // Create the request + byte[] expectedBytes = "abc".repeat(8192).getBytes(US_ASCII); + var requestBuilder = infra.requestBuilderSupplier.get() + // Intentionally opting for sending a body to cover code paths associated with its delivery + .POST(BodyPublishers.ofByteArray(expectedBytes)); + if (requestBuilderConsumer != null) { + requestBuilderConsumer.accept(requestBuilder); + } + var request = requestBuilder.build(); + + // Execute the request. + // Doing it twice to touch code paths before & after a protocol upgrade, if present. + for (int requestIndex = 0; requestIndex < 2; requestIndex++) { + LOGGER.log("Executing request (attempt=%s)", requestIndex + 1); + var response = client.send(request, BodyHandlers.ofByteArray()); + + // Verify the response status code + if (response.statusCode() != 200) { + var message = String.format( + "Unexpected status code: %s (attempt=%s)", + response.statusCode(), requestIndex + 1); + throw new AssertionError(message); + } + + // Verify the response body + int mismatchIndex = Arrays.mismatch(expectedBytes, response.body()); + if (mismatchIndex > 0) { + var message = String.format( + "Body mismatch at index %s (attempt=%s)", + mismatchIndex, requestIndex + 1); + throw new AssertionError(message); + } + + } + + } + + } + +} diff --git a/test/jdk/java/net/httpclient/whitebox/DeadlineOverflowTestDriver.java b/test/jdk/java/net/httpclient/whitebox/DeadlineOverflowTestDriver.java new file mode 100644 index 0000000000000..a7270c2bfdb24 --- /dev/null +++ b/test/jdk/java/net/httpclient/whitebox/DeadlineOverflowTestDriver.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8368528 + * @summary Verifies that `Deadline` returns extremums on numeric overflows + * @modules java.net.http/jdk.internal.net.http.common:+open + * @run junit java.net.http/jdk.internal.net.http.common.DeadlineOverflowTest + */ diff --git a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/common/DeadlineOverflowTest.java b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/common/DeadlineOverflowTest.java new file mode 100644 index 0000000000000..2d3d32b084d6a --- /dev/null +++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/common/DeadlineOverflowTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.net.http.common; + +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import static java.time.temporal.ChronoUnit.NANOS; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DeadlineOverflowTest { + + @Test + void test_DeadlineOf_InstantMin() { + assertEquals(Instant.MIN, Deadline.of(Instant.MIN).asInstant()); + } + + @Test + void test_DeadlineOf_InstantMax() { + assertEquals(Instant.MAX, Deadline.of(Instant.MAX).asInstant()); + } + + @Test + void test_plusNanos_min() { + assertEquals(Deadline.MIN, Deadline.MIN.plusNanos(-1)); + } + + @Test + void test_plusNanos_max() { + assertEquals(Deadline.MAX, Deadline.MAX.plusNanos(1)); + } + + @Test + void test_minus_min() { + assertEquals(Deadline.MIN, Deadline.MIN.minus(Duration.ofNanos(1))); + } + + @Test + void test_minus_max() { + assertEquals(Deadline.MAX, Deadline.MAX.minus(Duration.ofNanos(-1))); + } + + @Test + void test_plusAmount_min() { + assertEquals(Deadline.MIN, Deadline.MIN.plus(-1, ChronoUnit.NANOS)); + } + + @Test + void test_plusAmount_max() { + assertEquals(Deadline.MAX, Deadline.MAX.plus(1, ChronoUnit.NANOS)); + } + + @Test + void test_plusSeconds_min() { + assertEquals(Deadline.MIN, Deadline.MIN.plusSeconds(-1)); + } + + @Test + void test_plusSeconds_max() { + assertEquals(Deadline.MAX, Deadline.MAX.plusSeconds(1)); + } + + @Test + void test_plusMillis_min() { + assertEquals(Deadline.MIN, Deadline.MIN.plusMillis(-1)); + } + + @Test + void test_plusMillis_max() { + assertEquals(Deadline.MAX, Deadline.MAX.plusMillis(1)); + } + + @Test + void test_plusDuration_min() { + assertEquals(Deadline.MIN, Deadline.MIN.plus(Duration.ofNanos(-1))); + } + + @Test + void test_plusDuration_max() { + assertEquals(Deadline.MAX, Deadline.MAX.plus(Duration.ofNanos(1))); + } + + @Test + void test_until_min() { + assertEquals(Long.MIN_VALUE, Deadline.MAX.until(Deadline.MIN, NANOS)); + } + + @Test + void test_until_max() { + assertEquals(Long.MAX_VALUE, Deadline.MIN.until(Deadline.MAX, NANOS)); + } + + @Test + void test_between_min() { + Duration delta = Duration.between(Instant.MAX, Instant.MIN); + assertEquals(delta, Deadline.between(Deadline.MAX, Deadline.MIN)); + } + + @Test + void test_between_max() { + Duration delta = Duration.between(Instant.MIN, Instant.MAX); + assertEquals(delta, Deadline.between(Deadline.MIN, Deadline.MAX)); + } + +}