8157105: HTTP/2 client hangs in blocking mode if an invalid frame has been received
authormichaelm
Mon, 23 May 2016 12:38:48 +0100
changeset 38464 08512546de5f
parent 38463 9cc68bcd7993
child 38465 587c3935e850
8157105: HTTP/2 client hangs in blocking mode if an invalid frame has been received Reviewed-by: rriggs
jdk/src/java.httpclient/share/classes/java/net/http/AsyncSSLDelegate.java
jdk/src/java.httpclient/share/classes/java/net/http/Http2Connection.java
jdk/src/java.httpclient/share/classes/java/net/http/Queue.java
jdk/src/java.httpclient/share/classes/java/net/http/Stream.java
jdk/src/java.httpclient/share/classes/java/net/http/Utils.java
jdk/test/java/net/httpclient/http2/ErrorTest.java
--- a/jdk/src/java.httpclient/share/classes/java/net/http/AsyncSSLDelegate.java	Mon May 23 10:51:21 2016 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/AsyncSSLDelegate.java	Mon May 23 12:38:48 2016 +0100
@@ -217,8 +217,8 @@
             }
             returnBuffers(buffers);
         } catch (Throwable t) {
-            t.printStackTrace();
             close();
+            errorHandler.accept(t);
         }
     }
 
@@ -230,8 +230,8 @@
                 doHandshakeImpl(r);
                 channelInputQ.registerPutCallback(this::upperRead);
             } catch (Throwable t) {
-                t.printStackTrace();
                 close();
+                errorHandler.accept(t);
             }
         });
     }
@@ -510,7 +510,7 @@
                     }
                 }
             } catch (Throwable t) {
-                Utils.close(lowerOutput);
+                close();
                 errorHandler.accept(t);
             }
         }
--- a/jdk/src/java.httpclient/share/classes/java/net/http/Http2Connection.java	Mon May 23 10:51:21 2016 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Http2Connection.java	Mon May 23 12:38:48 2016 +0100
@@ -364,8 +364,7 @@
     }
 
     void shutdown(Throwable t) {
-        System.err.println("Shutdown: " + t);
-        t.printStackTrace();
+        Log.logError(t);
         closed = true;
         client2.deleteConnection(this);
         Collection<Stream> c = streams.values();
--- a/jdk/src/java.httpclient/share/classes/java/net/http/Queue.java	Mon May 23 10:51:21 2016 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Queue.java	Mon May 23 12:38:48 2016 +0100
@@ -105,6 +105,8 @@
             while (q.size() == 0) {
                 waiters++;
                 wait();
+                if (closed)
+                    throw new IOException("Queue closed");
                 waiters--;
             }
             return q.removeFirst();
--- a/jdk/src/java.httpclient/share/classes/java/net/http/Stream.java	Mon May 23 10:51:21 2016 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Stream.java	Mon May 23 12:38:48 2016 +0100
@@ -32,6 +32,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -420,7 +421,7 @@
             }
         } catch (TimeoutException e) {
             throw new HttpTimeoutException("Response timed out");
-        } catch (InterruptedException | ExecutionException e) {
+        } catch (InterruptedException | ExecutionException | CompletionException e) {
             Throwable t = e.getCause();
             if (t instanceof IOException) {
                 throw (IOException)t;
@@ -636,6 +637,7 @@
     void cancelImpl(Throwable e) {
         Log.logTrace("cancelling stream: {0}\n", e.toString());
         inputQ.close();
+        completeResponseExceptionally(e);
         try {
             connection.resetStream(streamid, ResetFrame.CANCEL);
         } catch (IOException | InterruptedException ex) {
--- a/jdk/src/java.httpclient/share/classes/java/net/http/Utils.java	Mon May 23 10:51:21 2016 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Utils.java	Mon May 23 12:38:48 2016 +0100
@@ -317,7 +317,6 @@
 
     static void close(Closeable... chans) {
         for (Closeable chan : chans) {
-            System.err.println("Closing " + chan);
             try {
                 chan.close();
             } catch (IOException e) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/http2/ErrorTest.java	Mon May 23 12:38:48 2016 +0100
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2015, 2016, 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 8157105
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @modules java.httpclient
+ * @compile/module=java.httpclient java/net/http/BodyOutputStream.java
+ * @compile/module=java.httpclient java/net/http/BodyInputStream.java
+ * @compile/module=java.httpclient java/net/http/EchoHandler.java
+ * @compile/module=java.httpclient java/net/http/Http2Handler.java
+ * @compile/module=java.httpclient java/net/http/Http2TestExchange.java
+ * @compile/module=java.httpclient java/net/http/Http2TestServerConnection.java
+ * @compile/module=java.httpclient java/net/http/Http2TestServer.java
+ * @compile/module=java.httpclient java/net/http/OutgoingPushPromise.java
+ * @compile/module=java.httpclient java/net/http/TestUtil.java
+ * @run testng/othervm -Djava.net.http.HttpClient.log=ssl,errors ErrorTest
+ * @summary check exception thrown when bad TLS parameters selected
+ */
+
+import java.io.*;
+import java.net.*;
+import java.net.http.*;
+import static java.net.http.HttpClient.Version.HTTP_2;
+import javax.net.ssl.*;
+import java.nio.file.*;
+import java.util.concurrent.*;
+import jdk.testlibrary.SimpleSSLContext;
+
+
+import org.testng.annotations.Test;
+import org.testng.annotations.Parameters;
+
+/**
+ * When selecting an unacceptable cipher suite the TLS handshake will fail.
+ * But, the exception that was thrown was not being returned up to application
+ * causing hang problems
+ */
+@Test
+public class ErrorTest {
+    static int httpsPort;
+    static Http2TestServer httpsServer;
+    static HttpClient client = null;
+    static ExecutorService exec;
+    static SSLContext sslContext;
+
+    static String httpsURIString;
+
+    static HttpClient getClient() {
+        if (client == null) {
+            client = HttpClient.create()
+                .sslContext(sslContext)
+                .sslParameters(new SSLParameters(
+                    new String[]{"TLS_KRB5_WITH_3DES_EDE_CBC_SHA"}))
+                .version(HTTP_2)
+                .build();
+        }
+        return client;
+    }
+
+    static URI getURI() {
+        return URI.create(httpsURIString);
+    }
+
+    static final String SIMPLE_STRING = "Hello world Goodbye world";
+
+    @Test(timeOut=5000)
+    static void test() throws Exception {
+        try {
+            SimpleSSLContext sslct = new SimpleSSLContext();
+            sslContext = sslct.get();
+            client = getClient();
+            exec = client.executorService();
+
+            httpsServer = new Http2TestServer(true, 0, new EchoHandler(),
+                    exec, sslContext);
+
+            httpsPort = httpsServer.getAddress().getPort();
+            httpsURIString = "https://127.0.0.1:" +
+                Integer.toString(httpsPort) + "/bar/";
+
+            httpsServer.start();
+            URI uri = getURI();
+            System.err.println("Request to " + uri);
+
+            HttpClient client = getClient();
+            HttpRequest req = client.request(uri)
+                    .body(HttpRequest.fromString(SIMPLE_STRING))
+                    .POST();
+            HttpResponse response = null;
+            try {
+                response = req.response();
+                throw new RuntimeException("Expected exception");
+            } catch (IOException e) {
+                System.err.println("Expected IOException received " + e);
+            }
+            System.err.println("DONE");
+        } finally {
+            httpsServer.stop();
+            exec.shutdownNow();
+        }
+    }
+}