8206001: Enable TLS1.3 by default in Http Client
authormichaelm
Wed, 04 Jul 2018 16:16:24 +0100
changeset 50985 cd41f34e548c
parent 50984 f1f4b8cd0192
child 50986 b289815d0db3
8206001: Enable TLS1.3 by default in Http Client Reviewed-by: dfuchs
src/java.net.http/share/classes/jdk/internal/net/http/AbstractAsyncSSLConnection.java
src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1AsyncReceiver.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java
src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java
src/java.net.http/share/classes/jdk/internal/net/http/Stream.java
src/java.net.http/share/classes/jdk/internal/net/http/WindowUpdateSender.java
src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java
src/java.net.http/share/classes/jdk/internal/net/http/common/SSLTube.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/SettingsFrame.java
test/jdk/java/net/httpclient/CancelledResponse.java
test/jdk/java/net/httpclient/MockServer.java
test/jdk/java/net/httpclient/ShortResponseBody.java
test/jdk/java/net/httpclient/SplitResponse.java
test/jdk/java/net/httpclient/http2/FixedThreadPoolTest.java
test/jdk/java/net/httpclient/http2/TLSConnection.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLEchoTubeTest.java
--- a/src/java.net.http/share/classes/jdk/internal/net/http/AbstractAsyncSSLConnection.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/AbstractAsyncSSLConnection.java	Wed Jul 04 16:16:24 2018 +0100
@@ -27,6 +27,7 @@
 
 import java.net.InetSocketAddress;
 import java.util.Arrays;
+import java.util.ArrayDeque;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import javax.net.ssl.SNIHostName;
@@ -89,11 +90,30 @@
 
     final SSLEngine getEngine() { return engine; }
 
+    private static boolean contains(String[] rr, String target) {
+        for (String s : rr)
+            if (target.equalsIgnoreCase(s))
+                return true;
+        return false;
+    }
+
     private static SSLParameters createSSLParameters(HttpClientImpl client,
                                                      ServerName serverName,
                                                      String[] alpn) {
         SSLParameters sslp = client.sslParameters();
         SSLParameters sslParameters = Utils.copySSLParameters(sslp);
+        // filter out unwanted protocols, if h2 only
+        if (alpn != null && alpn.length != 0 && !contains(alpn, "http/1.1")) {
+            ArrayDeque<String> l = new ArrayDeque<>();
+            for (String proto : sslParameters.getProtocols()) {
+                if (!proto.startsWith("SSL") && !proto.endsWith("v1.1") && !proto.endsWith("v1")) {
+                    l.add(proto);
+                }
+            }
+            String[] a1 = l.toArray(new String[0]);
+            sslParameters.setProtocols(a1);
+        }
+
         if (!disableHostnameVerification)
             sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
         if (alpn != null) {
@@ -112,10 +132,12 @@
         return sslParameters;
     }
 
+
     private static SSLEngine createEngine(SSLContext context, String serverName, int port,
                                           SSLParameters sslParameters) {
         SSLEngine engine = context.createSSLEngine(serverName, port);
         engine.setUseClientMode(true);
+
         engine.setSSLParameters(sslParameters);
         return engine;
     }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java	Wed Jul 04 16:16:24 2018 +0100
@@ -316,7 +316,7 @@
                     proxyResponse.version, true);
             return MinimalFuture.completedFuture(syntheticResponse);
         } else if (t != null) {
-            if (debug.on()) debug.log("checkFor407: no response - %s", t);
+            if (debug.on()) debug.log("checkFor407: no response - %s", (Object)t);
             return MinimalFuture.failedFuture(t);
         } else {
             if (debug.on()) debug.log("checkFor407: all clear");
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1AsyncReceiver.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1AsyncReceiver.java	Wed Jul 04 16:16:24 2018 +0100
@@ -386,8 +386,11 @@
             // we have a flow List<ByteBuffer> upstream.
             Http1AsyncDelegateSubscription subscription =
                     new Http1AsyncDelegateSubscription(scheduler, cancel, onSubscriptionError);
-            pending.onSubscribe(subscription);
-            this.delegate = delegate = pending;
+            try {
+                pending.onSubscribe(subscription);
+            } finally {
+                this.delegate = delegate = pending;
+            }
             final Object captured = delegate;
             if (debug.on())
                 debug.log("delegate is now " + captured
@@ -485,10 +488,11 @@
                 error = ex;
             }
         }
-            final Throwable t = (recorded == null ? ex : recorded);
-            if (debug.on())
-                debug.log("recorded " + t + "\n\t delegate: " + delegate
-                          + "\t\t queue.isEmpty: " + queue.isEmpty(), ex);
+
+        final Throwable t = (recorded == null ? ex : recorded);
+        if (debug.on())
+            debug.log("recorded " + t + "\n\t delegate: " + delegate
+                      + "\t\t queue.isEmpty: " + queue.isEmpty(), ex);
         if (Log.errors()) {
             Log.logError("HTTP/1 read subscriber recorded error: {0} - {1}", describe(), t);
         }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java	Wed Jul 04 16:16:24 2018 +0100
@@ -257,6 +257,14 @@
                 .thenCompose(unused -> {
                     CompletableFuture<Void> cf = new MinimalFuture<>();
                     try {
+                        asyncReceiver.whenFinished.whenComplete((r,t) -> {
+                            if (t != null) {
+                                if (debug.on())
+                                    debug.log("asyncReceiver finished (failed=%s)", (Object)t);
+                                if (!headersSentCF.isDone())
+                                    headersSentCF.completeAsync(() -> this, executor);
+                            }
+                        });
                         connectFlows(connection);
 
                         if (debug.on()) debug.log("requestAction.headers");
@@ -282,7 +290,8 @@
 
     private void cancelIfFailed(Flow.Subscription s) {
         asyncReceiver.whenFinished.whenCompleteAsync((r,t) -> {
-            if (debug.on()) debug.log("asyncReceiver finished (failed=%s)", t);
+            if (debug.on())
+                debug.log("asyncReceiver finished (failed=%s)", (Object)t);
             if (t != null) {
                 s.cancel();
                 // Don't complete exceptionally here as 't'
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java	Wed Jul 04 16:16:24 2018 +0100
@@ -673,7 +673,11 @@
         client2.deleteConnection(this);
         List<Stream<?>> c = new LinkedList<>(streams.values());
         for (Stream<?> s : c) {
-            s.connectionClosing(t);
+            try {
+                s.connectionClosing(t);
+            } catch (Throwable e) {
+                Log.logError("Failed to close stream {0}: {1}", s.streamid, e);
+            }
         }
         connection.close();
     }
@@ -738,6 +742,9 @@
                 }
 
                 if (!(frame instanceof ResetFrame)) {
+                    if (frame instanceof DataFrame) {
+                        dropDataFrame((DataFrame)frame);
+                    }
                     if (isServerInitiatedStream(streamid)) {
                         if (streamid < nextPushStream) {
                             // trailing data on a cancelled push promise stream,
@@ -776,6 +783,27 @@
         }
     }
 
+    final void dropDataFrame(DataFrame df) {
+        if (closed) return;
+        if (debug.on()) {
+            debug.log("Dropping data frame for stream %d (%d payload bytes)",
+                    df.streamid(), df.payloadLength());
+        }
+        ensureWindowUpdated(df);
+    }
+
+    final void ensureWindowUpdated(DataFrame df) {
+        try {
+            if (closed) return;
+            int length = df.payloadLength();
+            if (length > 0) {
+                windowUpdater.update(length);
+            }
+        } catch(Throwable t) {
+            Log.logError("Unexpected exception while updating window: {0}", (Object)t);
+        }
+    }
+
     private <T> void handlePushPromise(Stream<T> parent, PushPromiseFrame pp)
         throws IOException
     {
@@ -984,7 +1012,6 @@
                      connection.channel().getLocalAddress(),
                      connection.address());
         SettingsFrame sf = new SettingsFrame(clientSettings);
-        int initialWindowSize = sf.getParameter(INITIAL_WINDOW_SIZE);
         ByteBuffer buf = framesEncoder.encodeConnectionPreface(PREFACE_BYTES, sf);
         Log.logFrames(sf, "OUT");
         // send preface bytes and SettingsFrame together
@@ -997,9 +1024,20 @@
         Log.logTrace("Settings Frame sent");
 
         // send a Window update for the receive buffer we are using
-        // minus the initial 64 K specified in protocol
-        final int len = windowUpdater.initialWindowSize - initialWindowSize;
-        if (len > 0) {
+        // minus the initial 64 K -1 specified in protocol:
+        // RFC 7540, Section 6.9.2:
+        // "[...] the connection flow-control window is set to the default
+        // initial window size until a WINDOW_UPDATE frame is received."
+        //
+        // Note that the default initial window size, not to be confused
+        // with the initial window size, is defined by RFC 7540 as
+        // 64K -1.
+        final int len = windowUpdater.initialWindowSize - DEFAULT_INITIAL_WINDOW_SIZE;
+        if (len != 0) {
+            if (Log.channel()) {
+                Log.logChannel("Sending initial connection window update frame: {0} ({1} - {2})",
+                        len, windowUpdater.initialWindowSize, DEFAULT_INITIAL_WINDOW_SIZE);
+            }
             windowUpdater.sendWindowUpdate(len);
         }
         // there will be an ACK to the windows update - which should
@@ -1132,6 +1170,7 @@
 
     private Stream<?> registerNewStream(OutgoingHeaders<Stream<?>> oh) {
         Stream<?> stream = oh.getAttachment();
+        assert stream.streamid == 0;
         int streamid = nextstreamid;
         nextstreamid += 2;
         stream.registerStream(streamid);
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java	Wed Jul 04 16:16:24 2018 +0100
@@ -329,7 +329,18 @@
 
     private static SSLParameters getDefaultParams(SSLContext ctx) {
         SSLParameters params = ctx.getSupportedSSLParameters();
-        params.setProtocols(new String[]{"TLSv1.2"});
+        String[] protocols = params.getProtocols();
+        boolean found13 = false;
+        for (String proto : protocols) {
+            if (proto.equals("TLSv1.3")) {
+                found13 = true;
+                break;
+            }
+        }
+        if (found13)
+            params.setProtocols(new String[] {"TLSv1.3", "TLSv1.2"});
+        else
+            params.setProtocols(new String[] {"TLSv1.2"});
         return params;
     }
 
--- a/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java	Wed Jul 04 16:16:24 2018 +0100
@@ -360,7 +360,6 @@
                 }
             } catch (Throwable t) {
                 signalError(t);
-                subscription.cancel();
             }
         }
 
@@ -424,6 +423,8 @@
             }
             completed = true;
             readPublisher.signalError(error);
+            Flow.Subscription subscription = this.subscription;
+            if (subscription != null) subscription.cancel();
         }
 
         // A repeatable WriteEvent which is paused after firing and can
@@ -468,7 +469,11 @@
 
             @Override
             public void cancel() {
+                if (cancelled) return;
                 if (debug.on()) debug.log("write: cancel");
+                if (Log.channel()) {
+                    Log.logChannel("Cancelling write subscription");
+                }
                 dropSubscription();
                 upstreamSubscription.cancel();
             }
@@ -503,9 +508,7 @@
                 } catch (Throwable t) {
                     if (debug.on())
                         debug.log("write: error while requesting more: " + t);
-                    cancelled = true;
                     signalError(t);
-                    subscription.cancel();
                 } finally {
                     debugState("leaving requestMore: ");
                 }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java	Wed Jul 04 16:16:24 2018 +0100
@@ -185,6 +185,7 @@
                 int size = Utils.remaining(dsts, Integer.MAX_VALUE);
                 if (size == 0 && finished) {
                     inputQ.remove();
+                    connection.ensureWindowUpdated(df); // must update connection window
                     Log.logTrace("responseSubscriber.onComplete");
                     if (debug.on()) debug.log("incoming: onComplete");
                     sched.stop();
@@ -197,7 +198,12 @@
                     inputQ.remove();
                     Log.logTrace("responseSubscriber.onNext {0}", size);
                     if (debug.on()) debug.log("incoming: onNext(%d)", size);
-                    subscriber.onNext(dsts);
+                    try {
+                        subscriber.onNext(dsts);
+                    } catch (Throwable t) {
+                        connection.dropDataFrame(df); // must update connection window
+                        throw t;
+                    }
                     if (consumed(df)) {
                         Log.logTrace("responseSubscriber.onComplete");
                         if (debug.on()) debug.log("incoming: onComplete");
@@ -215,6 +221,8 @@
             }
         } catch (Throwable throwable) {
             errorRef.compareAndSet(null, throwable);
+        } finally {
+            if (sched.isStopped()) drainInputQueue();
         }
 
         Throwable t = errorRef.get();
@@ -223,20 +231,35 @@
             try {
                 if (!onCompleteCalled) {
                     if (debug.on())
-                        debug.log("calling subscriber.onError: %s", (Object)t);
+                        debug.log("calling subscriber.onError: %s", (Object) t);
                     subscriber.onError(t);
                 } else {
                     if (debug.on())
-                        debug.log("already completed: dropping error %s", (Object)t);
+                        debug.log("already completed: dropping error %s", (Object) t);
                 }
             } catch (Throwable x) {
-                Log.logError("Subscriber::onError threw exception: {0}", (Object)t);
+                Log.logError("Subscriber::onError threw exception: {0}", (Object) t);
             } finally {
                 cancelImpl(t);
+                drainInputQueue();
             }
         }
     }
 
+    // must only be called from the scheduler schedule() loop.
+    // ensure that all received data frames are accounted for
+    // in the connection window flow control if the scheduler
+    // is stopped before all the data is consumed.
+    private void drainInputQueue() {
+        Http2Frame frame;
+        while ((frame = inputQ.poll()) != null) {
+            if (frame instanceof DataFrame) {
+                connection.dropDataFrame((DataFrame)frame);
+            }
+        }
+    }
+
+
     // Callback invoked after the Response BodySubscriber has consumed the
     // buffers contained in a DataFrame.
     // Returns true if END_STREAM is reached, false otherwise.
@@ -245,15 +268,19 @@
         // The entire DATA frame payload is included in flow control,
         // including the Pad Length and Padding fields if present
         int len = df.payloadLength();
+        boolean endStream = df.getFlag(DataFrame.END_STREAM);
+        if (len == 0) return endStream;
+
         connection.windowUpdater.update(len);
 
-        if (!df.getFlag(DataFrame.END_STREAM)) {
+        if (!endStream) {
             // Don't send window update on a stream which is
             // closed or half closed.
             windowUpdater.update(len);
-            return false; // more data coming
         }
-        return true; // end of stream
+
+        // true: end of stream; false: more data coming
+        return endStream;
     }
 
     boolean deRegister() {
@@ -500,8 +527,8 @@
     {
         int amount = frame.getUpdate();
         if (amount <= 0) {
-            Log.logTrace("Resetting stream: {0} %d, Window Update amount: %d\n",
-                         streamid, streamid, amount);
+            Log.logTrace("Resetting stream: {0}, Window Update amount: {1}",
+                         streamid, amount);
             connection.resetStream(streamid, ResetFrame.FLOW_CONTROL_ERROR);
         } else {
             assert streamid != 0;
@@ -1126,7 +1153,7 @@
                     connection.resetStream(streamid, ResetFrame.CANCEL);
                 }
             }
-        } catch (IOException ex) {
+        } catch (Throwable ex) {
             Log.logError(ex);
         }
     }
@@ -1289,6 +1316,18 @@
         int getStreamId() {
             return streamid;
         }
+
+        @Override
+        String dbgString() {
+            String dbg = dbgString;
+            if (dbg != null) return dbg;
+            if (streamid == 0) {
+                return connection.dbgString() + ":WindowUpdateSender(stream: ?)";
+            } else {
+                dbg = connection.dbgString() + ":WindowUpdateSender(stream: " + streamid + ")";
+                return dbgString = dbg;
+            }
+        }
     }
 
     /**
--- a/src/java.net.http/share/classes/jdk/internal/net/http/WindowUpdateSender.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/WindowUpdateSender.java	Wed Jul 04 16:16:24 2018 +0100
@@ -25,6 +25,7 @@
 
 package jdk.internal.net.http;
 
+import jdk.internal.net.http.common.FlowTube;
 import jdk.internal.net.http.common.Logger;
 import jdk.internal.net.http.frame.SettingsFrame;
 import jdk.internal.net.http.frame.WindowUpdateFrame;
@@ -66,8 +67,9 @@
     abstract int getStreamId();
 
     void update(int delta) {
-        if (debug.on()) debug.log("update: %d", delta);
-        if (received.addAndGet(delta) > limit) {
+        int rcv = received.addAndGet(delta);
+        if (debug.on()) debug.log("update: %d, received: %d, limit: %d", delta, rcv, limit);
+        if (rcv > limit) {
             synchronized (this) {
                 int tosend = received.get();
                 if( tosend > limit) {
@@ -83,8 +85,18 @@
         connection.sendUnorderedFrame(new WindowUpdateFrame(getStreamId(), delta));
     }
 
+    volatile String dbgString;
     String dbgString() {
-        return "WindowUpdateSender(stream: " + getStreamId() + ")";
+        String dbg = dbgString;
+        if (dbg != null) return dbg;
+        FlowTube tube = connection.connection.getConnectionFlow();
+        if (tube == null) {
+            return "WindowUpdateSender(stream: " + getStreamId() + ")";
+        } else {
+            int streamId = getStreamId();
+            dbg = connection.dbgString() + ":WindowUpdateSender(stream: " + streamId + ")";
+            return streamId == 0 ? dbg : (dbgString = dbg);
+        }
     }
 
 }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java	Wed Jul 04 16:16:24 2018 +0100
@@ -33,6 +33,9 @@
 import javax.net.ssl.SSLEngineResult.Status;
 import javax.net.ssl.SSLException;
 import java.io.IOException;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -93,6 +96,8 @@
     // When handshake is in progress trying to wrap may produce no bytes.
     private static final ByteBuffer NOTHING = ByteBuffer.allocate(0);
     private static final String monProp = Utils.getProperty("jdk.internal.httpclient.monitorFlowDelegate");
+    private static final boolean isMonitored =
+            monProp != null && (monProp.equals("") || monProp.equalsIgnoreCase("true"));
 
     final Executor exec;
     final Reader reader;
@@ -100,6 +105,7 @@
     final SSLEngine engine;
     final String tubeName; // hack
     final CompletableFuture<String> alpnCF; // completes on initial handshake
+    final Monitorable monitor = isMonitored ? this::monitor : null; // prevent GC until SSLFD is stopped
     volatile boolean close_notify_received;
     final CompletableFuture<Void> readerCF;
     final CompletableFuture<Void> writerCF;
@@ -152,8 +158,7 @@
         // Writer to the downWriter.
         connect(downReader, downWriter);
 
-        if (monProp != null && (monProp.equals("") || monProp.equalsIgnoreCase("true")))
-            Monitor.add(this::monitor);
+        if (isMonitored) Monitor.add(monitor);
     }
 
     /**
@@ -202,6 +207,7 @@
     public String monitor() {
         StringBuilder sb = new StringBuilder();
         sb.append("SSL: id ").append(id);
+        sb.append(" ").append(dbgString());
         sb.append(" HS state: " + states(handshakeState));
         sb.append(" Engine state: " + engine.getHandshakeStatus().toString());
         if (stateList != null) {
@@ -293,8 +299,10 @@
 
         @Override
         public String toString() {
-            return "READER: " + super.toString() + " readBuf: " + readBuf.toString()
-                    + " count: " + count.toString();
+            return "READER: " + super.toString() + ", readBuf: " + readBuf.toString()
+                    + ", count: " + count.toString() + ", scheduler: "
+                    + (scheduler.isStopped() ? "stopped" : "running")
+                    + ", status: " + lastUnwrapStatus;
         }
 
         private void reallocReadBuf() {
@@ -335,6 +343,7 @@
                 }
                 if (complete) {
                     this.completing = complete;
+                    minBytesRequired = 0;
                 }
             }
         }
@@ -395,13 +404,23 @@
                             // not enough data in the read buffer...
                             // no need to try to unwrap again unless we get more bytes
                             // than minBytesRequired = len in the read buffer.
-                            minBytesRequired = len;
                             synchronized (readBufferLock) {
+                                minBytesRequired = len;
                                 // more bytes could already have been added...
                                 assert readBuf.remaining() >= len;
                                 // check if we have received some data, and if so
                                 // we can just re-spin the loop
                                 if (readBuf.remaining() > len) continue;
+                                else if (this.completing) {
+                                    if (debug.on()) {
+                                        debugr.log("BUFFER_UNDERFLOW with EOF," +
+                                                " %d bytes non decrypted.", len);
+                                    }
+                                    // The channel won't send us any more data, and
+                                    // we are in underflow: we need to fail.
+                                    throw new IOException("BUFFER_UNDERFLOW with EOF, "
+                                            + len + " bytes non decrypted.");
+                                }
                             }
                             // request more data and return.
                             requestMore();
@@ -429,6 +448,7 @@
                     } catch (IOException ex) {
                         errorCommon(ex);
                         handleError(ex);
+                        return;
                     }
                     if (handshaking && !complete)
                         return;
@@ -452,12 +472,13 @@
             }
         }
 
+        private volatile Status lastUnwrapStatus;
         EngineResult unwrapBuffer(ByteBuffer src) throws IOException {
             ByteBuffer dst = getAppBuffer();
             int len = src.remaining();
             while (true) {
                 SSLEngineResult sslResult = engine.unwrap(src, dst);
-                switch (sslResult.getStatus()) {
+                switch (lastUnwrapStatus = sslResult.getStatus()) {
                     case BUFFER_OVERFLOW:
                         // may happen if app size buffer was changed, or if
                         // our 'adaptiveBufferSize' guess was too small for
@@ -507,7 +528,9 @@
     }
 
     public static class Monitor extends Thread {
-        final List<Monitorable> list;
+        final List<WeakReference<Monitorable>> list;
+        final List<FinalMonitorable> finalList;
+        final ReferenceQueue<Monitorable> queue = new ReferenceQueue<>();
         static Monitor themon;
 
         static {
@@ -515,19 +538,61 @@
             themon.start(); // uncomment to enable Monitor
         }
 
+        // An instance used to temporarily store the
+        // last observable state of a monitorable object.
+        // When Monitor.remove(o) is called, we replace
+        // 'o' with a FinalMonitorable whose reference
+        // will be enqueued after the last observable state
+        // has been printed.
+        final class FinalMonitorable implements Monitorable {
+            final String finalState;
+            FinalMonitorable(Monitorable o) {
+                finalState = o.getInfo();
+                finalList.add(this);
+            }
+            @Override
+            public String getInfo() {
+                finalList.remove(this);
+                return finalState;
+            }
+        }
+
         Monitor() {
             super("Monitor");
             setDaemon(true);
             list = Collections.synchronizedList(new LinkedList<>());
+            finalList = new ArrayList<>(); // access is synchronized on list above
         }
 
         void addTarget(Monitorable o) {
-            list.add(o);
+            list.add(new WeakReference<>(o, queue));
+        }
+        void removeTarget(Monitorable o) {
+            // It can take a long time for GC to clean up references.
+            // Calling Monitor.remove() early helps removing noise from the
+            // logs/
+            synchronized (list) {
+                Iterator<WeakReference<Monitorable>> it = list.iterator();
+                while (it.hasNext()) {
+                    Monitorable m = it.next().get();
+                    if (m == null) it.remove();
+                    if (o == m) {
+                        it.remove();
+                        break;
+                    }
+                }
+                FinalMonitorable m = new FinalMonitorable(o);
+                addTarget(m);
+                Reference.reachabilityFence(m);
+            }
         }
 
         public static void add(Monitorable o) {
             themon.addTarget(o);
         }
+        public static void remove(Monitorable o) {
+            themon.removeTarget(o);
+        }
 
         @Override
         public void run() {
@@ -536,7 +601,14 @@
                 while (true) {
                     Thread.sleep(20 * 1000);
                     synchronized (list) {
-                        for (Monitorable o : list) {
+                        Reference<? extends Monitorable> expired;
+                        while ((expired = queue.poll()) != null) list.remove(expired);
+                        for (WeakReference<Monitorable> ref : list) {
+                            Monitorable o = ref.get();
+                            if (o == null) continue;
+                            if (o instanceof FinalMonitorable) {
+                                ref.enqueue();
+                            }
                             System.out.println(o.getInfo());
                             System.out.println("-------------------------");
                         }
@@ -733,6 +805,7 @@
         // downstream. Otherwise, we send the writeBuffer downstream
         // and will allocate a new one next time.
         volatile ByteBuffer writeBuffer;
+        private volatile Status lastWrappedStatus;
         @SuppressWarnings("fallthrough")
         EngineResult wrapBuffers(ByteBuffer[] src) throws SSLException {
             long len = Utils.remaining(src);
@@ -747,7 +820,7 @@
             while (true) {
                 SSLEngineResult sslResult = engine.wrap(src, dst);
                 if (debugw.on()) debugw.log("SSLResult: " + sslResult);
-                switch (sslResult.getStatus()) {
+                switch (lastWrappedStatus = sslResult.getStatus()) {
                     case BUFFER_OVERFLOW:
                         // Shouldn't happen. We allocated buffer with packet size
                         // get it again if net buffer size was changed
@@ -815,8 +888,10 @@
 
         @Override
         public String toString() {
-            return "WRITER: " + super.toString() +
-                    " writeList size " + Integer.toString(writeList.size());
+            return "WRITER: " + super.toString()
+                    + ", writeList size: " + Integer.toString(writeList.size())
+                    + ", scheduler: " + (scheduler.isStopped() ? "stopped" : "running")
+                    + ", status: " + lastWrappedStatus;
                     //" writeList: " + writeList.toString();
         }
     }
@@ -839,6 +914,7 @@
         stopped = true;
         reader.stop();
         writer.stop();
+        if (isMonitored) Monitor.remove(monitor);
     }
 
     private Void stopOnError(Throwable currentlyUnused) {
@@ -953,6 +1029,10 @@
             case NEED_UNWRAP_AGAIN:
                 // do nothing else
                 // receiving-side data will trigger unwrap
+                if (caller == WRITER) {
+                    reader.schedule();
+                    return false;
+                }
                 break;
             default:
                 throw new InternalError("Unexpected handshake status:"
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLTube.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLTube.java	Wed Jul 04 16:16:24 2018 +0100
@@ -406,6 +406,21 @@
             }
         }
 
+        private void complete(DelegateWrapper subscriberImpl, Throwable t) {
+            try {
+                if (t == null) subscriberImpl.onComplete();
+                else subscriberImpl.onError(t);
+                if (debug.on()) {
+                    debug.log("subscriber completed %s"
+                            + ((t == null) ? "normally" : ("with error: " + t)));
+                }
+            } finally {
+                // Error or EOF while reading:
+                // cancel write side after completing read side
+                writeSubscription.cancel();
+            }
+        }
+
         private void onNewSubscription(DelegateWrapper subscriberImpl,
                                        Flow.Subscription subscription) {
             assert subscriberImpl != null;
@@ -432,13 +447,13 @@
                 if (debug.on())
                     debug.log("onNewSubscription: subscriberImpl:%s, invoking onError:%s",
                               subscriberImpl, failed);
-                subscriberImpl.onError(failed);
+                complete(subscriberImpl, failed);
             } else if (completed) {
                 if (debug.on())
                     debug.log("onNewSubscription: subscriberImpl:%s, invoking onCompleted",
                               subscriberImpl);
                 finished = true;
-                subscriberImpl.onComplete();
+                complete(subscriberImpl, null);
             }
         }
 
@@ -463,7 +478,7 @@
                 subscriberImpl = subscribed;
             }
             if (subscriberImpl != null) {
-                subscriberImpl.onError(failed);
+                complete(subscriberImpl, failed);
             } else {
                 if (debug.on())
                     debug.log("%s: delegate null, stored %s", this, failed);
@@ -485,14 +500,22 @@
             return !(hs == NOT_HANDSHAKING || hs == FINISHED);
         }
 
-        private boolean handshakeFailed() {
+        private String handshakeFailed() {
             // sslDelegate can be null if we reach here
             // during the initial handshake, as that happens
             // within the SSLFlowDelegate constructor.
             // In that case we will want to raise an exception.
-            return handshaking()
+            if (handshaking()
                     && (sslDelegate == null
-                    || !sslDelegate.closeNotifyReceived());
+                    || !sslDelegate.closeNotifyReceived())) {
+                return "Remote host terminated the handshake";
+            }
+            // The initial handshake may not have been started yet.
+            // In which case - if we are completed before the initial handshake
+            // is started, we consider this a handshake failure as well.
+            if ("SSL_NULL_WITH_NULL_NULL".equals(engine.getSession().getCipherSuite()))
+                return "Remote host closed the channel";
+            return null;
         }
 
         @Override
@@ -503,17 +526,18 @@
                 subscriberImpl = subscribed;
             }
 
-            if (handshakeFailed()) {
+            String handshakeFailed = handshakeFailed();
+            if (handshakeFailed != null) {
                 if (debug.on())
-                    debug.log("handshake: %s, inbound done: %s outbound done: %s",
+                    debug.log("handshake: %s, inbound done: %s, outbound done: %s: %s",
                               engine.getHandshakeStatus(),
                               engine.isInboundDone(),
-                              engine.isOutboundDone());
-                onErrorImpl(new SSLHandshakeException(
-                        "Remote host terminated the handshake"));
+                              engine.isOutboundDone(),
+                              handshakeFailed);
+                onErrorImpl(new SSLHandshakeException(handshakeFailed));
             } else if (subscriberImpl != null) {
                 onCompleteReceived = finished = true;
-                subscriberImpl.onComplete();
+                complete(subscriberImpl, null);
             } else {
                 onCompleteReceived = true;
             }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/frame/SettingsFrame.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/SettingsFrame.java	Wed Jul 04 16:16:24 2018 +0100
@@ -161,14 +161,19 @@
         }
     }
 
+    public static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * K -1;
+    public static final int DEFAULT_HEADER_TABLE_SIZE = 4 * K;
+    public static final int DEFAULT_MAX_CONCURRENT_STREAMS = 100;
+    public static final int DEFAULT_MAX_FRAME_SIZE = 16 * K;
+
     public static SettingsFrame getDefaultSettings() {
         SettingsFrame f = new SettingsFrame();
         // TODO: check these values
         f.setParameter(ENABLE_PUSH, 1);
-        f.setParameter(HEADER_TABLE_SIZE, 4 * K);
-        f.setParameter(MAX_CONCURRENT_STREAMS, 100);
-        f.setParameter(INITIAL_WINDOW_SIZE, 64 * K - 1);
-        f.setParameter(MAX_FRAME_SIZE, 16 * K);
+        f.setParameter(HEADER_TABLE_SIZE, DEFAULT_HEADER_TABLE_SIZE);
+        f.setParameter(MAX_CONCURRENT_STREAMS, DEFAULT_MAX_CONCURRENT_STREAMS);
+        f.setParameter(INITIAL_WINDOW_SIZE, DEFAULT_INITIAL_WINDOW_SIZE);
+        f.setParameter(MAX_FRAME_SIZE, DEFAULT_MAX_FRAME_SIZE);
         return f;
     }
 }
--- a/test/jdk/java/net/httpclient/CancelledResponse.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/test/jdk/java/net/httpclient/CancelledResponse.java	Wed Jul 04 16:16:24 2018 +0100
@@ -339,9 +339,11 @@
                     Thread.sleep(10);
                 }
                 out.println("sent " + s);
-            } catch (SSLException | SocketException x) {
-                // if SSL then we might get a "Broken Pipe", otherwise
-                // a "Socket closed".
+            } catch (SSLException | SocketException | RuntimeException x) {
+                // if SSL then we might get a "Broken Pipe", or a
+                // RuntimeException wrapping an InvalidAlgorithmParameterException
+                // (probably if the channel is closed during the handshake),
+                // otherwise we get a "Socket closed".
                 boolean expected = cancelled.get();
                 if (sent > 0 && expected) {
                     System.out.println("Connection closed by peer as expected: " + x);
@@ -349,6 +351,7 @@
                 } else {
                     System.out.println("Unexpected exception (sent="
                             + sent + ", cancelled=" + expected + "): " + x);
+                    if (x instanceof RuntimeException) throw (RuntimeException) x;
                     throw new RuntimeException(x);
                 }
             } catch (IOException | InterruptedException e) {
--- a/test/jdk/java/net/httpclient/MockServer.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/test/jdk/java/net/httpclient/MockServer.java	Wed Jul 04 16:16:24 2018 +0100
@@ -284,7 +284,7 @@
             }
             try {
                 socket.close();
-            } catch (IOException e) {}
+            } catch (Throwable e) {}
             synchronized (removals) {
                 removals.add(this);
             }
@@ -339,7 +339,7 @@
         closed = true;
         try {
             ss.close();
-        } catch (IOException e) {
+        } catch (Throwable e) {
             e.printStackTrace();
         }
         for (Connection c : sockets) {
--- a/test/jdk/java/net/httpclient/ShortResponseBody.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/test/jdk/java/net/httpclient/ShortResponseBody.java	Wed Jul 04 16:16:24 2018 +0100
@@ -264,19 +264,47 @@
 
     // can be used to prolong request body publication
     static final class InfiniteInputStream extends InputStream {
+        int count = 0;
+        int k16 = 0;
         @Override
         public int read() throws IOException {
+            if (++count == 1) {
+                System.out.println("Start sending 1 byte");
+            }
+            if (count > 16 * 1024) {
+                k16++;
+                System.out.println("... 16K sent.");
+                count = count % (16 * 1024);
+            }
+            if (k16 > 128) {
+                System.out.println("WARNING: InfiniteInputStream: " +
+                        "more than 128 16k buffers generated: returning EOF");
+                return -1;
+            }
             return 1;
         }
 
         @Override
         public int read(byte[] buf, int offset, int length) {
             //int count = offset;
-            //length = Math.max(0, Math.min(buf.length - offset, length));
+            length = Math.max(0, Math.min(buf.length - offset, length));
             //for (; count < length; count++)
             //    buf[offset++] = 0x01;
             //return count;
-            return Math.max(0, Math.min(buf.length - offset, length));
+            if (count == 0) {
+                System.out.println("Start sending " + length);
+            } else if (count > 16 * 1024) {
+                k16++;
+                System.out.println("... 16K sent.");
+                count = count % (16 * 1024);
+            }
+            if (k16 > 128) {
+                System.out.println("WARNING: InfiniteInputStream: " +
+                        "more than 128 16k buffers generated: returning EOF");
+                return -1;
+            }
+            count += length;
+            return length;
         }
     }
 
@@ -493,10 +521,13 @@
                     out.print(requestMethod + " ");
                     URI uriPath = readRequestPath(is);
                     out.println(uriPath);
-                    readRequestHeaders(is);
+                    String headers = readRequestHeaders(is);
 
                     String query = uriPath.getRawQuery();
-                    assert query != null;
+                    if (query == null) {
+                        out.println("Request headers: [" + headers + "]");
+                    }
+                    assert query != null : "null query for uriPath: " + uriPath;
                     String qv = query.split("=")[1];
                     int len;
                     if (qv.equals("all")) {
@@ -542,9 +573,11 @@
         }
 
         // Read until the end of a HTTP request headers
-        static void readRequestHeaders(InputStream is) throws IOException {
+        static String readRequestHeaders(InputStream is) throws IOException {
             int requestEndCount = 0, r;
+            StringBuilder sb = new StringBuilder();
             while ((r = is.read()) != -1) {
+                sb.append((char) r);
                 if (r == requestEnd[requestEndCount]) {
                     requestEndCount++;
                     if (requestEndCount == 4) {
@@ -554,6 +587,7 @@
                     requestEndCount = 0;
                 }
             }
+            return sb.toString();
         }
     }
 
--- a/test/jdk/java/net/httpclient/SplitResponse.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/test/jdk/java/net/httpclient/SplitResponse.java	Wed Jul 04 16:16:24 2018 +0100
@@ -32,6 +32,7 @@
 import java.util.concurrent.CompletableFuture;
 import javax.net.ssl.SSLContext;
 import javax.net.ServerSocketFactory;
+import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLServerSocketFactory;
 import java.net.http.HttpClient;
 import java.net.http.HttpClient.Version;
@@ -268,7 +269,7 @@
                     String onechar = s.substring(i, i + 1);
                     try {
                         conn.send(onechar);
-                    } catch(SocketException x) {
+                    } catch(SocketException | SSLException x) {
                         if (!useSSL || i != len - 1) throw x;
                         if (x.getMessage().contains("closed by remote host")) {
                             String osname = System.getProperty("os.name", "unknown");
--- a/test/jdk/java/net/httpclient/http2/FixedThreadPoolTest.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/FixedThreadPoolTest.java	Wed Jul 04 16:16:24 2018 +0100
@@ -177,13 +177,24 @@
         System.err.println("DONE");
     }
 
+    // expect highest supported version we know about
+    static String expectedTLSVersion(SSLContext ctx) {
+        SSLParameters params = ctx.getSupportedSSLParameters();
+        String[] protocols = params.getProtocols();
+        for (String prot : protocols) {
+            if (prot.equals("TLSv1.3"))
+                return "TLSv1.3";
+        }
+        return "TLSv1.2";
+    }
+
     static void paramsTest() throws Exception {
         System.err.println("paramsTest");
         Http2TestServer server = new Http2TestServer(true, 0, exec, sslContext);
         server.addHandler((t -> {
             SSLSession s = t.getSSLSession();
             String prot = s.getProtocol();
-            if (prot.equals("TLSv1.2")) {
+            if (prot.equals(expectedTLSVersion(sslContext))) {
                 t.sendResponseHeaders(200, -1);
             } else {
                 System.err.printf("Protocols =%s\n", prot);
--- a/test/jdk/java/net/httpclient/http2/TLSConnection.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/TLSConnection.java	Wed Jul 04 16:16:24 2018 +0100
@@ -32,6 +32,7 @@
 import java.net.http.HttpRequest;
 import java.net.http.HttpRequest.BodyPublishers;
 import java.net.http.HttpResponse.BodyHandlers;
+import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLSession;
 
@@ -57,6 +58,19 @@
 
     private static final SSLParameters USE_DEFAULT_SSL_PARAMETERS = new SSLParameters();
 
+    // expect highest supported version we know about
+    static String expectedTLSVersion(SSLContext ctx) throws Exception {
+        if (ctx == null)
+            ctx = SSLContext.getDefault();
+        SSLParameters params = ctx.getSupportedSSLParameters();
+        String[] protocols = params.getProtocols();
+        for (String prot : protocols) {
+            if (prot.equals("TLSv1.3"))
+                return "TLSv1.3";
+        }
+        return "TLSv1.2";
+    }
+
     public static void main(String[] args) throws Exception {
         // re-enable 3DES
         Security.setProperty("jdk.tls.disabledAlgorithms", "");
@@ -92,7 +106,7 @@
                     "---\nTest #2: default SSL parameters, "
                             + "expect successful connection",
                     () -> connect(uriString, USE_DEFAULT_SSL_PARAMETERS));
-            success &= checkProtocol(handler.getSSLSession(), "TLSv1.2");
+            success &= checkProtocol(handler.getSSLSession(), expectedTLSVersion(null));
 
             // set SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA cipher suite
             // which has less priority in default cipher suite list
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLEchoTubeTest.java	Wed Jul 04 16:54:56 2018 +0200
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLEchoTubeTest.java	Wed Jul 04 16:16:24 2018 +0100
@@ -350,7 +350,7 @@
 
             @Override
             public void cancel() {
-                cancelled.set(true);
+                queue.add(EOF);
             }
         }