8221481: Reimplement the Legacy Socket API
authoralanb
Thu, 30 May 2019 07:19:19 +0100
changeset 55102 59567035d279
parent 55101 c41783eb76eb
child 55103 8a1095447ae6
8221481: Reimplement the Legacy Socket API Reviewed-by: michaelm, chegar
src/java.base/share/classes/java/net/SocketImpl.java
src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java
test/jdk/ProblemList.txt
test/jdk/com/sun/net/httpserver/Test1.java
test/jdk/java/net/ServerSocket/AcceptCauseFileDescriptorLeak.java
test/jdk/java/net/ServerSocket/UnreferencedSockets.java
test/jdk/java/net/Socket/ConnectionReset.java
test/jdk/java/net/Socket/Timeouts.java
test/jdk/java/net/Socket/UdpSocket.java
test/jdk/java/net/Socket/asyncClose/AsyncClose.java
test/jdk/java/net/SocketImpl/BadUsages.java
test/jdk/java/net/SocketImpl/CompareSocketOptions.java
test/jdk/java/net/SocketImpl/java.base/java/net/PlatformSocketImpl.java
test/jdk/java/net/SocketOption/OptionsTest.java
test/jdk/java/net/ipv6tests/TcpTest.java
test/jdk/sun/security/ssl/SSLSocketImpl/NewSocketMethods.java
--- a/src/java.base/share/classes/java/net/SocketImpl.java	Wed May 29 22:17:48 2019 -0400
+++ b/src/java.base/share/classes/java/net/SocketImpl.java	Thu May 30 07:19:19 2019 +0100
@@ -25,33 +25,63 @@
 
 package java.net;
 
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.FileDescriptor;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.util.Objects;
 import java.util.Set;
+
+import sun.net.NetProperties;
 import sun.net.PlatformSocketImpl;
+import sun.nio.ch.NioSocketImpl;
 
 /**
  * The abstract class {@code SocketImpl} is a common superclass
  * of all classes that actually implement sockets. It is used to
  * create both client and server sockets.
- * <p>
- * A "plain" socket implements these methods exactly as
- * described, without attempting to go through a firewall or proxy.
+ *
+ * @implNote Client and server sockets created with the {@code Socket} and
+ * {@code SocketServer} public constructors create a system-default
+ * {@code SocketImpl}. The JDK historically used a {@code SocketImpl}
+ * implementation type named "PlainSocketImpl" that has since been replaced by a
+ * newer implementation. The JDK continues to ship with the older implementation
+ * to allow code to run that depends on unspecified behavior that differs between
+ * the old and new implementations. The old implementation will be used if the
+ * Java virtual machine is started with the system property {@systemProperty
+ * jdk.net.usePlainSocketImpl} set to use the old implementation. It may also be
+ * set in the JDK's network configuration file, located in {@code
+ * ${java.home}/conf/net.properties}. The value of the property is the string
+ * representation of a boolean. If set without a value then it defaults to {@code
+ * true}, hence running with {@code -Djdk.net.usePlainSocketImpl} or {@code
+ * -Djdk.net.usePlainSocketImpl=true} will configure the Java virtual machine
+ * to use the old implementation. The property and old implementation will be
+ * removed in a future version.
  *
  * @author  unascribed
  * @since   1.0
  */
 public abstract class SocketImpl implements SocketOptions {
+    private static final boolean USE_PLAINSOCKETIMPL = usePlainSocketImpl();
+
+    private static boolean usePlainSocketImpl() {
+        PrivilegedAction<String> pa = () -> NetProperties.get("jdk.net.usePlainSocketImpl");
+        String s = AccessController.doPrivileged(pa);
+        return (s != null) && !s.equalsIgnoreCase("false");
+    }
 
     /**
      * Creates an instance of platform's SocketImpl
      */
     @SuppressWarnings("unchecked")
     static <S extends SocketImpl & PlatformSocketImpl> S createPlatformSocketImpl(boolean server) {
-        return (S) new PlainSocketImpl(server);
+        if (USE_PLAINSOCKETIMPL) {
+            return (S) new PlainSocketImpl(server);
+        } else {
+            return (S) new NioSocketImpl(server);
+        }
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java	Thu May 30 07:19:19 2019 +0100
@@ -0,0 +1,1283 @@
+/*
+ * Copyright (c) 2019, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 sun.nio.ch;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ProtocolFamily;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketImpl;
+import java.net.SocketOption;
+import java.net.SocketTimeoutException;
+import java.net.StandardProtocolFamily;
+import java.net.StandardSocketOptions;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+import jdk.internal.ref.CleanerFactory;
+import sun.net.ConnectionResetException;
+import sun.net.NetHooks;
+import sun.net.PlatformSocketImpl;
+import sun.net.ResourceManager;
+import sun.net.ext.ExtendedSocketOptions;
+import sun.net.util.SocketExceptions;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+/**
+ * NIO based SocketImpl.
+ *
+ * This implementation attempts to be compatible with legacy PlainSocketImpl,
+ * including behavior and exceptions that are not specified by SocketImpl.
+ *
+ * The underlying socket used by this SocketImpl is initially configured
+ * blocking. If the connect method is used to establish a connection with a
+ * timeout then the socket is configured non-blocking for the connect attempt,
+ * and then restored to blocking mode when the connection is established.
+ * If the accept or read methods are used with a timeout then the socket is
+ * configured non-blocking and is never restored. When in non-blocking mode,
+ * operations that don't complete immediately will poll the socket and preserve
+ * the semantics of blocking operations.
+ */
+
+public final class NioSocketImpl extends SocketImpl implements PlatformSocketImpl {
+    private static final NativeDispatcher nd = new SocketDispatcher();
+
+    // The maximum number of bytes to read/write per syscall to avoid needing
+    // a huge buffer from the temporary buffer cache
+    private static final int MAX_BUFFER_SIZE = 128 * 1024;
+
+    // true if this is a SocketImpl for a ServerSocket
+    private final boolean server;
+
+    // Lock held when reading (also used when accepting or connecting)
+    private final ReentrantLock readLock = new ReentrantLock();
+
+    // Lock held when writing
+    private final ReentrantLock writeLock = new ReentrantLock();
+
+    // The stateLock for read/changing state
+    private final Object stateLock = new Object();
+    private static final int ST_NEW = 0;
+    private static final int ST_UNCONNECTED = 1;
+    private static final int ST_CONNECTING = 2;
+    private static final int ST_CONNECTED = 3;
+    private static final int ST_CLOSING = 4;
+    private static final int ST_CLOSED = 5;
+    private volatile int state;  // need stateLock to change
+
+    // set by SocketImpl.create, protected by stateLock
+    private boolean stream;
+    private FileDescriptorCloser closer;
+
+    // set to true when the socket is in non-blocking mode
+    private volatile boolean nonBlocking;
+
+    // used by connect/read/write/accept, protected by stateLock
+    private long readerThread;
+    private long writerThread;
+
+    // used when SO_REUSEADDR is emulated, protected by stateLock
+    private boolean isReuseAddress;
+
+    // read or accept timeout in millis
+    private volatile int timeout;
+
+    // flags to indicate if the connection is shutdown for input and output
+    private volatile boolean isInputClosed;
+    private volatile boolean isOutputClosed;
+
+    // used by read to emulate legacy behavior, protected by readLock
+    private boolean readEOF;
+    private boolean connectionReset;
+
+    /**
+     * Creates an instance of this SocketImpl.
+     * @param server true if this is a SocketImpl for a ServerSocket
+     */
+    public NioSocketImpl(boolean server) {
+        this.server = server;
+    }
+
+    /**
+     * Returns true if the socket is open.
+     */
+    private boolean isOpen() {
+        return state < ST_CLOSING;
+    }
+
+    /**
+     * Throws SocketException if the socket is not open.
+     */
+    private void ensureOpen() throws SocketException {
+        int state = this.state;
+        if (state == ST_NEW)
+            throw new SocketException("Socket not created");
+        if (state >= ST_CLOSING)
+            throw new SocketException("Socket closed");
+    }
+
+    /**
+     * Throws SocketException if the socket is not open and connected.
+     */
+    private void ensureOpenAndConnected() throws SocketException {
+        int state = this.state;
+        if (state < ST_CONNECTED)
+            throw new SocketException("Not connected");
+        if (state > ST_CONNECTED)
+            throw new SocketException("Socket closed");
+    }
+
+    /**
+     * Disables the current thread for scheduling purposes until the socket is
+     * ready for I/O, or is asynchronously closed, for up to the specified
+     * waiting time.
+     * @throws IOException if an I/O error occurs
+     */
+    private void park(FileDescriptor fd, int event, long nanos) throws IOException {
+        long millis;
+        if (nanos == 0) {
+            millis = -1;
+        } else {
+            millis = NANOSECONDS.toMillis(nanos);
+        }
+        Net.poll(fd, event, millis);
+    }
+
+    /**
+     * Disables the current thread for scheduling purposes until the socket is
+     * ready for I/O or is asynchronously closed.
+     * @throws IOException if an I/O error occurs
+     */
+    private void park(FileDescriptor fd, int event) throws IOException {
+        park(fd, event, 0);
+    }
+
+    /**
+     * Configures the socket to blocking mode. This method is a no-op if the
+     * socket is already in blocking mode.
+     * @throws IOException if closed or there is an I/O error changing the mode
+     */
+    private void configureBlocking(FileDescriptor fd) throws IOException {
+        assert readLock.isHeldByCurrentThread();
+        if (nonBlocking) {
+            synchronized (stateLock) {
+                ensureOpen();
+                IOUtil.configureBlocking(fd, true);
+                nonBlocking = false;
+            }
+        }
+    }
+
+    /**
+     * Configures the socket to non-blocking mode. This method is a no-op if the
+     * socket is already in non-blocking mode.
+     * @throws IOException if closed or there is an I/O error changing the mode
+     */
+    private void configureNonBlocking(FileDescriptor fd) throws IOException {
+        assert readLock.isHeldByCurrentThread();
+        if (!nonBlocking) {
+            synchronized (stateLock) {
+                ensureOpen();
+                IOUtil.configureBlocking(fd, false);
+                nonBlocking = true;
+            }
+        }
+    }
+
+    /**
+     * Marks the beginning of a read operation that might block.
+     * @throws SocketException if the socket is closed or not connected
+     */
+    private FileDescriptor beginRead() throws SocketException {
+        synchronized (stateLock) {
+            ensureOpenAndConnected();
+            readerThread = NativeThread.current();
+            return fd;
+        }
+    }
+
+    /**
+     * Marks the end of a read operation that may have blocked.
+     * @throws SocketException is the socket is closed
+     */
+    private void endRead(boolean completed) throws SocketException {
+        synchronized (stateLock) {
+            readerThread = 0;
+            int state = this.state;
+            if (state == ST_CLOSING)
+                tryFinishClose();
+            if (!completed && state >= ST_CLOSING)
+                throw new SocketException("Socket closed");
+        }
+    }
+
+    /**
+     * Attempts to read bytes from the socket into the given byte array.
+     */
+    private int tryRead(FileDescriptor fd, byte[] b, int off, int len)
+        throws IOException
+    {
+        ByteBuffer dst = Util.getTemporaryDirectBuffer(len);
+        assert dst.position() == 0;
+        try {
+            int n = nd.read(fd, ((DirectBuffer)dst).address(), len);
+            if (n > 0) {
+                dst.get(b, off, n);
+            }
+            return n;
+        } finally {
+            Util.offerFirstTemporaryDirectBuffer(dst);
+        }
+    }
+
+    /**
+     * Reads bytes from the socket into the given byte array with a timeout.
+     * @throws SocketTimeoutException if the read timeout elapses
+     */
+    private int timedRead(FileDescriptor fd, byte[] b, int off, int len, long nanos)
+        throws IOException
+    {
+        long startNanos = System.nanoTime();
+        int n = tryRead(fd, b, off, len);
+        while (n == IOStatus.UNAVAILABLE && isOpen()) {
+            long remainingNanos = nanos - (System.nanoTime() - startNanos);
+            if (remainingNanos <= 0) {
+                throw new SocketTimeoutException("Read timed out");
+            }
+            park(fd, Net.POLLIN, remainingNanos);
+            n = tryRead(fd, b, off, len);
+        }
+        return n;
+    }
+
+    /**
+     * Reads bytes from the socket into the given byte array.
+     * @return the number of bytes read or -1 at EOF
+     * @throws SocketException if the socket is closed or a socket I/O error occurs
+     * @throws SocketTimeoutException if the read timeout elapses
+     */
+    private int implRead(byte[] b, int off, int len) throws IOException {
+        int n = 0;
+        FileDescriptor fd = beginRead();
+        try {
+            if (connectionReset)
+                throw new SocketException("Connection reset");
+            if (isInputClosed)
+                return -1;
+            int timeout = this.timeout;
+            if (timeout > 0) {
+                // read with timeout
+                configureNonBlocking(fd);
+                n = timedRead(fd, b, off, len, MILLISECONDS.toNanos(timeout));
+            } else {
+                // read, no timeout
+                n = tryRead(fd, b, off, len);
+                while (IOStatus.okayToRetry(n) && isOpen()) {
+                    park(fd, Net.POLLIN);
+                    n = tryRead(fd, b, off, len);
+                }
+            }
+            return n;
+        } catch (SocketTimeoutException e) {
+            throw e;
+        } catch (ConnectionResetException e) {
+            connectionReset = true;
+            throw new SocketException("Connection reset");
+        } catch (IOException ioe) {
+            throw new SocketException(ioe.getMessage());
+        } finally {
+            endRead(n > 0);
+        }
+    }
+
+    /**
+     * Reads bytes from the socket into the given byte array.
+     * @return the number of bytes read or -1 at EOF
+     * @throws IndexOutOfBoundsException if the bound checks fail
+     * @throws SocketException if the socket is closed or a socket I/O error occurs
+     * @throws SocketTimeoutException if the read timeout elapses
+     */
+    private int read(byte[] b, int off, int len) throws IOException {
+        Objects.checkFromIndexSize(off, len, b.length);
+        if (len == 0) {
+            return 0;
+        } else {
+            readLock.lock();
+            try {
+                // emulate legacy behavior to return -1, even if socket is closed
+                if (readEOF)
+                    return -1;
+                // read up to MAX_BUFFER_SIZE bytes
+                int size = Math.min(len, MAX_BUFFER_SIZE);
+                int n = implRead(b, off, size);
+                if (n == -1)
+                    readEOF = true;
+                return n;
+            } finally {
+                readLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * Marks the beginning of a write operation that might block.
+     * @throws SocketException if the socket is closed or not connected
+     */
+    private FileDescriptor beginWrite() throws SocketException {
+        synchronized (stateLock) {
+            ensureOpenAndConnected();
+            writerThread = NativeThread.current();
+            return fd;
+        }
+    }
+
+    /**
+     * Marks the end of a write operation that may have blocked.
+     * @throws SocketException is the socket is closed
+     */
+    private void endWrite(boolean completed) throws SocketException {
+        synchronized (stateLock) {
+            writerThread = 0;
+            int state = this.state;
+            if (state == ST_CLOSING)
+                tryFinishClose();
+            if (!completed && state >= ST_CLOSING)
+                throw new SocketException("Socket closed");
+        }
+    }
+
+    /**
+     * Attempts to write a sequence of bytes to the socket from the given
+     * byte array.
+     */
+    private int tryWrite(FileDescriptor fd, byte[] b, int off, int len)
+        throws IOException
+    {
+        ByteBuffer src = Util.getTemporaryDirectBuffer(len);
+        assert src.position() == 0;
+        try {
+            src.put(b, off, len);
+            return nd.write(fd, ((DirectBuffer)src).address(), len);
+        } finally {
+            Util.offerFirstTemporaryDirectBuffer(src);
+        }
+    }
+
+    /**
+     * Writes a sequence of bytes to the socket from the given byte array.
+     * @return the number of bytes written
+     * @throws SocketException if the socket is closed or a socket I/O error occurs
+     */
+    private int implWrite(byte[] b, int off, int len) throws IOException {
+        int n = 0;
+        FileDescriptor fd = beginWrite();
+        try {
+            n = tryWrite(fd, b, off, len);
+            while (IOStatus.okayToRetry(n) && isOpen()) {
+                park(fd, Net.POLLOUT);
+                n = tryWrite(fd, b, off, len);
+            }
+            return n;
+        } catch (IOException ioe) {
+            throw new SocketException(ioe.getMessage());
+        } finally {
+            endWrite(n > 0);
+        }
+    }
+
+    /**
+     * Writes a sequence of bytes to the socket from the given byte array.
+     * @throws SocketException if the socket is closed or a socket I/O error occurs
+     */
+    private void write(byte[] b, int off, int len) throws IOException {
+        Objects.checkFromIndexSize(off, len, b.length);
+        if (len > 0) {
+            writeLock.lock();
+            try {
+                int pos = off;
+                int end = off + len;
+                while (pos < end) {
+                    // write up to MAX_BUFFER_SIZE bytes
+                    int size = Math.min((end - pos), MAX_BUFFER_SIZE);
+                    int n = implWrite(b, pos, size);
+                    pos += n;
+                }
+            } finally {
+                writeLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * Creates the socket.
+     * @param stream {@code true} for a streams socket
+     */
+    @Override
+    protected void create(boolean stream) throws IOException {
+        synchronized (stateLock) {
+            if (state != ST_NEW)
+                throw new IOException("Already created");
+            if (!stream)
+                ResourceManager.beforeUdpCreate();
+            FileDescriptor fd;
+            try {
+                if (server) {
+                    assert stream;
+                    fd = Net.serverSocket(true);
+                } else {
+                    fd = Net.socket(stream);
+                }
+            } catch (IOException ioe) {
+                if (!stream)
+                    ResourceManager.afterUdpClose();
+                throw ioe;
+            }
+            this.fd = fd;
+            this.stream = stream;
+            this.closer = FileDescriptorCloser.create(this);
+            this.state = ST_UNCONNECTED;
+        }
+    }
+
+    /**
+     * Marks the beginning of a connect operation that might block.
+     * @throws SocketException if the socket is closed or already connected
+     */
+    private FileDescriptor beginConnect(InetAddress address, int port)
+        throws IOException
+    {
+        synchronized (stateLock) {
+            int state = this.state;
+            if (state != ST_UNCONNECTED) {
+                if (state == ST_NEW)
+                    throw new SocketException("Not created");
+                if (state == ST_CONNECTING)
+                    throw new SocketException("Connection in progress");
+                if (state == ST_CONNECTED)
+                    throw new SocketException("Already connected");
+                if (state >= ST_CLOSING)
+                    throw new SocketException("Socket closed");
+                assert false;
+            }
+            this.state = ST_CONNECTING;
+
+            // invoke beforeTcpConnect hook if not already bound
+            if (localport == 0) {
+                NetHooks.beforeTcpConnect(fd, address, port);
+            }
+
+            // save the remote address/port
+            this.address = address;
+            this.port = port;
+
+            readerThread = NativeThread.current();
+            return fd;
+        }
+    }
+
+    /**
+     * Marks the end of a connect operation that may have blocked.
+     * @throws SocketException is the socket is closed
+     */
+    private void endConnect(FileDescriptor fd, boolean completed) throws IOException {
+        synchronized (stateLock) {
+            readerThread = 0;
+            int state = this.state;
+            if (state == ST_CLOSING)
+                tryFinishClose();
+            if (completed && state == ST_CONNECTING) {
+                this.state = ST_CONNECTED;
+                localport = Net.localAddress(fd).getPort();
+            } else if (!completed && state >= ST_CLOSING) {
+                throw new SocketException("Socket closed");
+            }
+        }
+    }
+
+    /**
+     * Waits for a connection attempt to finish with a timeout
+     * @throws SocketTimeoutException if the connect timeout elapses
+     */
+    private boolean timedFinishConnect(FileDescriptor fd, long nanos) throws IOException {
+        long startNanos = System.nanoTime();
+        boolean polled = Net.pollConnectNow(fd);
+        while (!polled && isOpen()) {
+            long remainingNanos = nanos - (System.nanoTime() - startNanos);
+            if (remainingNanos <= 0) {
+                throw new SocketTimeoutException("Connect timed out");
+            }
+            park(fd, Net.POLLOUT, remainingNanos);
+            polled = Net.pollConnectNow(fd);
+        }
+        return polled && isOpen();
+    }
+
+    /**
+     * Attempts to establish a connection to the given socket address with a
+     * timeout. Closes the socket if connection cannot be established.
+     * @throws IOException if the address is not a resolved InetSocketAddress or
+     *         the connection cannot be established
+     */
+    @Override
+    protected void connect(SocketAddress remote, int millis) throws IOException {
+        // SocketImpl connect only specifies IOException
+        if (!(remote instanceof InetSocketAddress))
+            throw new IOException("Unsupported address type");
+        InetSocketAddress isa = (InetSocketAddress) remote;
+        if (isa.isUnresolved()) {
+            throw new UnknownHostException(isa.getHostName());
+        }
+
+        InetAddress address = isa.getAddress();
+        if (address.isAnyLocalAddress())
+            address = InetAddress.getLocalHost();
+        int port = isa.getPort();
+
+        ReentrantLock connectLock = readLock;
+        try {
+            connectLock.lock();
+            try {
+                boolean connected = false;
+                FileDescriptor fd = beginConnect(address, port);
+                try {
+
+                    // configure socket to non-blocking mode when there is a timeout
+                    if (millis > 0) {
+                        configureNonBlocking(fd);
+                    }
+
+                    int n = Net.connect(fd, address, port);
+                    if (n > 0) {
+                        // connection established
+                        connected = true;
+                    } else {
+                        assert IOStatus.okayToRetry(n);
+                        if (millis > 0) {
+                            // finish connect with timeout
+                            long nanos = MILLISECONDS.toNanos(millis);
+                            connected = timedFinishConnect(fd, nanos);
+                        } else {
+                            // finish connect, no timeout
+                            boolean polled = false;
+                            while (!polled && isOpen()) {
+                                park(fd, Net.POLLOUT);
+                                polled = Net.pollConnectNow(fd);
+                            }
+                            connected = polled && isOpen();
+                        }
+                    }
+
+                    // restore socket to blocking mode
+                    if (connected && millis > 0) {
+                        configureBlocking(fd);
+                    }
+
+                } finally {
+                    endConnect(fd, connected);
+                }
+            } finally {
+                connectLock.unlock();
+            }
+        } catch (IOException ioe) {
+            close();
+            throw SocketExceptions.of(ioe, isa);
+        }
+    }
+
+    @Override
+    protected void connect(String host, int port) throws IOException {
+        connect(new InetSocketAddress(host, port), 0);
+    }
+
+    @Override
+    protected void connect(InetAddress address, int port) throws IOException {
+        connect(new InetSocketAddress(address, port), 0);
+    }
+
+    @Override
+    protected void bind(InetAddress host, int port) throws IOException {
+        synchronized (stateLock) {
+            ensureOpen();
+            if (localport != 0)
+                throw new SocketException("Already bound");
+            NetHooks.beforeTcpBind(fd, host, port);
+            Net.bind(fd, host, port);
+            // set the address field to the given host address to keep
+            // compatibility with PlainSocketImpl. When binding to 0.0.0.0
+            // then the actual local address will be ::0 when IPv6 is enabled.
+            address = host;
+            localport = Net.localAddress(fd).getPort();
+        }
+    }
+
+    @Override
+    protected void listen(int backlog) throws IOException {
+        synchronized (stateLock) {
+            ensureOpen();
+            if (localport == 0)
+                throw new SocketException("Not bound");
+            Net.listen(fd, backlog < 1 ? 50 : backlog);
+        }
+    }
+
+    /**
+     * Marks the beginning of an accept operation that might block.
+     * @throws SocketException if the socket is closed
+     */
+    private FileDescriptor beginAccept() throws SocketException {
+        synchronized (stateLock) {
+            ensureOpen();
+            if (!stream)
+                throw new SocketException("Not a stream socket");
+            if (localport == 0)
+                throw new SocketException("Not bound");
+            readerThread = NativeThread.current();
+            return fd;
+        }
+    }
+
+    /**
+     * Marks the end of an accept operation that may have blocked.
+     * @throws SocketException is the socket is closed
+     */
+    private void endAccept(boolean completed) throws SocketException {
+        synchronized (stateLock) {
+            int state = this.state;
+            readerThread = 0;
+            if (state == ST_CLOSING)
+                tryFinishClose();
+            if (!completed && state >= ST_CLOSING)
+                throw new SocketException("Socket closed");
+        }
+    }
+
+    /**
+     * Accepts a new connection with a timeout.
+     * @throws SocketTimeoutException if the accept timeout elapses
+     */
+    private int timedAccept(FileDescriptor fd,
+                            FileDescriptor newfd,
+                            InetSocketAddress[] isaa,
+                            long nanos)
+        throws IOException
+    {
+        long startNanos = System.nanoTime();
+        int n = Net.accept(fd, newfd, isaa);
+        while (n == IOStatus.UNAVAILABLE && isOpen()) {
+            long remainingNanos = nanos - (System.nanoTime() - startNanos);
+            if (remainingNanos <= 0) {
+                throw new SocketTimeoutException("Accept timed out");
+            }
+            park(fd, Net.POLLIN, remainingNanos);
+            n = Net.accept(fd, newfd, isaa);
+        }
+        return n;
+    }
+
+    /**
+     * Accepts a new connection so that the given SocketImpl is connected to
+     * the peer. The SocketImpl must be a newly created NioSocketImpl.
+     */
+    @Override
+    protected void accept(SocketImpl si) throws IOException {
+        NioSocketImpl nsi = (NioSocketImpl) si;
+        if (nsi.state != ST_NEW)
+            throw new SocketException("Not a newly created SocketImpl");
+
+        FileDescriptor newfd = new FileDescriptor();
+        InetSocketAddress[] isaa = new InetSocketAddress[1];
+
+        // acquire the lock, adjusting the timeout for cases where several
+        // threads are accepting connections and there is a timeout set
+        ReentrantLock acceptLock = readLock;
+        int timeout = this.timeout;
+        long remainingNanos = 0;
+        if (timeout > 0) {
+            remainingNanos = tryLock(acceptLock, timeout, MILLISECONDS);
+            if (remainingNanos <= 0) {
+                assert !acceptLock.isHeldByCurrentThread();
+                throw new SocketTimeoutException("Accept timed out");
+            }
+        } else {
+            acceptLock.lock();
+        }
+
+        // accept a connection
+        try {
+            int n = 0;
+            FileDescriptor fd = beginAccept();
+            try {
+                if (remainingNanos > 0) {
+                    // accept with timeout
+                    configureNonBlocking(fd);
+                    n = timedAccept(fd, newfd, isaa, remainingNanos);
+                } else {
+                    // accept, no timeout
+                    n = Net.accept(fd, newfd, isaa);
+                    while (IOStatus.okayToRetry(n) && isOpen()) {
+                        park(fd, Net.POLLIN);
+                        n = Net.accept(fd, newfd, isaa);
+                    }
+                }
+            } finally {
+                endAccept(n > 0);
+                assert IOStatus.check(n);
+            }
+        } finally {
+            acceptLock.unlock();
+        }
+
+        // get local address and configure accepted socket to blocking mode
+        InetSocketAddress localAddress;
+        try {
+            localAddress = Net.localAddress(newfd);
+            IOUtil.configureBlocking(newfd, true);
+        } catch (IOException ioe) {
+            nd.close(newfd);
+            throw ioe;
+        }
+
+        // set the fields
+        synchronized (nsi.stateLock) {
+            nsi.fd = newfd;
+            nsi.stream = true;
+            nsi.closer = FileDescriptorCloser.create(nsi);
+            nsi.localport = localAddress.getPort();
+            nsi.address = isaa[0].getAddress();
+            nsi.port = isaa[0].getPort();
+            nsi.state = ST_CONNECTED;
+        }
+    }
+
+    @Override
+    protected InputStream getInputStream() {
+        return new InputStream() {
+            @Override
+            public int read() throws IOException {
+                byte[] a = new byte[1];
+                int n = read(a, 0, 1);
+                return (n > 0) ? (a[0] & 0xff) : -1;
+            }
+            @Override
+            public int read(byte[] b, int off, int len) throws IOException {
+                return NioSocketImpl.this.read(b, off, len);
+            }
+            @Override
+            public int available() throws IOException {
+                return NioSocketImpl.this.available();
+            }
+            @Override
+            public void close() throws IOException {
+                NioSocketImpl.this.close();
+            }
+        };
+    }
+
+    @Override
+    protected OutputStream getOutputStream() {
+        return new OutputStream() {
+            @Override
+            public void write(int b) throws IOException {
+                byte[] a = new byte[]{(byte) b};
+                write(a, 0, 1);
+            }
+            @Override
+            public void write(byte[] b, int off, int len) throws IOException {
+                NioSocketImpl.this.write(b, off, len);
+            }
+            @Override
+            public void close() throws IOException {
+                NioSocketImpl.this.close();
+            }
+        };
+    }
+
+    @Override
+    protected int available() throws IOException {
+        synchronized (stateLock) {
+            ensureOpenAndConnected();
+            if (isInputClosed) {
+                return 0;
+            } else {
+                return Net.available(fd);
+            }
+        }
+    }
+
+    /**
+     * Closes the socket if there are no I/O operations in progress.
+     */
+    private boolean tryClose() throws IOException {
+        assert Thread.holdsLock(stateLock) && state == ST_CLOSING;
+        if (readerThread == 0 && writerThread == 0) {
+            try {
+                closer.run();
+            } catch (UncheckedIOException ioe) {
+                throw ioe.getCause();
+            } finally {
+                state = ST_CLOSED;
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Invokes tryClose to attempt to close the socket.
+     *
+     * This method is used for deferred closing by I/O operations.
+     */
+    private void tryFinishClose() {
+        try {
+            tryClose();
+        } catch (IOException ignore) { }
+    }
+
+    /**
+     * Closes the socket. If there are I/O operations in progress then the
+     * socket is pre-closed and the threads are signalled. The socket will be
+     * closed when the last I/O operation aborts.
+     */
+    @Override
+    protected void close() throws IOException {
+        synchronized (stateLock) {
+            int state = this.state;
+            if (state >= ST_CLOSING)
+                return;
+            if (state == ST_NEW) {
+                // stillborn
+                this.state = ST_CLOSED;
+                return;
+            }
+            this.state = ST_CLOSING;
+
+            // shutdown output when linger interval not set to 0
+            try {
+                var SO_LINGER = StandardSocketOptions.SO_LINGER;
+                if ((int) Net.getSocketOption(fd, SO_LINGER) != 0) {
+                    Net.shutdown(fd, Net.SHUT_WR);
+                }
+            } catch (IOException ignore) { }
+
+            // attempt to close the socket. If there are I/O operations in progress
+            // then the socket is pre-closed and the thread(s) signalled. The
+            // last thread will close the file descriptor.
+            if (!tryClose()) {
+                nd.preClose(fd);
+                long reader = readerThread;
+                if (reader != 0)
+                    NativeThread.signal(reader);
+                long writer = writerThread;
+                if (writer != 0)
+                    NativeThread.signal(writer);
+            }
+        }
+    }
+
+    // the socket options supported by client and server sockets
+    private static volatile Set<SocketOption<?>> clientSocketOptions;
+    private static volatile Set<SocketOption<?>> serverSocketOptions;
+
+    @Override
+    protected Set<SocketOption<?>> supportedOptions() {
+        Set<SocketOption<?>> options = (server) ? serverSocketOptions : clientSocketOptions;
+        if (options == null) {
+            options = new HashSet<>();
+            options.add(StandardSocketOptions.SO_RCVBUF);
+            options.add(StandardSocketOptions.SO_REUSEADDR);
+            if (server) {
+                // IP_TOS added for server socket to maintain compatibility
+                options.add(StandardSocketOptions.IP_TOS);
+                options.addAll(ExtendedSocketOptions.serverSocketOptions());
+            } else {
+                options.add(StandardSocketOptions.IP_TOS);
+                options.add(StandardSocketOptions.SO_KEEPALIVE);
+                options.add(StandardSocketOptions.SO_SNDBUF);
+                options.add(StandardSocketOptions.SO_LINGER);
+                options.add(StandardSocketOptions.TCP_NODELAY);
+                options.addAll(ExtendedSocketOptions.clientSocketOptions());
+            }
+            if (Net.isReusePortAvailable())
+                options.add(StandardSocketOptions.SO_REUSEPORT);
+            options = Collections.unmodifiableSet(options);
+            if (server) {
+                serverSocketOptions = options;
+            } else {
+                clientSocketOptions = options;
+            }
+        }
+        return options;
+    }
+
+    @Override
+    protected <T> void setOption(SocketOption<T> opt, T value) throws IOException {
+        if (!supportedOptions().contains(opt))
+            throw new UnsupportedOperationException("'" + opt + "' not supported");
+        if (!opt.type().isInstance(value))
+            throw new IllegalArgumentException("Invalid value '" + value + "'");
+        synchronized (stateLock) {
+            ensureOpen();
+            if (opt == StandardSocketOptions.IP_TOS) {
+                // maps to IP_TOS or IPV6_TCLASS
+                Net.setSocketOption(fd, family(), opt, value);
+            } else if (opt == StandardSocketOptions.SO_REUSEADDR) {
+                boolean b = (boolean) value;
+                if (Net.useExclusiveBind()) {
+                    isReuseAddress = b;
+                } else {
+                    Net.setSocketOption(fd, opt, b);
+                }
+            } else {
+                // option does not need special handling
+                Net.setSocketOption(fd, opt, value);
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T> T getOption(SocketOption<T> opt) throws IOException {
+        if (!supportedOptions().contains(opt))
+            throw new UnsupportedOperationException("'" + opt + "' not supported");
+        synchronized (stateLock) {
+            ensureOpen();
+            if (opt == StandardSocketOptions.IP_TOS) {
+                return (T) Net.getSocketOption(fd, family(), opt);
+            } else if (opt == StandardSocketOptions.SO_REUSEADDR) {
+                if (Net.useExclusiveBind()) {
+                    return (T) Boolean.valueOf(isReuseAddress);
+                } else {
+                    return (T) Net.getSocketOption(fd, opt);
+                }
+            } else {
+                // option does not need special handling
+                return (T) Net.getSocketOption(fd, opt);
+            }
+        }
+    }
+
+    private boolean booleanValue(Object value, String desc) throws SocketException {
+        if (!(value instanceof Boolean))
+            throw new SocketException("Bad value for " + desc);
+        return (boolean) value;
+    }
+
+    private int intValue(Object value, String desc) throws SocketException {
+        if (!(value instanceof Integer))
+            throw new SocketException("Bad value for " + desc);
+        return (int) value;
+    }
+
+    @Override
+    public void setOption(int opt, Object value) throws SocketException {
+        synchronized (stateLock) {
+            ensureOpen();
+            try {
+                switch (opt) {
+                case SO_LINGER: {
+                    // the value is "false" to disable, or linger interval to enable
+                    int i;
+                    if (value instanceof Boolean && ((boolean) value) == false) {
+                        i = -1;
+                    } else {
+                        i = intValue(value, "SO_LINGER");
+                    }
+                    Net.setSocketOption(fd, StandardSocketOptions.SO_LINGER, i);
+                    break;
+                }
+                case SO_TIMEOUT: {
+                    int i = intValue(value, "SO_TIMEOUT");
+                    if (i < 0)
+                        throw new IllegalArgumentException("timeout < 0");
+                    timeout = i;
+                    break;
+                }
+                case IP_TOS: {
+                    int i = intValue(value, "IP_TOS");
+                    Net.setSocketOption(fd, family(), StandardSocketOptions.IP_TOS, i);
+                    break;
+                }
+                case TCP_NODELAY: {
+                    boolean b = booleanValue(value, "TCP_NODELAY");
+                    Net.setSocketOption(fd, StandardSocketOptions.TCP_NODELAY, b);
+                    break;
+                }
+                case SO_SNDBUF: {
+                    int i = intValue(value, "SO_SNDBUF");
+                    if (i <= 0)
+                        throw new SocketException("SO_SNDBUF <= 0");
+                    Net.setSocketOption(fd, StandardSocketOptions.SO_SNDBUF, i);
+                    break;
+                }
+                case SO_RCVBUF: {
+                    int i = intValue(value, "SO_RCVBUF");
+                    if (i <= 0)
+                        throw new SocketException("SO_RCVBUF <= 0");
+                    Net.setSocketOption(fd, StandardSocketOptions.SO_RCVBUF, i);
+                    break;
+                }
+                case SO_KEEPALIVE: {
+                    boolean b = booleanValue(value, "SO_KEEPALIVE");
+                    Net.setSocketOption(fd, StandardSocketOptions.SO_KEEPALIVE, b);
+                    break;
+                }
+                case SO_OOBINLINE: {
+                    boolean b = booleanValue(value, "SO_OOBINLINE");
+                    Net.setSocketOption(fd, ExtendedSocketOption.SO_OOBINLINE, b);
+                    break;
+                }
+                case SO_REUSEADDR: {
+                    boolean b = booleanValue(value, "SO_REUSEADDR");
+                    if (Net.useExclusiveBind()) {
+                        isReuseAddress = b;
+                    } else {
+                        Net.setSocketOption(fd, StandardSocketOptions.SO_REUSEADDR, b);
+                    }
+                    break;
+                }
+                case SO_REUSEPORT: {
+                    if (!Net.isReusePortAvailable())
+                        throw new SocketException("SO_REUSEPORT not supported");
+                    boolean b = booleanValue(value, "SO_REUSEPORT");
+                    Net.setSocketOption(fd, StandardSocketOptions.SO_REUSEPORT, b);
+                    break;
+                }
+                default:
+                    throw new SocketException("Unknown option " + opt);
+                }
+            } catch (SocketException e) {
+                throw e;
+            } catch (IllegalArgumentException | IOException e) {
+                throw new SocketException(e.getMessage());
+            }
+        }
+    }
+
+    @Override
+    public Object getOption(int opt) throws SocketException {
+        synchronized (stateLock) {
+            ensureOpen();
+            try {
+                switch (opt) {
+                case SO_TIMEOUT:
+                    return timeout;
+                case TCP_NODELAY:
+                    return Net.getSocketOption(fd, StandardSocketOptions.TCP_NODELAY);
+                case SO_OOBINLINE:
+                    return Net.getSocketOption(fd, ExtendedSocketOption.SO_OOBINLINE);
+                case SO_LINGER: {
+                    // return "false" when disabled, linger interval when enabled
+                    int i = (int) Net.getSocketOption(fd, StandardSocketOptions.SO_LINGER);
+                    if (i == -1) {
+                        return Boolean.FALSE;
+                    } else {
+                        return i;
+                    }
+                }
+                case SO_REUSEADDR:
+                    if (Net.useExclusiveBind()) {
+                        return isReuseAddress;
+                    } else {
+                        return Net.getSocketOption(fd, StandardSocketOptions.SO_REUSEADDR);
+                    }
+                case SO_BINDADDR:
+                    return Net.localAddress(fd).getAddress();
+                case SO_SNDBUF:
+                    return Net.getSocketOption(fd, StandardSocketOptions.SO_SNDBUF);
+                case SO_RCVBUF:
+                    return Net.getSocketOption(fd, StandardSocketOptions.SO_RCVBUF);
+                case IP_TOS:
+                    return Net.getSocketOption(fd, family(), StandardSocketOptions.IP_TOS);
+                case SO_KEEPALIVE:
+                    return Net.getSocketOption(fd, StandardSocketOptions.SO_KEEPALIVE);
+                case SO_REUSEPORT:
+                    if (!Net.isReusePortAvailable())
+                        throw new SocketException("SO_REUSEPORT not supported");
+                    return Net.getSocketOption(fd, StandardSocketOptions.SO_REUSEPORT);
+                default:
+                    throw new SocketException("Unknown option " + opt);
+                }
+            } catch (SocketException e) {
+                throw e;
+            } catch (IllegalArgumentException | IOException e) {
+                throw new SocketException(e.getMessage());
+            }
+        }
+    }
+
+    @Override
+    protected void shutdownInput() throws IOException {
+        synchronized (stateLock) {
+            ensureOpenAndConnected();
+            if (!isInputClosed) {
+                Net.shutdown(fd, Net.SHUT_RD);
+                isInputClosed = true;
+            }
+        }
+    }
+
+    @Override
+    protected void shutdownOutput() throws IOException {
+        synchronized (stateLock) {
+            ensureOpenAndConnected();
+            if (!isOutputClosed) {
+                Net.shutdown(fd, Net.SHUT_WR);
+                isOutputClosed = true;
+            }
+        }
+    }
+
+    @Override
+    protected boolean supportsUrgentData() {
+        return true;
+    }
+
+    @Override
+    protected void sendUrgentData(int data) throws IOException {
+        writeLock.lock();
+        try {
+            int n = 0;
+            FileDescriptor fd = beginWrite();
+            try {
+                do {
+                    n = Net.sendOOB(fd, (byte) data);
+                } while (n == IOStatus.INTERRUPTED && isOpen());
+                if (n == IOStatus.UNAVAILABLE) {
+                    throw new SocketException("No buffer space available");
+                }
+            } finally {
+                endWrite(n > 0);
+            }
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    /**
+     * A task that closes a SocketImpl's file descriptor. The task runs when the
+     * SocketImpl is explicitly closed and when the SocketImpl becomes phantom
+     * reachable.
+     */
+    private static class FileDescriptorCloser implements Runnable {
+        private static final VarHandle CLOSED;
+        static {
+            try {
+                MethodHandles.Lookup l = MethodHandles.lookup();
+                CLOSED = l.findVarHandle(FileDescriptorCloser.class,
+                                         "closed",
+                                         boolean.class);
+            } catch (Exception e) {
+                throw new InternalError(e);
+            }
+        }
+
+        private final FileDescriptor fd;
+        private final boolean stream;
+        private volatile boolean closed;
+
+        FileDescriptorCloser(FileDescriptor fd, boolean stream) {
+            this.fd = fd;
+            this.stream = stream;
+        }
+
+        static FileDescriptorCloser create(NioSocketImpl impl) {
+            assert Thread.holdsLock(impl.stateLock);
+            var closer = new FileDescriptorCloser(impl.fd, impl.stream);
+            CleanerFactory.cleaner().register(impl, closer);
+            return closer;
+        }
+
+        @Override
+        public void run() {
+            if (CLOSED.compareAndSet(this, false, true)) {
+                try {
+                    nd.close(fd);
+                } catch (IOException ioe) {
+                    throw new UncheckedIOException(ioe);
+                } finally {
+                    if (!stream) {
+                        // decrement
+                        ResourceManager.afterUdpClose();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Attempts to acquire the given lock within the given waiting time.
+     * @return the remaining time in nanoseconds when the lock is acquired, zero
+     *         or less if the lock was not acquired before the timeout expired
+     */
+    private static long tryLock(ReentrantLock lock, long timeout, TimeUnit unit) {
+        assert timeout > 0;
+        boolean interrupted = false;
+        long nanos = NANOSECONDS.convert(timeout, unit);
+        long remainingNanos = nanos;
+        long startNanos = System.nanoTime();
+        boolean acquired = false;
+        while (!acquired && (remainingNanos > 0)) {
+            try {
+                acquired = lock.tryLock(remainingNanos, NANOSECONDS);
+            } catch (InterruptedException e) {
+                interrupted = true;
+            }
+            remainingNanos = nanos - (System.nanoTime() - startNanos);
+        }
+        if (acquired && remainingNanos <= 0L)
+            lock.unlock();  // release lock if timeout has expired
+        if (interrupted)
+            Thread.currentThread().interrupt();
+        return remainingNanos;
+    }
+
+    /**
+     * Returns the socket protocol family.
+     */
+    private static ProtocolFamily family() {
+        if (Net.isIPv6Available()) {
+            return StandardProtocolFamily.INET6;
+        } else {
+            return StandardProtocolFamily.INET;
+        }
+    }
+}
--- a/test/jdk/ProblemList.txt	Wed May 29 22:17:48 2019 -0400
+++ b/test/jdk/ProblemList.txt	Thu May 30 07:19:19 2019 +0100
@@ -600,6 +600,9 @@
 
 java/net/ServerSocket/AcceptInheritHandle.java                  8211854 aix-ppc64
 
+java/net/Inet6Address/B6206527.java                             8216417 macosx-all
+java/net/ipv6tests/B6521014.java                                8216417 macosx-all
+
 ############################################################################
 
 # jdk_nio
@@ -862,6 +865,7 @@
 jdk/jfr/event/io/TestInstrumentation.java                       8202142    generic-all
 jdk/jfr/api/recording/event/TestPeriod.java                     8215890    generic-all
 
+jdk/jfr/event/io/EvilInstrument.java                            8221331    generic-all
 
 ############################################################################
 
--- a/test/jdk/com/sun/net/httpserver/Test1.java	Wed May 29 22:17:48 2019 -0400
+++ b/test/jdk/com/sun/net/httpserver/Test1.java	Thu May 30 07:19:19 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2019, 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
@@ -27,6 +27,7 @@
  * @library /test/lib
  * @build jdk.test.lib.net.SimpleSSLContext
  * @run main/othervm Test1
+ * @run main/othervm -Djdk.net.usePlainSocketImpl Test1
  * @run main/othervm -Dsun.net.httpserver.maxReqTime=10 Test1
  * @run main/othervm -Dsun.net.httpserver.nodelay=true Test1
  * @summary  Light weight HTTP server
--- a/test/jdk/java/net/ServerSocket/AcceptCauseFileDescriptorLeak.java	Wed May 29 22:17:48 2019 -0400
+++ b/test/jdk/java/net/ServerSocket/AcceptCauseFileDescriptorLeak.java	Thu May 30 07:19:19 2019 +0100
@@ -38,6 +38,7 @@
  *        jdk.test.lib.process.*
  *        AcceptCauseFileDescriptorLeak
  * @run main/othervm AcceptCauseFileDescriptorLeak root
+ * @run main/othervm -Djdk.net.usePlainSocketImpl AcceptCauseFileDescriptorLeak root
  */
 
 import java.io.IOException;
--- a/test/jdk/java/net/ServerSocket/UnreferencedSockets.java	Wed May 29 22:17:48 2019 -0400
+++ b/test/jdk/java/net/ServerSocket/UnreferencedSockets.java	Thu May 30 07:19:19 2019 +0100
@@ -27,6 +27,7 @@
  * @modules java.management java.base/java.io:+open java.base/java.net:+open
  * @run main/othervm UnreferencedSockets
  * @run main/othervm -Djava.net.preferIPv4Stack=true UnreferencedSockets
+ * @run main/othervm -Djdk.net.usePlainSocketImpl UnreferencedSockets
  * @summary Check that unreferenced sockets are closed
  */
 
--- a/test/jdk/java/net/Socket/ConnectionReset.java	Wed May 29 22:17:48 2019 -0400
+++ b/test/jdk/java/net/Socket/ConnectionReset.java	Thu May 30 07:19:19 2019 +0100
@@ -25,6 +25,7 @@
  * @test
  * @requires os.family != "solaris"
  * @run testng ConnectionReset
+ * @run testng/othervm -Djdk.net.usePlainSocketImpl ConnectionReset
  * @summary Test behavior of read and available when a connection is reset
  */
 
--- a/test/jdk/java/net/Socket/Timeouts.java	Wed May 29 22:17:48 2019 -0400
+++ b/test/jdk/java/net/Socket/Timeouts.java	Thu May 30 07:19:19 2019 +0100
@@ -23,9 +23,10 @@
 
 /*
  * @test
+ * @bug 8221481
  * @library /test/lib
  * @build jdk.test.lib.Utils
- * @run testng Timeouts
+ * @run testng/timeout=180 Timeouts
  * @summary Test Socket timeouts
  */
 
@@ -34,12 +35,17 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.net.SocketTimeoutException;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
@@ -54,7 +60,7 @@
      * Test timed connect where connection is established
      */
     public void testTimedConnect1() throws IOException {
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = boundServerSocket()) {
             try (Socket s = new Socket()) {
                 s.connect(ss.getLocalSocketAddress(), 2000);
             }
@@ -77,7 +83,7 @@
      * Test connect with a timeout of Integer.MAX_VALUE
      */
     public void testTimedConnect3() throws IOException {
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = boundServerSocket()) {
             try (Socket s = new Socket()) {
                 s.connect(ss.getLocalSocketAddress(), Integer.MAX_VALUE);
             }
@@ -88,12 +94,10 @@
      * Test connect with a negative timeout.
      */
     public void testTimedConnect4() throws IOException {
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = boundServerSocket()) {
             try (Socket s = new Socket()) {
-                try {
-                    s.connect(ss.getLocalSocketAddress(), -1);
-                    assertTrue(false);
-                } catch (IllegalArgumentException expected) { }
+                expectThrows(IllegalArgumentException.class,
+                             () -> s.connect(ss.getLocalSocketAddress(), -1));
             }
         }
     }
@@ -128,10 +132,10 @@
     public void testTimedRead3() throws IOException {
         withConnection((s1, s2) -> {
             s2.setSoTimeout(2000);
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketTimeoutException expected) { }
+            long startMillis = millisTime();
+            expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read());
+            int timeout = s2.getSoTimeout();
+            checkDuration(startMillis, timeout-100, timeout+2000);
         });
     }
 
@@ -141,10 +145,7 @@
     public void testTimedRead4() throws IOException {
         withConnection((s1, s2) -> {
             s2.setSoTimeout(2000);
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketTimeoutException e) { }
+            expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read());
             s1.getOutputStream().write(99);
             int b = s2.getInputStream().read();
             assertTrue(b == 99);
@@ -158,10 +159,7 @@
     public void testTimedRead5() throws IOException {
         withConnection((s1, s2) -> {
             s2.setSoTimeout(2000);
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketTimeoutException e) { }
+            expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read());
             s2.setSoTimeout(30*3000);
             scheduleWrite(s1.getOutputStream(), 99, 2000);
             int b = s2.getInputStream().read();
@@ -175,10 +173,7 @@
     public void testTimedRead6() throws IOException {
         withConnection((s1, s2) -> {
             s2.setSoTimeout(2000);
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketTimeoutException e) { }
+            expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read());
             s1.getOutputStream().write(99);
             s2.setSoTimeout(0);
             int b = s2.getInputStream().read();
@@ -193,10 +188,7 @@
     public void testTimedRead7() throws IOException {
         withConnection((s1, s2) -> {
             s2.setSoTimeout(2000);
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketTimeoutException e) { }
+            expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read());
             scheduleWrite(s1.getOutputStream(), 99, 2000);
             s2.setSoTimeout(0);
             int b = s2.getInputStream().read();
@@ -211,10 +203,7 @@
         withConnection((s1, s2) -> {
             s2.setSoTimeout(30*1000);
             scheduleClose(s2, 2000);
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketException expected) { }
+            expectThrows(SocketException.class, () -> s2.getInputStream().read());
         });
     }
 
@@ -280,7 +269,7 @@
     public void testTimedAccept1() throws IOException {
         Socket s1 = null;
         Socket s2 = null;
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = boundServerSocket()) {
             s1 = new Socket();
             s1.connect(ss.getLocalSocketAddress());
             ss.setSoTimeout(30*1000);
@@ -295,7 +284,7 @@
      * Test timed accept where a connection is established after a short delay
      */
     public void testTimedAccept2() throws IOException {
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = boundServerSocket()) {
             ss.setSoTimeout(30*1000);
             scheduleConnect(ss.getLocalSocketAddress(), 2000);
             Socket s = ss.accept();
@@ -307,13 +296,17 @@
      * Test timed accept where the accept times out
      */
     public void testTimedAccept3() throws IOException {
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = boundServerSocket()) {
             ss.setSoTimeout(2000);
+            long startMillis = millisTime();
             try {
                 Socket s = ss.accept();
                 s.close();
-                assertTrue(false);
-            } catch (SocketTimeoutException expected) { }
+                fail();
+            } catch (SocketTimeoutException expected) {
+                int timeout = ss.getSoTimeout();
+                checkDuration(startMillis, timeout-100, timeout+2000);
+            }
         }
     }
 
@@ -322,12 +315,12 @@
      * previous accept timed out.
      */
     public void testTimedAccept4() throws IOException {
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = boundServerSocket()) {
             ss.setSoTimeout(2000);
             try {
                 Socket s = ss.accept();
                 s.close();
-                assertTrue(false);
+                fail();
             } catch (SocketTimeoutException expected) { }
             try (Socket s1 = new Socket()) {
                 s1.connect(ss.getLocalSocketAddress());
@@ -342,12 +335,12 @@
      * accept timed out
      */
     public void testTimedAccept5() throws IOException {
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = boundServerSocket()) {
             ss.setSoTimeout(2000);
             try {
                 Socket s = ss.accept();
                 s.close();
-                assertTrue(false);
+                fail();
             } catch (SocketTimeoutException expected) { }
             ss.setSoTimeout(0);
             try (Socket s1 = new Socket()) {
@@ -363,12 +356,12 @@
      * accept timed out and after a short delay
      */
     public void testTimedAccept6() throws IOException {
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = boundServerSocket()) {
             ss.setSoTimeout(2000);
             try {
                 Socket s = ss.accept();
                 s.close();
-                assertTrue(false);
+                fail();
             } catch (SocketTimeoutException expected) { }
             ss.setSoTimeout(0);
             scheduleConnect(ss.getLocalSocketAddress(), 2000);
@@ -381,13 +374,134 @@
      * Test async close of a timed accept
      */
     public void testTimedAccept7() throws IOException {
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = boundServerSocket()) {
             ss.setSoTimeout(30*1000);
-            scheduleClose(ss, 2000);
+            long delay = 2000;
+            scheduleClose(ss, delay);
+            long startMillis = millisTime();
             try {
                 ss.accept().close();
-                assertTrue(false);
-            } catch (SocketException expected) { }
+                fail();
+            } catch (SocketException expected) {
+                checkDuration(startMillis, delay-100, delay+2000);
+            }
+        }
+    }
+
+    /**
+     * Test timed accept with the thread interrupt status set.
+     */
+    public void testTimedAccept8() throws IOException {
+        try (ServerSocket ss = boundServerSocket()) {
+            ss.setSoTimeout(2000);
+            Thread.currentThread().interrupt();
+            long startMillis = millisTime();
+            try {
+                Socket s = ss.accept();
+                s.close();
+                fail();
+            } catch (SocketTimeoutException expected) {
+                // accept should have blocked for 2 seconds
+                int timeout = ss.getSoTimeout();
+                checkDuration(startMillis, timeout-100, timeout+2000);
+                assertTrue(Thread.currentThread().isInterrupted());
+            } finally {
+                Thread.interrupted(); // clear interrupt status
+            }
+        }
+    }
+
+    /**
+     * Test interrupt of thread blocked in timed accept.
+     */
+    public void testTimedAccept9() throws IOException {
+        try (ServerSocket ss = boundServerSocket()) {
+            ss.setSoTimeout(4000);
+            // interrupt thread after 1 second
+            Future<?> interrupter = scheduleInterrupt(Thread.currentThread(), 1000);
+            long startMillis = millisTime();
+            try {
+                Socket s = ss.accept();   // should block for 4 seconds
+                s.close();
+                fail();
+            } catch (SocketTimeoutException expected) {
+                // accept should have blocked for 4 seconds
+                int timeout = ss.getSoTimeout();
+                checkDuration(startMillis, timeout-100, timeout+2000);
+                assertTrue(Thread.currentThread().isInterrupted());
+            } finally {
+                interrupter.cancel(true);
+                Thread.interrupted(); // clear interrupt status
+            }
+        }
+    }
+
+    /**
+     * Test two threads blocked in timed accept where no connection is established.
+     */
+    public void testTimedAccept10() throws Exception {
+        ExecutorService pool = Executors.newFixedThreadPool(2);
+        try (ServerSocket ss = boundServerSocket()) {
+            ss.setSoTimeout(4000);
+
+            long startMillis = millisTime();
+
+            Future<Socket> result1 = pool.submit(ss::accept);
+            Future<Socket> result2 = pool.submit(ss::accept);
+
+            // both tasks should complete with SocketTimeoutException
+            Throwable e = expectThrows(ExecutionException.class, result1::get);
+            assertTrue(e.getCause() instanceof SocketTimeoutException);
+            e = expectThrows(ExecutionException.class, result2::get);
+            assertTrue(e.getCause() instanceof SocketTimeoutException);
+
+            // should get here in 4 seconds, not 8 seconds
+            int timeout = ss.getSoTimeout();
+            checkDuration(startMillis, timeout-100, timeout+2000);
+        } finally {
+            pool.shutdown();
+        }
+    }
+
+    /**
+     * Test two threads blocked in timed accept where one connection is established.
+     */
+    public void testTimedAccept11() throws Exception {
+        ExecutorService pool = Executors.newFixedThreadPool(2);
+        try (ServerSocket ss = boundServerSocket()) {
+            ss.setSoTimeout(4000);
+
+            long startMillis = millisTime();
+
+            Future<Socket> result1 = pool.submit(ss::accept);
+            Future<Socket> result2 = pool.submit(ss::accept);
+
+            // establish connection after 2 seconds
+            scheduleConnect(ss.getLocalSocketAddress(), 2000);
+
+            // one task should have accepted the connection, the other should
+            // have completed with SocketTimeoutException
+            Socket s1 = null;
+            try {
+                s1 = result1.get();
+                s1.close();
+            } catch (ExecutionException e) {
+                assertTrue(e.getCause() instanceof SocketTimeoutException);
+            }
+            Socket s2 = null;
+            try {
+                s2 = result2.get();
+                s2.close();
+            } catch (ExecutionException e) {
+                assertTrue(e.getCause() instanceof SocketTimeoutException);
+            }
+            assertTrue((s1 != null) ^ (s2 != null));
+
+            // should get here in 4 seconds, not 8 seconds
+            int timeout = ss.getSoTimeout();
+            checkDuration(startMillis, timeout-100, timeout+2000);
+        } finally {
+            pool.shutdown();
         }
     }
 
@@ -411,6 +525,19 @@
         }
     }
 
+    /**
+     * Returns a ServerSocket bound to a port on the loopback address
+     */
+    static ServerSocket boundServerSocket() throws IOException {
+        var loopback = InetAddress.getLoopbackAddress();
+        ServerSocket ss = new ServerSocket();
+        ss.bind(new InetSocketAddress(loopback, 0));
+        return ss;
+    }
+
+    /**
+     * An operation that accepts two arguments and may throw IOException
+     */
     interface ThrowingBiConsumer<T, U> {
         void accept(T t, U u) throws IOException;
     }
@@ -423,7 +550,7 @@
     {
         Socket s1 = null;
         Socket s2 = null;
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = boundServerSocket()) {
             s1 = new Socket();
             s1.connect(ss.getLocalSocketAddress());
             s2 = ss.accept();
@@ -446,6 +573,13 @@
     }
 
     /**
+     * Schedule thread to be interrupted after a delay
+     */
+    static Future<?> scheduleInterrupt(Thread thread, long delay) {
+        return schedule(() -> thread.interrupt(), delay);
+    }
+
+    /**
      * Schedule a thread to connect to the given end point after a delay
      */
     static void scheduleConnect(SocketAddress remote, long delay) {
@@ -482,12 +616,36 @@
         scheduleWrite(out, new byte[] { (byte)b }, delay);
     }
 
-    static void schedule(Runnable task, long delay) {
+    static Future<?> schedule(Runnable task, long delay) {
         ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
         try {
-            executor.schedule(task, delay, TimeUnit.MILLISECONDS);
+            return executor.schedule(task, delay, TimeUnit.MILLISECONDS);
         } finally {
             executor.shutdown();
         }
     }
+
+    /**
+     * Returns the current time in milliseconds.
+     */
+    private static long millisTime() {
+        long now = System.nanoTime();
+        return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
+    }
+
+    /**
+     * Check the duration of a task
+     * @param start start time, in milliseconds
+     * @param min minimum expected duration, in milliseconds
+     * @param max maximum expected duration, in milliseconds
+     * @return the duration (now - start), in milliseconds
+     */
+    private static long checkDuration(long start, long min, long max) {
+        long duration = millisTime() - start;
+        assertTrue(duration >= min,
+                "Duration " + duration + "ms, expected >= " + min + "ms");
+        assertTrue(duration <= max,
+                "Duration " + duration + "ms, expected <= " + max + "ms");
+        return duration;
+    }
 }
--- a/test/jdk/java/net/Socket/UdpSocket.java	Wed May 29 22:17:48 2019 -0400
+++ b/test/jdk/java/net/Socket/UdpSocket.java	Thu May 30 07:19:19 2019 +0100
@@ -23,24 +23,36 @@
 
 /**
  * @test
- * @run main UdpSocket
+ * @run testng/othervm -Dsun.net.maxDatagramSockets=32 UdpSocket
  * @summary Basic test for a Socket to a UDP socket
  */
 
 import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.nio.ByteBuffer;
 import java.nio.channels.DatagramChannel;
+import java.security.Permission;
 import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.ArrayDeque;
+import java.util.Deque;
 
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+@Test
 public class UdpSocket {
 
-    static final String MESSAGE = "hello";
+    /**
+     * Test using the Socket API to send/receive datagrams
+     */
+    public void testSendReceive() throws IOException {
+        final String MESSAGE = "hello";
 
-    public static void main(String[] args) throws IOException {
         try (DatagramChannel dc = DatagramChannel.open()) {
             var loopback = InetAddress.getLoopbackAddress();
             dc.bind(new InetSocketAddress(loopback, 0));
@@ -56,8 +68,7 @@
                 var buf = ByteBuffer.allocate(100);
                 SocketAddress remote = dc.receive(buf);
                 buf.flip();
-                if (buf.remaining() != MESSAGE.length())
-                    throw new RuntimeException("Unexpected size");
+                assertTrue(buf.remaining() == MESSAGE.length(), "Unexpected size");
 
                 // echo the datagram
                 dc.send(buf, remote);
@@ -65,11 +76,71 @@
                 // receive datagram with the socket input stream
                 byte[] array2 = new byte[100];
                 int n = s.getInputStream().read(array2);
-                if (n != MESSAGE.length())
-                    throw new RuntimeException("Unexpected size");
-                if (!Arrays.equals(array1, 0, n, array2, 0, n))
-                    throw new RuntimeException("Unexpected contents");
+                assertTrue(n == MESSAGE.length(), "Unexpected size");
+                assertEquals(Arrays.copyOf(array1, n), Arrays.copyOf(array2, n),
+                            "Unexpected contents");
             }
         }
     }
+
+    /**
+     * Test that the number of UDP sockets is limited when running with a
+     * security manager.
+     */
+    public void testMaxSockets() throws Exception {
+        int limit = Integer.getInteger("sun.net.maxDatagramSockets");
+
+        // security manager grants all permissions
+        var securityManager = new SecurityManager() {
+            @Override public void checkPermission(Permission perm) { }
+        };
+
+        System.setSecurityManager(securityManager);
+        Deque<Socket> sockets = new ArrayDeque<>();
+        try {
+            // create the maximum number of sockets
+            for (int i=0; i<limit; i++) {
+                sockets.offer(newUdpSocket());
+            }
+
+            // try to create another socket - should fail
+            try {
+                Socket s = newUdpSocket();
+                s.close();
+                assertTrue(false);
+            } catch (IOException expected) { }
+
+            // close one socket
+            sockets.pop().close();
+
+            // try to create another socket - should succeed
+            Socket s = newUdpSocket();
+
+            // unreference the socket and wait for it to be closed by the cleaner
+            var ref = new WeakReference<>(s);
+            s = null;
+            while (ref.get() != null) {
+                System.gc();
+                Thread.sleep(100);
+            }
+
+            // try to create another socket - should succeed
+            s = newUdpSocket();
+            s.close();
+        } finally {
+            closeAll(sockets);
+            System.setSecurityManager(null);
+        }
+    }
+
+    private Socket newUdpSocket() throws IOException {
+        return new Socket(InetAddress.getLoopbackAddress(), 8000, false);
+    }
+
+    private void closeAll(Deque<Socket> sockets) throws IOException {
+        Socket s;
+        while ((s = sockets.poll()) != null) {
+            s.close();
+        }
+    }
 }
--- a/test/jdk/java/net/Socket/asyncClose/AsyncClose.java	Wed May 29 22:17:48 2019 -0400
+++ b/test/jdk/java/net/Socket/asyncClose/AsyncClose.java	Thu May 30 07:19:19 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2019, 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
@@ -35,6 +35,7 @@
  *          cause any thread blocked on the socket to throw a SocketException.
  * @run main AsyncClose
  * @run main/othervm -Djava.net.preferIPv4Stack=true AsyncClose
+ * @run main/othervm -Djdk.net.usePlainSocketImpl AsyncClose
  */
 
 public class AsyncClose {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/SocketImpl/BadUsages.java	Thu May 30 07:19:19 2019 +0100
@@ -0,0 +1,490 @@
+/*
+ * Copyright (c) 2019, 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 8221481
+ * @compile/module=java.base java/net/PlatformSocketImpl.java
+ * @run testng/othervm BadUsages
+ * @summary Test the platform SocketImpl when used in unintended ways
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketImpl;
+import java.net.SocketOption;
+import java.net.SocketOptions;
+import java.net.StandardSocketOptions;
+import java.util.Set;
+
+import java.net.PlatformSocketImpl;  // test helper
+
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+/**
+ * SocketImpl does not specify how the SocketImpl behaves when used in ways
+ * that are not intended, e.g. invoking socket operations before the socket is
+ * created or trying to establish a connection after the socket is connected or
+ * closed.
+ *
+ * This test exercises the platform SocketImpl to test that it is reliable, and
+ * throws reasonable exceptions, for these scenarios.
+ */
+
+@Test
+public class BadUsages {
+
+    /**
+     * Test create when already created.
+     */
+    public void testCreate1() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            expectThrows(IOException.class, () -> impl.create(true));
+        }
+    }
+
+    /**
+     * Test create when closed.
+     */
+    public void testCreate2() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        impl.close();
+        expectThrows(IOException.class, () -> impl.create(true));
+    }
+
+    /**
+     * Test connect when not created.
+     */
+    public void testConnect1() throws IOException {
+        try (var ss = new ServerSocket(0)) {
+            var impl = new PlatformSocketImpl(false);
+            var address = ss.getInetAddress();
+            int port = ss.getLocalPort();
+            expectThrows(IOException.class, () -> impl.connect(address, port));
+        }
+    }
+
+    /**
+     * Test connect with unsupported address type.
+     */
+    public void testConnect2() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            var remote = new SocketAddress() { };
+            expectThrows(IOException.class, () -> impl.connect(remote, 0));
+        }
+    }
+
+    /**
+     * Test connect with an unresolved address.
+     */
+    public void testConnect3() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            var remote = new InetSocketAddress("blah-blah.blah-blah", 80);
+            expectThrows(IOException.class, () -> impl.connect(remote, 0));
+        }
+    }
+
+    /**
+     * Test connect when already connected.
+     */
+    public void testConnect4() throws IOException {
+        try (var ss = new ServerSocket();
+             var impl = new PlatformSocketImpl(false)) {
+            var loopback = InetAddress.getLoopbackAddress();
+            ss.bind(new InetSocketAddress(loopback, 0));
+            impl.create(true);
+            int port = ss.getLocalPort();
+            impl.connect(loopback, port);
+            expectThrows(IOException.class, () -> impl.connect(loopback, port));
+        }
+    }
+
+    /**
+     * Test connect when closed.
+     */
+    public void testConnect5() throws IOException {
+        try (var ss = new ServerSocket(0)) {
+            var impl = new PlatformSocketImpl(false);
+            impl.close();
+            String host = ss.getInetAddress().getHostAddress();
+            int port = ss.getLocalPort();
+            expectThrows(IOException.class, () -> impl.connect(host, port));
+        }
+    }
+
+    /**
+     * Test bind when not created.
+     */
+    public void testBind1() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        var loopback = InetAddress.getLoopbackAddress();
+        expectThrows(IOException.class, () -> impl.bind(loopback, 0));
+    }
+
+    /**
+     * Test bind when already bound.
+     */
+    public void testBind2() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            var loopback = InetAddress.getLoopbackAddress();
+            impl.bind(loopback, 0);
+            expectThrows(IOException.class, () -> impl.bind(loopback, 0));
+        }
+    }
+
+    /**
+     * Test bind when connected.
+     */
+    public void testBind3() throws IOException {
+        try (var ss = new ServerSocket();
+             var impl = new PlatformSocketImpl(false)) {
+            var loopback = InetAddress.getLoopbackAddress();
+            ss.bind(new InetSocketAddress(loopback, 0));
+            impl.create(true);
+            impl.connect(ss.getLocalSocketAddress(), 0);
+            expectThrows(IOException.class, () -> impl.bind(loopback, 0));
+        }
+    }
+
+    /**
+     * Test bind when closed.
+     */
+    public void testBind4() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        impl.close();
+        var loopback = InetAddress.getLoopbackAddress();
+        expectThrows(IOException.class, () -> impl.bind(loopback, 0));
+    }
+
+
+    /**
+     * Test listen when not created.
+     */
+    public void testListen1() {
+        var impl = new PlatformSocketImpl(false);
+        expectThrows(IOException.class, () -> impl.listen(16));
+    }
+
+    /**
+     * Test listen when not bound.
+     */
+    public void testListen2() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            expectThrows(IOException.class, () -> impl.listen(16));
+        }
+    }
+
+    /**
+     * Test listen when closed.
+     */
+    public void testListen3() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        impl.close();
+        expectThrows(IOException.class, () -> impl.listen(16));
+    }
+
+    /**
+     * Test accept when not created.
+     */
+    public void testAccept1() throws IOException {
+        var impl = new PlatformSocketImpl(true);
+        var si = new PlatformSocketImpl(false);
+        expectThrows(IOException.class, () -> impl.accept(si));
+    }
+
+    /**
+     * Test accept when not bound.
+     */
+    public void testAccept2() throws IOException {
+        try (var impl = new PlatformSocketImpl(true)) {
+            impl.create(true);
+            var si = new PlatformSocketImpl(false);
+            expectThrows(IOException.class, () -> impl.accept(si));
+        }
+    }
+
+    /**
+     * Test accept when not a stream socket.
+     */
+    public void testAccept3() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(false);
+            impl.bind(InetAddress.getLoopbackAddress(), 0);
+            var si = new PlatformSocketImpl(false);
+            expectThrows(IOException.class, () -> impl.accept(si));
+        }
+    }
+
+    /**
+     * Test accept when closed.
+     */
+    public void testAccept4() throws IOException {
+        var impl = new PlatformSocketImpl(true);
+        impl.close();
+        var si = new PlatformSocketImpl(false);
+        expectThrows(IOException.class, () -> impl.accept(si));
+    }
+
+    /**
+     * Test accept with SocketImpl that is already created.
+     */
+    public void testAccept5() throws IOException {
+        try (var impl = new PlatformSocketImpl(true);
+             var si = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            impl.bind(InetAddress.getLoopbackAddress(), 0);
+            si.create(true);
+            expectThrows(IOException.class, () -> impl.accept(si));
+        }
+    }
+
+    /**
+     * Test accept with SocketImpl that is closed.
+     */
+    public void testAccept6() throws IOException {
+        try (var impl = new PlatformSocketImpl(true);
+             var si = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            impl.bind(InetAddress.getLoopbackAddress(), 0);
+            si.create(true);
+            si.close();
+            expectThrows(IOException.class, () -> impl.accept(si));
+        }
+    }
+
+    /**
+     * Test available when not created.
+     */
+    public void testAvailable1() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        expectThrows(IOException.class, () -> impl.available());
+    }
+
+    /**
+     * Test available when created but not connected.
+     */
+    public void testAvailable2() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            expectThrows(IOException.class, () -> impl.available());
+        }
+    }
+
+    /**
+     * Test available when closed.
+     */
+    public void testAvailable3() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        impl.close();
+        expectThrows(IOException.class, () -> impl.available());
+    }
+
+    /**
+     * Test setOption when not created.
+     */
+    public void testSetOption1() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        expectThrows(IOException.class,
+                     () -> impl.setOption(StandardSocketOptions.SO_REUSEADDR, true));
+        // legacy
+        expectThrows(SocketException.class,
+                     () -> impl.setOption(SocketOptions.SO_REUSEADDR, true));
+    }
+
+    /**
+     * Test setOption when closed.
+     */
+    public void testSetOption2() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        impl.close();
+        expectThrows(IOException.class,
+                     () -> impl.setOption(StandardSocketOptions.SO_REUSEADDR, true));
+        // legacy
+        expectThrows(SocketException.class,
+                     () -> impl.setOption(SocketOptions.SO_REUSEADDR, true));
+    }
+
+    /**
+     * Test setOption with unsupported option.
+     */
+    public void testSetOption3() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            var opt = new SocketOption<String>() {
+                @Override public String name() { return "birthday"; }
+                @Override public Class<String> type() { return String.class; }
+            };
+            expectThrows(UnsupportedOperationException.class, () -> impl.setOption(opt, ""));
+            // legacy
+            expectThrows(SocketException.class, () -> impl.setOption(-1, ""));
+        }
+    }
+
+    /**
+     * Test setOption(int, Object) with invalid values.
+     */
+    public void testSetOption4() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            expectThrows(SocketException.class,
+                         () -> impl.setOption(SocketOptions.SO_REUSEADDR, -1));
+            expectThrows(SocketException.class,
+                         () -> impl.setOption(SocketOptions.SO_TIMEOUT, -1));
+            expectThrows(SocketException.class,
+                         () -> impl.setOption(SocketOptions.SO_SNDBUF, -1));
+            expectThrows(SocketException.class,
+                         () -> impl.setOption(SocketOptions.SO_RCVBUF, -1));
+        }
+    }
+
+    /**
+     * Test getOption when not created.
+     */
+    public void testGetOption1() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        expectThrows(IOException.class,
+                     () -> impl.getOption(StandardSocketOptions.SO_REUSEADDR));
+        expectThrows(SocketException.class,
+                     () -> impl.getOption(-1));
+    }
+
+    /**
+     * Test getOption when closed.
+     */
+    public void testGetOption2() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        impl.close();
+        expectThrows(IOException.class,
+                     () -> impl.getOption(StandardSocketOptions.SO_REUSEADDR));
+        expectThrows(SocketException.class,
+                     () -> impl.getOption(SocketOptions.SO_REUSEADDR));
+    }
+
+    /**
+     * Test getOption with unsupported option.
+     */
+    public void testGetOption3() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            var opt = new SocketOption<String>() {
+                @Override public String name() { return "birthday"; }
+                @Override public Class<String> type() { return String.class; }
+            };
+            expectThrows(UnsupportedOperationException.class, () -> impl.getOption(opt));
+            expectThrows(SocketException.class, () -> impl.getOption(-1));
+        }
+    }
+
+    /**
+     * Test shutdownInput when not created.
+     */
+    public void testShutdownInput1() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        expectThrows(IOException.class, () -> impl.shutdownInput());
+    }
+
+    /**
+     * Test shutdownInput when not connected.
+     */
+    public void testShutdownInput2() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            expectThrows(IOException.class, () -> impl.shutdownInput());
+        }
+    }
+
+    /**
+     * Test shutdownInput when closed.
+     */
+    public void testShutdownInput3() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        impl.close();
+        expectThrows(IOException.class, () -> impl.shutdownInput());
+    }
+
+    /**
+     * Test shutdownOutput when not created.
+     */
+    public void testShutdownOutput1() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        expectThrows(IOException.class, () -> impl.shutdownOutput());
+    }
+
+    /**
+     * Test shutdownOutput when not connected.
+     */
+    public void testShutdownOutput2() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            expectThrows(IOException.class, () -> impl.shutdownOutput());
+        }
+    }
+
+    /**
+     * Test shutdownOutput when closed.
+     */
+    public void testShutdownOutput3() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        impl.close();
+        expectThrows(IOException.class, () -> impl.shutdownOutput());
+    }
+
+    /**
+     * Test sendUrgentData when not created.
+     */
+    public void testSendUrgentData1() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        expectThrows(IOException.class, () -> impl.sendUrgentData(0));
+    }
+
+    /**
+     * Test sendUrgentData when not connected.
+     */
+    public void testSendUrgentData2() throws IOException {
+        try (var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            expectThrows(IOException.class, () -> impl.sendUrgentData(0));
+        }
+    }
+
+    /**
+     * Test sendUrgentData when closed.
+     */
+    public void testSendUrgentData3() throws IOException {
+        var impl = new PlatformSocketImpl(false);
+        impl.close();
+        expectThrows(IOException.class, () -> impl.sendUrgentData(0));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/SocketImpl/CompareSocketOptions.java	Thu May 30 07:19:19 2019 +0100
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019, 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 8221481
+ * @modules java.base/java.net:+open java.base/sun.nio.ch:+open
+ * @run testng CompareSocketOptions
+ * @summary Compare the set of socket options supported by the old and new SocketImpls
+ */
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketImpl;
+
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+@Test
+public class CompareSocketOptions {
+
+    /**
+     * Test that the old and new platform SocketImpl support the same set of
+     * client socket options.
+     */
+    public void testClientSocketSupportedOptions() throws IOException {
+        Socket s1 = new Socket(createOldSocketImpl(false)) { };
+        Socket s2 = new Socket(createNewSocketImpl(false)) { };
+        assertEquals(s1.supportedOptions(), s2.supportedOptions());
+    }
+
+    /**
+     * Test that the old and new platform SocketImpl support the same set of
+     * server socket options.
+     */
+    public void testServerSocketSupportedOptions() throws IOException {
+        ServerSocket ss1 = new ServerSocket(createOldSocketImpl(true)) { };
+        ServerSocket ss2 = new ServerSocket(createNewSocketImpl(true)) { };
+        assertEquals(ss1.supportedOptions(), ss2.supportedOptions());
+    }
+
+    private static SocketImpl createOldSocketImpl(boolean server) {
+        return newPlatformSocketImpl("java.net.PlainSocketImpl", server);
+    }
+
+    private static SocketImpl createNewSocketImpl(boolean server) {
+        return newPlatformSocketImpl("sun.nio.ch.NioSocketImpl", server);
+    }
+
+    private static SocketImpl newPlatformSocketImpl(String name, boolean server) {
+        try {
+            var ctor = Class.forName(name).getDeclaredConstructor(boolean.class);
+            ctor.setAccessible(true);
+            return (SocketImpl) ctor.newInstance(server);
+        } catch (Exception e) {
+            fail("Should not get here", e);
+            return null;
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/SocketImpl/java.base/java/net/PlatformSocketImpl.java	Thu May 30 07:19:19 2019 +0100
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2019, 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 java.net;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * A SocketImpl that delegates all operations to a platform SocketImpl. It also
+ * overrides all methods with public methods and implements AutoCloseable to make
+ * it easy to write tests.
+ */
+
+public class PlatformSocketImpl extends SocketImpl implements AutoCloseable {
+    private final SocketImpl impl;
+
+    public PlatformSocketImpl(boolean server) {
+        impl = new sun.nio.ch.NioSocketImpl(server);
+    }
+
+    @Override
+    public void close() throws IOException {
+        impl.close();
+    }
+
+    @Override
+    public void create(boolean stream) throws IOException {
+        impl.create(stream);
+    }
+
+    @Override
+    public void connect(SocketAddress remote, int millis) throws IOException {
+        impl.connect(remote, millis);
+    }
+
+    @Override
+    public void connect(String host, int port) throws IOException {
+        impl.connect(host, port);
+    }
+
+    @Override
+    public void connect(InetAddress address, int port) throws IOException {
+        impl.connect(address, port);
+    }
+
+    @Override
+    public void bind(InetAddress address, int port) throws IOException {
+        impl.bind(address, port);
+    }
+
+    @Override
+    public void listen(int backlog) throws IOException {
+        impl.listen(backlog);
+    }
+
+    @Override
+    public void accept(SocketImpl si) throws IOException {
+        impl.accept(((PlatformSocketImpl) si).impl);
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        return impl.getInputStream();
+    }
+
+    @Override
+    public OutputStream getOutputStream() throws IOException {
+        return impl.getOutputStream();
+    }
+
+    @Override
+    public int available() throws IOException {
+        return impl.available();
+    }
+
+    @Override
+    public Set<SocketOption<?>> supportedOptions() {
+        return impl.supportedOptions();
+    }
+
+    @Override
+    public <T> void setOption(SocketOption<T> opt, T value) throws IOException {
+        impl.setOption(opt, value);
+    }
+
+    @Override
+    public <T> T getOption(SocketOption<T> opt) throws IOException {
+        return impl.getOption(opt);
+    }
+
+    @Override
+    public void setOption(int opt, Object value) throws SocketException {
+        impl.setOption(opt, value);
+    }
+
+    @Override
+    public Object getOption(int opt) throws SocketException {
+       return impl.getOption(opt);
+    }
+
+    @Override
+    public void shutdownInput() throws IOException {
+       impl.shutdownInput();
+    }
+
+    @Override
+    public void shutdownOutput() throws IOException {
+        impl.shutdownOutput();
+    }
+
+    @Override
+    public boolean supportsUrgentData() {
+        return impl.supportsUrgentData();
+    }
+
+    @Override
+    public void sendUrgentData(int data) throws IOException {
+        impl.sendUrgentData(data);
+    }
+}
--- a/test/jdk/java/net/SocketOption/OptionsTest.java	Wed May 29 22:17:48 2019 -0400
+++ b/test/jdk/java/net/SocketOption/OptionsTest.java	Thu May 30 07:19:19 2019 +0100
@@ -38,23 +38,24 @@
 
 public class OptionsTest {
 
-    static class Test {
-        Test(SocketOption<?> option, Object testValue) {
+    static class Test<T> {
+        final SocketOption<T> option;
+        final T value;
+        Test(SocketOption<T> option, T value) {
             this.option = option;
-            this.testValue = testValue;
+            this.value = value;
         }
-        static Test create (SocketOption<?> option, Object testValue) {
-            return new Test(option, testValue);
+        static <T> Test<T> create(SocketOption<T> option, T value) {
+            return new Test<T>(option, value);
         }
-        Object option;
-        Object testValue;
+
     }
 
     // The tests set the option using the new API, read back the set value
     // which could be diferent, and then use the legacy get API to check
     // these values are the same
 
-    static Test[] socketTests = new Test[] {
+    static Test<?>[] socketTests = new Test<?>[] {
         Test.create(StandardSocketOptions.SO_KEEPALIVE, Boolean.TRUE),
         Test.create(StandardSocketOptions.SO_SNDBUF, Integer.valueOf(10 * 100)),
         Test.create(StandardSocketOptions.SO_RCVBUF, Integer.valueOf(8 * 100)),
@@ -66,7 +67,7 @@
         Test.create(StandardSocketOptions.IP_TOS, Integer.valueOf(255))  //upper-bound
     };
 
-    static Test[] serverSocketTests = new Test[] {
+    static Test<?>[] serverSocketTests = new Test<?>[] {
         Test.create(StandardSocketOptions.SO_RCVBUF, Integer.valueOf(8 * 100)),
         Test.create(StandardSocketOptions.SO_REUSEADDR, Boolean.FALSE),
         Test.create(StandardSocketOptions.SO_REUSEPORT, Boolean.FALSE),
@@ -75,7 +76,7 @@
         Test.create(StandardSocketOptions.IP_TOS, Integer.valueOf(255))  //upper-bound
     };
 
-    static Test[] dgSocketTests = new Test[] {
+    static Test<?>[] datagramSocketTests = new Test<?>[] {
         Test.create(StandardSocketOptions.SO_SNDBUF, Integer.valueOf(10 * 100)),
         Test.create(StandardSocketOptions.SO_RCVBUF, Integer.valueOf(8 * 100)),
         Test.create(StandardSocketOptions.SO_REUSEADDR, Boolean.FALSE),
@@ -85,7 +86,7 @@
         Test.create(StandardSocketOptions.IP_TOS, Integer.valueOf(255))  //upper-bound
     };
 
-    static Test[] mcSocketTests = new Test[] {
+    static Test<?>[] multicastSocketTests = new Test<?>[] {
         Test.create(StandardSocketOptions.IP_MULTICAST_IF, getNetworkInterface()),
         Test.create(StandardSocketOptions.IP_MULTICAST_TTL, Integer.valueOf(0)),   // lower-bound
         Test.create(StandardSocketOptions.IP_MULTICAST_TTL, Integer.valueOf(10)),
@@ -97,7 +98,7 @@
         try {
             Enumeration<NetworkInterface> nifs = NetworkInterface.getNetworkInterfaces();
             while (nifs.hasMoreElements()) {
-                NetworkInterface ni = (NetworkInterface)nifs.nextElement();
+                NetworkInterface ni = nifs.nextElement();
                 if (ni.supportsMulticast()) {
                     return ni;
                 }
@@ -107,99 +108,110 @@
         return null;
     }
 
+    static boolean okayToTest(Socket s, SocketOption<?> option) {
+        if (option == StandardSocketOptions.SO_REUSEPORT) {
+            // skip SO_REUSEPORT if option is not supported
+            return s.supportedOptions().contains(StandardSocketOptions.SO_REUSEPORT);
+        }
+        if (option == StandardSocketOptions.IP_TOS && s.isConnected()) {
+            // skip IP_TOS if connected
+            return false;
+        }
+        return true;
+    }
+
+    static <T> void testEqual(SocketOption<T> option, T value1, T value2) {
+        if (!value1.equals(value2)) {
+            throw new RuntimeException("Test of " + option.name() + " failed: "
+                    + value1 + " != " + value2);
+        }
+    }
+
+    static <T> void test(Socket s, Test<T> test) throws Exception {
+        SocketOption<T> option = test.option;
+        s.setOption(option, test.value);
+        T value1 = s.getOption(test.option);
+        T value2 = (T) legacyGetOption(Socket.class, s, test.option);
+        testEqual(option, value1, value2);
+    }
+
+    static <T> void test(ServerSocket ss, Test<T> test) throws Exception {
+        SocketOption<T> option = test.option;
+        ss.setOption(option, test.value);
+        T value1 = ss.getOption(test.option);
+        T value2 = (T) legacyGetOption(ServerSocket.class, ss, test.option);
+        testEqual(option, value1, value2);
+    }
+
+    static <T> void test(DatagramSocket ds, Test<T> test) throws Exception {
+        SocketOption<T> option = test.option;
+        ds.setOption(option, test.value);
+        T value1 = ds.getOption(test.option);
+        T value2 = (T) legacyGetOption(ds.getClass(), ds, test.option);
+        testEqual(option, value1, value2);
+    }
+
+    @SuppressWarnings("try")
     static void doSocketTests() throws Exception {
-        try (
-            ServerSocket srv = new ServerSocket(0, 50, InetAddress.getLoopbackAddress());
-            Socket c = new Socket(InetAddress.getLoopbackAddress(), srv.getLocalPort());
-            Socket s = srv.accept();
-        ) {
-            Set<SocketOption<?>> options = c.supportedOptions();
-            boolean reuseport = options.contains(StandardSocketOptions.SO_REUSEPORT);
-            for (int i=0; i<socketTests.length; i++) {
-                Test test = socketTests[i];
-                if (!(test.option == StandardSocketOptions.SO_REUSEPORT && !reuseport)) {
-                    c.setOption((SocketOption)test.option, test.testValue);
-                    Object getval = c.getOption((SocketOption)test.option);
-                    Object legacyget = legacyGetOption(Socket.class, c,test.option);
-                    if (!getval.equals(legacyget)) {
-                        Formatter f = new Formatter();
-                        f.format("S Err %d: %s/%s", i, getval, legacyget);
-                        throw new RuntimeException(f.toString());
-                    }
+        // unconnected socket
+        try (Socket s = new Socket()) {
+            for (Test<?> test : socketTests) {
+                if (okayToTest(s, test.option)) {
+                    test(s, test);
                 }
             }
         }
-    }
 
-    static void doDgSocketTests() throws Exception {
-        try (
-            DatagramSocket c = new DatagramSocket(0);
-        ) {
-            Set<SocketOption<?>> options = c.supportedOptions();
-            boolean reuseport = options.contains(StandardSocketOptions.SO_REUSEPORT);
-            for (int i=0; i<dgSocketTests.length; i++) {
-                Test test = dgSocketTests[i];
-                if (!(test.option == StandardSocketOptions.SO_REUSEPORT && !reuseport)) {
-                    c.setOption((SocketOption)test.option, test.testValue);
-                    Object getval = c.getOption((SocketOption)test.option);
-                    Object legacyget = legacyGetOption(DatagramSocket.class, c,test.option);
-                    if (!getval.equals(legacyget)) {
-                        Formatter f = new Formatter();
-                        f.format("DG Err %d: %s/%s", i, getval, legacyget);
-                        throw new RuntimeException(f.toString());
+        // connected socket
+        try (ServerSocket ss = new ServerSocket()) {
+            var loopback = InetAddress.getLoopbackAddress();
+            ss.bind(new InetSocketAddress(loopback, 0));
+            try (Socket s1 = new Socket()) {
+                s1.connect(ss.getLocalSocketAddress());
+                try (Socket s2 = ss.accept()) {
+                    for (Test<?> test : socketTests) {
+                        if (okayToTest(s1, test.option)) {
+                            test(s1, test);
+                        }
                     }
                 }
             }
         }
     }
 
-    static void doMcSocketTests() throws Exception {
-        try (
-            MulticastSocket c = new MulticastSocket(0);
-        ) {
-            for (int i=0; i<mcSocketTests.length; i++) {
-                Test test = mcSocketTests[i];
-                c.setOption((SocketOption)test.option, test.testValue);
-                Object getval = c.getOption((SocketOption)test.option);
-                Object legacyget = legacyGetOption(MulticastSocket.class, c,test.option);
-                if (!getval.equals(legacyget)) {
-                    Formatter f = new Formatter();
-                    f.format("MC Err %d: %s/%s", i, getval, legacyget);
-                    throw new RuntimeException(f.toString());
+    static void doServerSocketTests() throws Exception {
+        try (ServerSocket ss = new ServerSocket(0)) {
+            Set<SocketOption<?>> options = ss.supportedOptions();
+            boolean reuseport = options.contains(StandardSocketOptions.SO_REUSEPORT);
+            for (Test<?> test : serverSocketTests) {
+                if (!(test.option == StandardSocketOptions.SO_REUSEPORT && !reuseport)) {
+                    test(ss, test);
                 }
             }
         }
     }
 
-    static void doServerSocketTests() throws Exception {
-        try (
-            ServerSocket c = new ServerSocket(0);
-        ) {
-            Set<SocketOption<?>> options = c.supportedOptions();
+    static void doDatagramSocketTests() throws Exception {
+        try (DatagramSocket ds = new DatagramSocket(0)) {
+            Set<SocketOption<?>> options = ds.supportedOptions();
             boolean reuseport = options.contains(StandardSocketOptions.SO_REUSEPORT);
-            for (int i=0; i<serverSocketTests.length; i++) {
-                Test test = serverSocketTests[i];
+            for (Test<?> test : datagramSocketTests) {
                 if (!(test.option == StandardSocketOptions.SO_REUSEPORT && !reuseport)) {
-                    c.setOption((SocketOption)test.option, test.testValue);
-                    Object getval = c.getOption((SocketOption)test.option);
-                    Object legacyget = legacyGetOption(
-                        ServerSocket.class, c, test.option
-                    );
-                    if (!getval.equals(legacyget)) {
-                        Formatter f = new Formatter();
-                        f.format("SS Err %d: %s/%s", i, getval, legacyget);
-                        throw new RuntimeException(f.toString());
-                    }
+                    test(ds, test);
                 }
             }
         }
     }
 
-    static Object legacyGetOption(
-        Class<?> type, Object s, Object option)
+    static void doMulticastSocketTests() throws Exception {
+        try (MulticastSocket ms = new MulticastSocket(0)) {
+            for (Test<?> test : multicastSocketTests) {
+                test(ms, test);
+            }
+        }
+    }
 
-        throws Exception
-    {
+    static Object legacyGetOption(Class<?> type, Object s, Object option) throws Exception {
         if (type.equals(Socket.class)) {
             Socket socket = (Socket)s;
             Set<SocketOption<?>> options = socket.supportedOptions();
@@ -291,8 +303,8 @@
         IPSupport.throwSkippedExceptionIfNonOperational();
         doSocketTests();
         doServerSocketTests();
-        doDgSocketTests();
-        doMcSocketTests();
+        doDatagramSocketTests();
+        doMulticastSocketTests();
     }
 
     // Reflectively access jdk.net.Sockets.getOption so that the test can run
--- a/test/jdk/java/net/ipv6tests/TcpTest.java	Wed May 29 22:17:48 2019 -0400
+++ b/test/jdk/java/net/ipv6tests/TcpTest.java	Thu May 30 07:19:19 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2019, 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
@@ -32,6 +32,7 @@
  * @build jdk.test.lib.NetworkConfiguration
  *        jdk.test.lib.Platform
  * @run main TcpTest -d
+ * @run main/othervm -Djdk.net.usePlainSocketImpl TcpTest -d
  */
 
 import java.net.*;
--- a/test/jdk/sun/security/ssl/SSLSocketImpl/NewSocketMethods.java	Wed May 29 22:17:48 2019 -0400
+++ b/test/jdk/sun/security/ssl/SSLSocketImpl/NewSocketMethods.java	Thu May 30 07:19:19 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2019, 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
@@ -197,14 +197,19 @@
             /**
              * test some new methods of java.net.Socket added to merlin.
              */
-            socket.setTrafficClass(8);
-            socket.setReuseAddress(true);
-            System.out.println("Client getTrafficClass(): "
-                        + socket.getTrafficClass());
             System.out.println("Client isInputShutdown() "
                         + socket.isInputShutdown());
+            socket.setReuseAddress(true);
             System.out.println("Client getReuseAddress(): "
                         + socket.getReuseAddress());
+
+            // Solaris does not support set/get of IPV6_TCLASS when connected
+            if (!"SunOS".equals(System.getProperty("os.name"))) {
+                socket.setTrafficClass(8);
+                System.out.println("Client getTrafficClass(): "
+                        + socket.getTrafficClass());
+            }
+
             os.write(237);
             os.flush();
             System.out.println("Client read: " + is.read());