8180044: java/net/httpclient/ManyRequests.java failed due to timeout

Fixes several race conditions observed while testing.

Reviewed-by: michaelm, msheppar, prappo
This commit is contained in:
Daniel Fuchs 2017-06-08 12:41:07 +01:00
parent 7a520e19a1
commit 3d902cf92a
6 changed files with 140 additions and 18 deletions

View File

@ -344,7 +344,13 @@ class HttpClientImpl extends HttpClient {
c.configureBlocking(false);
SelectionKey key = c.keyFor(selector);
SelectorAttachment sa;
if (key == null) {
if (key == null || !key.isValid()) {
if (key != null) {
// key is canceled.
// invoke selectNow() to purge it
// before registering the new event.
selector.selectNow();
}
sa = new SelectorAttachment(c, selector);
} else {
sa = (SelectorAttachment) key.attachment();

View File

@ -62,6 +62,7 @@ class PlainHttpConnection extends HttpConnection implements AsyncConnection {
private volatile Consumer<ByteBufferReference> asyncReceiver;
private volatile Consumer<Throwable> errorReceiver;
private volatile Supplier<ByteBufferReference> readBufferSupplier;
private boolean asyncReading;
private final AsyncWriteQueue asyncOutputQ = new AsyncWriteQueue(this::asyncOutput);
@ -70,6 +71,9 @@ class PlainHttpConnection extends HttpConnection implements AsyncConnection {
@Override
public void startReading() {
try {
synchronized(reading) {
asyncReading = true;
}
client.registerEvent(new ReadEvent());
} catch (IOException e) {
shutdown();
@ -78,6 +82,9 @@ class PlainHttpConnection extends HttpConnection implements AsyncConnection {
@Override
public void stopAsyncReading() {
synchronized(reading) {
asyncReading = false;
}
client.cancelRegistration(chan);
}
@ -279,7 +286,7 @@ class PlainHttpConnection extends HttpConnection implements AsyncConnection {
void asyncRead() {
synchronized (reading) {
try {
while (true) {
while (asyncReading) {
ByteBufferReference buf = readBufferSupplier.get();
int n = chan.read(buf.get());
if (n == -1) {
@ -325,7 +332,7 @@ class PlainHttpConnection extends HttpConnection implements AsyncConnection {
return -1;
}
Utils.flipToMark(buf, mark);
String s = "Receive (" + n + " bytes) ";
// String s = "Receive (" + n + " bytes) ";
//debugPrint(s, buf);
return n;
}
@ -393,6 +400,10 @@ class PlainHttpConnection extends HttpConnection implements AsyncConnection {
shutdown();
}
@Override
public String toString() {
return super.toString() + "/" + chan;
}
}
// used in blocking channels only
@ -422,6 +433,11 @@ class PlainHttpConnection extends HttpConnection implements AsyncConnection {
public void abort() {
close();
}
@Override
public String toString() {
return super.toString() + "/" + chan;
}
}
@Override
@ -447,7 +463,8 @@ class PlainHttpConnection extends HttpConnection implements AsyncConnection {
CompletableFuture<Void> whenReceivingResponse() {
CompletableFuture<Void> cf = new MinimalFuture<>();
try {
client.registerEvent(new ReceiveResponseEvent(cf));
ReceiveResponseEvent evt = new ReceiveResponseEvent(cf);
client.registerEvent(evt);
} catch (IOException e) {
cf.completeExceptionally(e);
}

View File

@ -803,7 +803,9 @@ class Stream<T> extends ExchangeImpl<T> {
completeResponseExceptionally(e);
try {
// will send a RST_STREAM frame
connection.resetStream(streamid, ResetFrame.CANCEL);
if (streamid != 0) {
connection.resetStream(streamid, ResetFrame.CANCEL);
}
} catch (IOException ex) {
Log.logError(ex);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2017, 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
@ -214,8 +214,8 @@ class EchoHandler implements HttpHandler {
t.sendResponseHeaders(200, in.length);
OutputStream os = t.getResponseBody();
os.write(in);
os.close();
is.close();
close(os);
close(is);
} else {
OutputStream os = t.getResponseBody();
byte[] buf = new byte[64 * 1024];
@ -232,9 +232,15 @@ class EchoHandler implements HttpHandler {
String s = Integer.toString(count);
os.write(s.getBytes());
}
close(os);
close(is);
}
}
protected void close(OutputStream os) throws IOException {
os.close();
}
protected void close(InputStream is) throws IOException {
is.close();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2017, 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
@ -23,7 +23,7 @@
/*
* @test
* @bug 8087112
* @bug 8087112 8180044
* @modules jdk.incubator.httpclient
* java.logging
* jdk.httpserver
@ -32,13 +32,20 @@
* @compile ../../../com/sun/net/httpserver/LogFilter.java
* @compile ../../../com/sun/net/httpserver/FileServerHandler.java
* @run main/othervm/timeout=40 -Djdk.httpclient.HttpClient.log=ssl ManyRequests
* @run main/othervm/timeout=40 -Dtest.insertDelay=true ManyRequests
* @run main/othervm/timeout=40 -Dtest.chunkSize=64 ManyRequests
* @run main/othervm/timeout=40 -Dtest.insertDelay=true -Dtest.chunkSize=64 ManyRequests
* @summary Send a large number of requests asynchronously
*/
// * @run main/othervm/timeout=40 -Djdk.httpclient.HttpClient.log=ssl ManyRequests
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import jdk.incubator.http.HttpClient;
import jdk.incubator.http.HttpRequest;
import java.net.InetSocketAddress;
@ -65,7 +72,10 @@ public class ManyRequests {
Logger logger = Logger.getLogger("com.sun.net.httpserver");
logger.setLevel(Level.ALL);
logger.info("TEST");
System.out.println("Sending " + REQUESTS
+ " requests; delay=" + INSERT_DELAY
+ ", chunks=" + CHUNK_SIZE
+ ", XFixed=" + XFIXED);
SSLContext ctx = new SimpleSSLContext().get();
InetSocketAddress addr = new InetSocketAddress(0);
@ -86,11 +96,36 @@ public class ManyRequests {
//static final int REQUESTS = 1000;
static final int REQUESTS = 20;
static final boolean INSERT_DELAY = Boolean.getBoolean("test.insertDelay");
static final int CHUNK_SIZE = Math.max(0,
Integer.parseInt(System.getProperty("test.chunkSize", "0")));
static final boolean XFIXED = Boolean.getBoolean("test.XFixed");
static class TestEchoHandler extends EchoHandler {
final Random rand = new Random();
@Override
public void handle(HttpExchange e) throws IOException {
System.out.println("Server: received " + e.getRequestURI());
super.handle(e);
}
protected void close(OutputStream os) throws IOException {
if (INSERT_DELAY) {
try { Thread.sleep(rand.nextInt(200)); } catch (InterruptedException e) {}
}
super.close(os);
}
protected void close(InputStream is) throws IOException {
if (INSERT_DELAY) {
try { Thread.sleep(rand.nextInt(200)); } catch (InterruptedException e) {}
}
super.close(is);
}
}
static void test(HttpsServer server, HttpClient client) throws Exception {
int port = server.getAddress().getPort();
URI uri = new URI("https://127.0.0.1:" + port + "/foo/x");
server.createContext("/foo", new EchoHandler());
URI baseURI = new URI("https://127.0.0.1:" + port + "/foo/x");
server.createContext("/foo", new TestEchoHandler());
server.start();
RequestLimiter limiter = new RequestLimiter(40);
@ -99,24 +134,32 @@ public class ManyRequests {
HashMap<HttpRequest,byte[]> bodies = new HashMap<>();
for (int i=0; i<REQUESTS; i++) {
byte[] buf = new byte[i+1]; // different size bodies
byte[] buf = new byte[(i+1)*CHUNK_SIZE+i+1]; // different size bodies
rand.nextBytes(buf);
URI uri = new URI(baseURI.toString() + String.valueOf(i+1));
HttpRequest r = HttpRequest.newBuilder(uri)
.header("XFixed", "true")
.POST(fromByteArray(buf))
.build();
bodies.put(r, buf);
results[i] =
limiter.whenOkToSend()
.thenCompose((v) -> client.sendAsync(r, asByteArray()))
.thenCompose((v) -> {
System.out.println("Client: sendAsync: " + r.uri());
return client.sendAsync(r, asByteArray());
})
.thenCompose((resp) -> {
limiter.requestComplete();
if (resp.statusCode() != 200) {
String s = "Expected 200, got: " + resp.statusCode();
System.out.println(s + " from "
+ resp.request().uri().getPath());
return completedWithIOException(s);
} else {
counter++;
System.out.println("Result from " + counter);
System.out.println("Result (" + counter + ") from "
+ resp.request().uri().getPath());
}
return CompletableFuture.completedStage(resp.body())
.thenApply((b) -> new Pair<>(resp, b));

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2017, 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 8087112 8180044
* @modules jdk.incubator.httpclient
* java.logging
* jdk.httpserver
* @library /lib/testlibrary/ /
* @build jdk.testlibrary.SimpleSSLContext EchoHandler
* @compile ../../../com/sun/net/httpserver/LogFilter.java
* @compile ../../../com/sun/net/httpserver/FileServerHandler.java
* @build ManyRequests ManyRequests2
* @run main/othervm/timeout=40 -Dtest.XFixed=true ManyRequests2
* @run main/othervm/timeout=40 -Dtest.XFixed=true -Dtest.insertDelay=true ManyRequests2
* @run main/othervm/timeout=40 -Dtest.XFixed=true -Dtest.chunkSize=64 ManyRequests2
* @run main/othervm/timeout=40 -Dtest.XFixed=true -Dtest.insertDelay=true -Dtest.chunkSize=64 ManyRequests2
* @summary Send a large number of requests asynchronously. The server echoes back using known content length.
*/
// * @run main/othervm/timeout=40 -Djdk.httpclient.HttpClient.log=ssl ManyRequests
public class ManyRequests2 {
public static void main(String[] args) throws Exception {
ManyRequests.main(args);
}
}