Prototype of NIO based SocketImpl niosocketimpl-branch
authoralanb
Wed, 23 Jan 2019 19:30:59 +0000
branchniosocketimpl-branch
changeset 57110 b848ca1ef778
parent 53457 e3ed96060992
child 57111 a57c4dc7e2fe
Prototype of NIO based SocketImpl
src/java.base/share/classes/java/net/HttpConnectSocketImpl.java
src/java.base/share/classes/java/net/ServerSocket.java
src/java.base/share/classes/java/net/Socket.java
src/java.base/share/classes/java/net/SocksSocketImpl.java
src/java.base/share/classes/sun/nio/ch/Net.java
src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java
src/java.base/share/classes/sun/nio/ch/ServerSocketChannelImpl.java
test/jdk/ProblemList.txt
test/jdk/java/net/Socket/asyncClose/BrokenPipe.java
--- a/src/java.base/share/classes/java/net/HttpConnectSocketImpl.java	Wed Jan 23 19:56:28 2019 +0100
+++ b/src/java.base/share/classes/java/net/HttpConnectSocketImpl.java	Wed Jan 23 19:30:59 2019 +0000
@@ -32,6 +32,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import sun.nio.ch.NioSocketImpl;
+
 /**
  * Basic SocketImpl that relies on the internal HTTP protocol handler
  * implementation to perform the HTTP tunneling and authentication. The
@@ -41,7 +43,7 @@
  * @since 1.8
  */
 
-/*package*/ class HttpConnectSocketImpl extends PlainSocketImpl {
+/*package*/ class HttpConnectSocketImpl extends NioSocketImpl {
 
     private static final String httpURLClazzStr =
                                   "sun.net.www.protocol.http.HttpURLConnection";
@@ -76,12 +78,8 @@
         }
     }
 
-    HttpConnectSocketImpl(String server, int port) {
-        this.server = server;
-        this.port = port;
-    }
-
     HttpConnectSocketImpl(Proxy proxy) {
+        super(false);
         SocketAddress a = proxy.address();
         if ( !(a instanceof InetSocketAddress) )
             throw new IllegalArgumentException("Unsupported address type");
@@ -92,6 +90,16 @@
     }
 
     @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 connect(SocketAddress endpoint, int timeout)
         throws IOException
     {
@@ -117,14 +125,14 @@
         close();
 
         // update the Sockets impl to the impl from the http Socket
-        AbstractPlainSocketImpl psi = (AbstractPlainSocketImpl) httpSocket.impl;
-        this.getSocket().impl = psi;
+        SocketImpl si = httpSocket.impl;
+        ((SocketImpl) this).getSocket().setImpl(si);
 
         // best effort is made to try and reset options previously set
         Set<Map.Entry<Integer,Object>> options = optionsMap.entrySet();
         try {
             for(Map.Entry<Integer,Object> entry : options) {
-                psi.setOption(entry.getKey(), entry.getValue());
+                si.setOption(entry.getKey(), entry.getValue());
             }
         } catch (IOException x) {  /* gulp! */  }
     }
@@ -163,7 +171,11 @@
         URL destURL = new URL(urlString);
         HttpURLConnection conn = (HttpURLConnection) destURL.openConnection(proxy);
         conn.setConnectTimeout(connectTimeout);
-        conn.setReadTimeout(this.timeout);
+        Object value = getOption(SocketOptions.SO_TIMEOUT);
+        if (value != null) {
+            Integer timeout = (Integer) value;
+            conn.setReadTimeout(timeout);
+        }
         conn.connect();
         doTunneling(conn);
         try {
@@ -197,14 +209,4 @@
         else
             return super.getPort();
     }
-
-    @Override
-    protected int getLocalPort() {
-        if (socket != null)
-            return super.getLocalPort();
-        if (external_address != null)
-            return external_address.getPort();
-        else
-            return super.getLocalPort();
-    }
 }
--- a/src/java.base/share/classes/java/net/ServerSocket.java	Wed Jan 23 19:56:28 2019 +0100
+++ b/src/java.base/share/classes/java/net/ServerSocket.java	Wed Jan 23 19:30:59 2019 +0000
@@ -27,6 +27,7 @@
 
 import jdk.internal.access.JavaNetSocketAccess;
 import jdk.internal.access.SharedSecrets;
+import sun.nio.ch.NioSocketImpl;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -296,7 +297,7 @@
         } else {
             // No need to do a checkOldImpl() here, we know it's an up to date
             // SocketImpl!
-            impl = new SocksSocketImpl();
+            impl = new NioSocketImpl(true);
         }
         if (impl != null)
             impl.setServerSocket(this);
@@ -542,41 +543,82 @@
      * @spec JSR-51
      */
     protected final void implAccept(Socket s) throws IOException {
-        SocketImpl si = null;
-        try {
-            if (s.impl == null)
-              s.setImpl();
-            else {
-                s.impl.reset();
+        SocketImpl impl = getImpl();
+        SocketImpl si = s.impl;
+
+        // Socket does not have a SocketImpl
+        if (si == null) {
+            // create a SocketImpl and accept the connection
+            si = Socket.createImpl();
+            impl.accept(si);
+
+            try {
+                // a custom impl has accepted the connection with a NIO SocketImpl
+                if (!(impl instanceof NioSocketImpl) && (si instanceof NioSocketImpl)) {
+                    ((NioSocketImpl) si).postCustomAccept();
+                }
+            } finally {
+                securityCheckAccept(si);  // closes si if permission check fails
             }
-            si = s.impl;
-            s.impl = null;
-            si.address = new InetAddress();
-            si.fd = new FileDescriptor();
-            getImpl().accept(si);
-            SocketCleanable.register(si.fd);   // raw fd has been set
+
+            // bind Socket to the SocketImpl and update socket state
+            s.setImpl(si);
+            s.postAccept();
+            return;
+        }
+
+        // ServerSocket or Socket is using NIO SocketImpl
+        if (impl instanceof NioSocketImpl || si instanceof NioSocketImpl) {
+            // not implemented
+            if (impl instanceof NioSocketImpl && impl.getClass() != NioSocketImpl.class)
+                throw new UnsupportedOperationException();
+
+            // accept connection via new SocketImpl
+            NioSocketImpl nsi = new NioSocketImpl(false);
+            impl.accept(nsi);
+            securityCheckAccept(nsi);  // closes si if permission check fails
 
-            SecurityManager security = System.getSecurityManager();
-            if (security != null) {
-                security.checkAccept(si.getInetAddress().getHostAddress(),
-                                     si.getPort());
-            }
-        } catch (IOException e) {
-            if (si != null)
+            // copy state to the existing SocketImpl and update socket state
+            nsi.copyTo(si);
+            s.postAccept();
+            return;
+        }
+
+        // ServerSocket and Socket bound to custom SocketImpls
+        s.impl = null; // break connection to impl
+        boolean completed = false;
+        try {
+            si.reset();
+            si.fd = new FileDescriptor();
+            si.address = new InetAddress();
+            impl.accept(si);
+            securityCheckAccept(si);  // closes si if permission check fails
+            completed = true;
+        } finally {
+            if (!completed)
                 si.reset();
-            s.impl = si;
-            throw e;
-        } catch (SecurityException e) {
-            if (si != null)
-                si.reset();
-            s.impl = si;
-            throw e;
+            s.impl = si;  // restore connection to impl
         }
-        s.impl = si;
         s.postAccept();
     }
 
     /**
+     * Invokes the security manager's checkAccept method. If the permission
+     * check fails then it closes the SocketImpl.
+     */
+    private void securityCheckAccept(SocketImpl si) throws IOException {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            try {
+                sm.checkAccept(si.getInetAddress().getHostAddress(), si.getPort());
+            } catch (SecurityException se) {
+                si.close();
+                throw se;
+            }
+        }
+    }
+
+    /**
      * Closes this socket.
      *
      * Any thread currently blocked in {@link #accept()} will throw
--- a/src/java.base/share/classes/java/net/Socket.java	Wed Jan 23 19:56:28 2019 +0100
+++ b/src/java.base/share/classes/java/net/Socket.java	Wed Jan 23 19:30:59 2019 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2018, 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
@@ -34,6 +34,7 @@
 import java.security.PrivilegedAction;
 import java.util.Set;
 import java.util.Collections;
+import sun.nio.ch.NioSocketImpl;
 
 /**
  * This class implements client sockets (also called just
@@ -143,7 +144,7 @@
         } else {
             if (p == Proxy.NO_PROXY) {
                 if (factory == null) {
-                    impl = new PlainSocketImpl();
+                    impl = new NioSocketImpl(false);
                     impl.setSocket(this);
                 } else
                     setImpl();
@@ -491,6 +492,20 @@
         });
     }
 
+    static SocketImpl createImpl() {
+        SocketImplFactory factory = Socket.factory;
+        if (factory != null) {
+            return factory.createSocketImpl();
+        } else {
+            return new SocksSocketImpl();
+        }
+    }
+
+    void setImpl(SocketImpl si) {
+         impl = si;
+         impl.setSocket(this);
+    }
+
     /**
      * Sets impl to the system-default type of SocketImpl.
      * @since 1.4
@@ -508,7 +523,6 @@
             impl.setSocket(this);
     }
 
-
     /**
      * Get the {@code SocketImpl} attached to this socket, creating
      * it if necessary.
@@ -907,18 +921,33 @@
             throw new SocketException("Socket is not connected");
         if (isInputShutdown())
             throw new SocketException("Socket input is shutdown");
-        InputStream is = null;
-        try {
-            is = AccessController.doPrivileged(
-                new PrivilegedExceptionAction<>() {
-                    public InputStream run() throws IOException {
-                        return impl.getInputStream();
-                    }
-                });
-        } catch (java.security.PrivilegedActionException e) {
-            throw (IOException) e.getException();
+        // wrap the input stream so that the close method closes this socket
+        return new SocketInputStream(this, impl.getInputStream());
+    }
+
+    private static class SocketInputStream extends InputStream {
+        private final Socket parent;
+        private final InputStream in;
+        SocketInputStream(Socket parent, InputStream in) {
+            this.parent = parent;
+            this.in = in;
         }
-        return is;
+        @Override
+        public int read() throws IOException {
+            return in.read();
+        }
+        @Override
+        public int read(byte b[], int off, int len) throws IOException {
+            return in.read(b, off, len);
+        }
+        @Override
+        public int available() throws IOException {
+            return in.available();
+        }
+        @Override
+        public void close() throws IOException {
+            parent.close();
+        }
     }
 
     /**
@@ -946,18 +975,29 @@
             throw new SocketException("Socket is not connected");
         if (isOutputShutdown())
             throw new SocketException("Socket output is shutdown");
-        OutputStream os = null;
-        try {
-            os = AccessController.doPrivileged(
-                new PrivilegedExceptionAction<>() {
-                    public OutputStream run() throws IOException {
-                        return impl.getOutputStream();
-                    }
-                });
-        } catch (java.security.PrivilegedActionException e) {
-            throw (IOException) e.getException();
+        // wrap the output stream so that the close method closes this socket
+        return new SocketOutputStream(this, impl.getOutputStream());
+    }
+
+    private static class SocketOutputStream extends OutputStream {
+        private final Socket parent;
+        private final OutputStream out;
+        SocketOutputStream(Socket parent, OutputStream out) {
+            this.parent = parent;
+            this.out = out;
         }
-        return os;
+        @Override
+        public void write(int b) throws IOException {
+            out.write(b);
+        }
+        @Override
+        public void write(byte b[], int off, int len) throws IOException {
+            out.write(b, off, len);
+        }
+        @Override
+        public void close() throws IOException {
+            parent.close();
+        }
     }
 
     /**
--- a/src/java.base/share/classes/java/net/SocksSocketImpl.java	Wed Jan 23 19:56:28 2019 +0100
+++ b/src/java.base/share/classes/java/net/SocksSocketImpl.java	Wed Jan 23 19:30:59 2019 +0000
@@ -35,15 +35,15 @@
 import sun.net.SocksProxy;
 import sun.net.spi.DefaultProxySelector;
 import sun.net.www.ParseUtil;
+import sun.nio.ch.NioSocketImpl;
 /* import org.ietf.jgss.*; */
 
 /**
  * SOCKS (V4 & V5) TCP socket implementation (RFC 1928).
- * This is a subclass of PlainSocketImpl.
  * Note this class should <b>NOT</b> be public.
  */
 
-class SocksSocketImpl extends PlainSocketImpl implements SocksConsts {
+class SocksSocketImpl extends NioSocketImpl implements SocksConsts {
     private String server = null;
     private int serverPort = DEFAULT_PORT;
     private InetSocketAddress external_address;
@@ -54,17 +54,12 @@
     /* true if the Proxy has been set programmatically */
     private boolean applicationSetProxy;  /* false */
 
-
     SocksSocketImpl() {
-        // Nothing needed
-    }
-
-    SocksSocketImpl(String server, int port) {
-        this.server = server;
-        this.serverPort = (port == -1 ? DEFAULT_PORT : port);
+        super(false);
     }
 
     SocksSocketImpl(Proxy proxy) {
+        super(false);
         SocketAddress a = proxy.address();
         if (a instanceof InetSocketAddress) {
             InetSocketAddress ad = (InetSocketAddress) a;
@@ -130,16 +125,23 @@
     private int readSocksReply(InputStream in, byte[] data, long deadlineMillis) throws IOException {
         int len = data.length;
         int received = 0;
-        while (received < len) {
-            int count;
-            try {
-                count = ((SocketInputStream)in).read(data, received, len - received, remainingMillis(deadlineMillis));
-            } catch (SocketTimeoutException e) {
-                throw new SocketTimeoutException("Connect timed out");
+        int originalTimeout = (int) getOption(SocketOptions.SO_TIMEOUT);
+        try {
+            while (received < len) {
+                int count;
+                int remaining = remainingMillis(deadlineMillis);
+                setOption(SocketOptions.SO_TIMEOUT, remaining);
+                try {
+                    count = in.read(data, received, len - received);
+                } catch (SocketTimeoutException e) {
+                    throw new SocketTimeoutException("Connect timed out");
+                }
+                if (count < 0)
+                    throw new SocketException("Malformed reply from SOCKS server");
+                received += count;
             }
-            if (count < 0)
-                throw new SocketException("Malformed reply from SOCKS server");
-            received += count;
+        } finally {
+            setOption(SocketOptions.SO_TIMEOUT, originalTimeout);
         }
         return received;
     }
@@ -665,7 +667,7 @@
      * @exception  IOException  if an I/O error occurs when binding this socket.
      */
     protected synchronized void socksBind(InetSocketAddress saddr) throws IOException {
-        if (socket != null) {
+        if (((SocketImpl) this).socket != null) {
             // this is a client socket, not a server socket, don't
             // call the SOCKS proxy for a bind!
             return;
@@ -729,7 +731,7 @@
                     AccessController.doPrivileged(
                         new PrivilegedExceptionAction<>() {
                             public Void run() throws Exception {
-                                cmdsock = new Socket(new PlainSocketImpl());
+                                cmdsock = new Socket(new NioSocketImpl(false));
                                 cmdsock.connect(new InetSocketAddress(server, serverPort));
                                 cmdIn = cmdsock.getInputStream();
                                 cmdOut = cmdsock.getOutputStream();
@@ -760,7 +762,7 @@
                 AccessController.doPrivileged(
                     new PrivilegedExceptionAction<>() {
                         public Void run() throws Exception {
-                            cmdsock = new Socket(new PlainSocketImpl());
+                            cmdsock = new Socket(new NioSocketImpl(false));
                             cmdsock.connect(new InetSocketAddress(server, serverPort));
                             cmdIn = cmdsock.getInputStream();
                             cmdOut = cmdsock.getOutputStream();
@@ -1065,16 +1067,6 @@
     }
 
     @Override
-    protected int getLocalPort() {
-        if (socket != null)
-            return super.getLocalPort();
-        if (external_address != null)
-            return external_address.getPort();
-        else
-            return super.getLocalPort();
-    }
-
-    @Override
     protected void close() throws IOException {
         if (cmdsock != null)
             cmdsock.close();
--- a/src/java.base/share/classes/sun/nio/ch/Net.java	Wed Jan 23 19:56:28 2019 +0100
+++ b/src/java.base/share/classes/sun/nio/ch/Net.java	Wed Jan 23 19:30:59 2019 +0000
@@ -310,6 +310,12 @@
     static final ExtendedSocketOptions extendedOptions =
             ExtendedSocketOptions.getInstance();
 
+    static void setSocketOption(FileDescriptor fd, SocketOption<?> name, Object value)
+        throws IOException
+    {
+        setSocketOption(fd, Net.UNSPEC, name, value);
+    }
+
     static void setSocketOption(FileDescriptor fd, ProtocolFamily family,
                                 SocketOption<?> name, Object value)
         throws IOException
@@ -372,8 +378,13 @@
         setIntOption0(fd, mayNeedConversion, key.level(), key.name(), arg, isIPv6);
     }
 
-    static Object getSocketOption(FileDescriptor fd, ProtocolFamily family,
-                                  SocketOption<?> name)
+    static Object getSocketOption(FileDescriptor fd, SocketOption<?> name)
+        throws IOException
+    {
+        return getSocketOption(fd, Net.UNSPEC, name);
+    }
+
+    static Object getSocketOption(FileDescriptor fd, ProtocolFamily family, SocketOption<?> name)
         throws IOException
     {
         Class<?> type = name.type();
@@ -426,8 +437,7 @@
         return socket(UNSPEC, stream);
     }
 
-    static FileDescriptor socket(ProtocolFamily family, boolean stream)
-        throws IOException {
+    static FileDescriptor socket(ProtocolFamily family, boolean stream) throws IOException {
         boolean preferIPv6 = isIPv6Available() &&
             (family != StandardProtocolFamily.INET);
         return IOUtil.newFD(socket0(preferIPv6, stream, false, fastLoopback));
@@ -524,6 +534,10 @@
     static native int poll(FileDescriptor fd, int events, long timeout)
         throws IOException;
 
+    static int pollNow(FileDescriptor fd, int events) throws IOException {
+        return poll(fd, events, 0);
+    }
+
     /**
      * Polls a connecting socket to test if the connection has been established.
      *
@@ -535,6 +549,10 @@
     public static native int pollConnect(FileDescriptor fd, long timeout)
         throws IOException;
 
+    static int polConnectlNow(FileDescriptor fd) throws IOException {
+        return pollConnect(fd, 0);
+    }
+
     /**
      * Return the number of bytes in the socket input buffer.
      */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java	Wed Jan 23 19:30:59 2019 +0000
@@ -0,0 +1,1222 @@
+/*
+ * Copyright (c) 2018, 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.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.Field;
+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.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+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.access.SharedSecrets;
+import jdk.internal.ref.CleanerFactory;
+import sun.net.NetHooks;
+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 a connect, accept or read is attempted with a timeout then the
+ * socket is changed to non-blocking mode. When in non-blocking mode, operations
+ * that don't complete immediately will poll the socket.
+ *
+ * Behavior differences to examine:
+ * "Connection reset" handling differs to PlainSocketImpl for cases where
+ * an application continues to call read or available after a reset.
+ */
+
+public class NioSocketImpl extends SocketImpl {
+    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, accepting or connecting
+    private final ReentrantLock readLock = new ReentrantLock();
+
+    // Lock held when writing or connecting
+    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;
+
+    // lazily set to true when the socket is configured non-blocking
+    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
+    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;
+
+    /**
+     * Creates a 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 {
+        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(int event, long nanos) throws IOException {
+        long millis;
+        if (nanos == 0) {
+            millis = -1;
+        } else {
+            millis = MILLISECONDS.convert(nanos, NANOSECONDS);
+        }
+        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(int event) throws IOException {
+        park(event, 0);
+    }
+
+    /**
+     * Ensures that the socket is configured non-blocking when a timeout is specified.
+     * @throws IOException if there is an I/O error changing the blocking mode
+     */
+    private void maybeConfigureNonBlocking(FileDescriptor fd, int timeout)
+        throws IOException
+    {
+        assert readLock.isHeldByCurrentThread() || writeLock.isHeldByCurrentThread();
+        if (!nonBlocking && (timeout > 0)) {
+            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();
+            assert fd != null;
+            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)
+                stateLock.notifyAll();
+            if (!completed && state >= ST_CLOSING)
+                throw new SocketException("Socket closed");
+        }
+    }
+
+    /**
+     * Reads bytes from the socket into the given buffer.
+     * @throws IOException if the socket is closed or an I/O occurs
+     * @throws SocketTimeoutException if the read timeout elapses
+     */
+    private int read(ByteBuffer dst) throws IOException {
+        readLock.lock();
+        try {
+            int n = 0;
+            FileDescriptor fd = beginRead();
+            try {
+                if (isInputClosed) {
+                    return IOStatus.EOF;
+                }
+                int timeout = this.timeout;
+                maybeConfigureNonBlocking(fd, timeout);
+                n = IOUtil.read(fd, dst, -1, nd);
+                if (statusImpliesRetry(n) && isOpen()) {
+                    if (timeout > 0) {
+                        // read with timeout
+                        assert nonBlocking;
+                        long nanos = NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS);
+                        do {
+                            long startTime = System.nanoTime();
+                            park(Net.POLLIN, nanos);
+                            n = IOUtil.read(fd, dst, -1, nd);
+                            if (n == IOStatus.UNAVAILABLE) {
+                                nanos -= System.nanoTime() - startTime;
+                                if (nanos <= 0)
+                                    throw new SocketTimeoutException("read timeout");
+                            }
+                        } while (n == IOStatus.UNAVAILABLE && isOpen());
+                    } else {
+                        // read, no timeout
+                        do {
+                            park(Net.POLLIN);
+                            n = IOUtil.read(fd, dst, -1, nd);
+                        } while (statusImpliesRetry(n) && isOpen());
+                    }
+                }
+                return n;
+            } finally {
+                endRead(n > 0);
+            }
+        } 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();
+            assert fd != null;
+            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)
+                stateLock.notifyAll();
+            if (!completed && state >= ST_CLOSING)
+                throw new SocketException("Socket closed");
+        }
+    }
+
+    /**
+     * Writes a sequence of bytes to this socket from the given buffer.
+     * @throws IOException if the socket is closed or an I/O occurs
+     */
+    private int write(ByteBuffer dst) throws IOException {
+        writeLock.lock();
+        try {
+            int n = 0;
+            FileDescriptor fd = beginWrite();
+            try {
+                maybeConfigureNonBlocking(fd, 0);
+                n = IOUtil.write(fd, dst, -1, nd);
+                while (statusImpliesRetry(n) && isOpen()) {
+                    park(Net.POLLOUT);
+                    n = IOUtil.write(fd, dst, -1, nd);
+                }
+                return n;
+            } finally {
+                endWrite(n > 0);
+            }
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    /**
+     * Creates the socket.
+     * @param stream {@code true} for a streams socket
+     */
+    @Override
+    protected void create(boolean stream) throws IOException {
+        synchronized (stateLock) {
+            assert state == ST_NEW;
+            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;
+        }
+    }
+
+    /**
+     * For use by ServerSocket to set the state and other fields after a
+     * connection is accepted by a ServerSocket using a custom SocketImpl.
+     * The protected fields defined by SocketImpl should be set.
+     */
+    public void postCustomAccept() throws IOException {
+        synchronized (stateLock) {
+            assert state == ST_NEW;
+            assert fd.valid() && localport != 0 && address != null && port != 0;
+            IOUtil.configureBlocking(fd, true);
+            stream = true;
+            closer = FileDescriptorCloser.create(this);
+            state = ST_CONNECTED;
+        }
+    }
+
+    /**
+     * For use by ServerSocket to copy the state from this connected SocketImpl
+     * to a target SocketImpl. If the target SocketImpl is not a newly created
+     * SocketImpl then it is first closed to release any resources. The target
+     * SocketImpl becomes the owner of the file descriptor, this SocketImpl
+     * is marked as closed and should be discarded.
+     */
+    public void copyTo(SocketImpl si) {
+        if (si instanceof NioSocketImpl) {
+            NioSocketImpl nsi = (NioSocketImpl) si;
+            if (nsi.state != ST_NEW) {
+                try {
+                    nsi.close();
+                } catch (IOException ignore) { }
+            }
+            synchronized (nsi.stateLock) {
+                assert nsi.state == ST_NEW || nsi.state == ST_CLOSED;
+                synchronized (this.stateLock) {
+                    // this SocketImpl should be connected
+                    assert state == ST_CONNECTED && fd.valid()
+                        && localport != 0 && address != null && port != 0;
+
+                    // copy fields
+                    nsi.fd = this.fd;
+                    nsi.stream = this.stream;
+                    nsi.closer = FileDescriptorCloser.create(nsi);
+                    nsi.localport = this.localport;
+                    nsi.address = this.address;
+                    nsi.port = this.port;
+                    nsi.state = ST_CONNECTED;
+
+                    // disable closer to prevent GC'ing of this impl from
+                    // closing the file descriptor
+                    this.closer.disable();
+                    this.state = ST_CLOSED;
+                }
+            }
+        } else {
+            synchronized (this.stateLock) {
+                // this SocketImpl should be connected
+                assert state == ST_CONNECTED && fd.valid()
+                        && localport != 0 && address != null && port != 0;
+
+                // set fields in foreign impl
+                setSocketImplFields(si, fd, localport, address, port);
+
+                // disable closer to prevent GC'ing of this impl from
+                // closing the file descriptor
+                this.closer.disable();
+                this.state = ST_CLOSED;
+            }
+        }
+    }
+
+    /**
+     * 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_CLOSING)
+                throw new SocketException("Socket closed");
+            if (state == ST_CONNECTED)
+                throw new SocketException("Already connected");
+            assert state == ST_UNCONNECTED;
+            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();
+            assert fd != null;
+            return fd;
+        }
+    }
+
+    /**
+     * Marks the end of a connect operation that may have blocked.
+     * @throws SocketException is the socket is closed
+     */
+    private void endConnect(boolean completed) throws IOException {
+        synchronized (stateLock) {
+            readerThread = 0;
+            int state = this.state;
+            if (state == ST_CLOSING)
+                stateLock.notifyAll();
+            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");
+            }
+        }
+    }
+
+    /**
+     * Connect the socket. Closes the socket if connection cannot be established.
+     * @throws IllegalArgumentException if the address is not an InetSocketAddress
+     * @throws UnknownHostException if the InetSocketAddress is not resolved
+     * @throws IOException if the connection cannot be established
+     */
+    private void implConnect(SocketAddress remote, int millis) throws IOException {
+        if (!(remote instanceof InetSocketAddress))
+            throw new IllegalArgumentException("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();
+
+        try {
+            readLock.lock();
+            try {
+                writeLock.lock();
+                try {
+                    boolean connected = false;
+                    FileDescriptor fd = beginConnect(address, port);
+                    try {
+                        maybeConfigureNonBlocking(fd, millis);
+                        int n = Net.connect(fd, address, port);
+                        if (statusImpliesRetry(n) && isOpen()) {
+                            if (millis > 0) {
+                                // connect with timeout
+                                assert nonBlocking;
+                                long nanos = NANOSECONDS.convert(millis, MILLISECONDS);
+                                do {
+                                    long startTime = System.nanoTime();
+                                    park(Net.POLLOUT, nanos);
+                                    n = Net.polConnectlNow(fd);
+                                    if (n == 0) {
+                                        nanos -= System.nanoTime() - startTime;
+                                        if (nanos <= 0)
+                                            throw new SocketTimeoutException("connect timeout");
+                                    }
+                                } while (n == 0 && isOpen());
+                            } else {
+                                // connect, no timeout
+                                do {
+                                    park(Net.POLLOUT);
+                                    n = Net.polConnectlNow(fd);
+                                } while ((n == 0 || n == IOStatus.INTERRUPTED) && isOpen());
+                            }
+                        }
+                        connected = (n > 0) && isOpen();
+                    } finally {
+                        endConnect(connected);
+                    }
+                } finally {
+                    writeLock.unlock();
+                }
+            } finally {
+                readLock.unlock();
+            }
+        } catch (IOException ioe) {
+            close();
+            throw SocketExceptions.of(ioe, isa);
+        }
+    }
+
+    @Override
+    protected void connect(String host, int port) throws IOException {
+        implConnect(new InetSocketAddress(host, port), timeout);
+    }
+
+    @Override
+    protected void connect(InetAddress address, int port) throws IOException {
+        implConnect(new InetSocketAddress(address, port), timeout);
+    }
+
+    @Override
+    protected void connect(SocketAddress address, int timeout) throws IOException {
+        implConnect(address, timeout);
+    }
+
+    @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 address specified to the method 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();
+            assert fd != null;
+            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)
+                stateLock.notifyAll();
+            if (!completed && state >= ST_CLOSING)
+                throw new SocketException("Socket closed");
+        }
+    }
+
+    @Override
+    protected void accept(SocketImpl si) throws IOException {
+        // accept a connection
+        FileDescriptor newfd = new FileDescriptor();
+        InetSocketAddress[] isaa = new InetSocketAddress[1];
+        readLock.lock();
+        try {
+            int n = 0;
+            FileDescriptor fd = beginAccept();
+            try {
+                int timeout = this.timeout;
+                maybeConfigureNonBlocking(fd, timeout);
+                n = ServerSocketChannelImpl.accept0(fd, newfd, isaa);
+                if (statusImpliesRetry(n) && isOpen()) {
+                    if (timeout > 0) {
+                        // accept with timeout
+                        assert nonBlocking;
+                        long nanos = NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS);
+                        do {
+                            long startTime = System.nanoTime();
+                            park(Net.POLLIN, nanos);
+                            n = ServerSocketChannelImpl.accept0(fd, newfd, isaa);
+                            if (n == IOStatus.UNAVAILABLE) {
+                                nanos -= System.nanoTime() - startTime;
+                                if (nanos <= 0)
+                                    throw new SocketTimeoutException("accept timeout");
+                            }
+                        } while (n == IOStatus.UNAVAILABLE && isOpen());
+                    } else {
+                        // accept, no timeout
+                        do {
+                            park(Net.POLLIN);
+                            n = ServerSocketChannelImpl.accept0(fd, newfd, isaa);
+                        } while (statusImpliesRetry(n) && isOpen());
+                    }
+                }
+            } finally {
+                endAccept(n > 0);
+                assert IOStatus.check(n);
+            }
+        } finally {
+            readLock.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
+        InetSocketAddress remoteAddress = isaa[0];
+        if (si instanceof NioSocketImpl) {
+            NioSocketImpl nsi = (NioSocketImpl) si;
+            synchronized (nsi.stateLock) {
+                nsi.fd = newfd;
+                nsi.stream = true;
+                nsi.closer = FileDescriptorCloser.create(nsi);
+                nsi.localport = localAddress.getPort();
+                nsi.address = remoteAddress.getAddress();
+                nsi.port = remoteAddress.getPort();
+                nsi.state = ST_CONNECTED;
+            }
+        } else {
+            // set fields in foreign impl
+            setSocketImplFields(si, newfd,
+                                localAddress.getPort(),
+                                remoteAddress.getAddress(),
+                                remoteAddress.getPort());
+        }
+    }
+
+    @Override
+    protected InputStream getInputStream() {
+        return new InputStream() {
+            private volatile boolean eof;
+            @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 {
+                Objects.checkFromIndexSize(off, len, b.length);
+                if (eof) {
+                    return -1; // legacy SocketInputStream behavior
+                } else if (len == 0) {
+                    return 0;
+                } else {
+                    try {
+                        // read up to MAX_BUFFER_SIZE bytes
+                        int size = Math.min(len, MAX_BUFFER_SIZE);
+                        ByteBuffer dst = ByteBuffer.wrap(b, off, size);
+                        int n = NioSocketImpl.this.read(dst);
+                        if (n == -1)
+                            eof = true;
+                        return n;
+                    } catch (SocketTimeoutException e) {
+                        throw e;
+                    } catch (IOException ioe) {
+                        throw new SocketException(ioe.getMessage());
+                    }
+                }
+            }
+            @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 {
+                Objects.checkFromIndexSize(off, len, b.length);
+                if (len > 0) {
+                    try {
+                        ByteBuffer src = ByteBuffer.wrap(b, off, len);
+                        int end = src.limit();
+                        int pos;
+                        // write up to MAX_BUFFER_SIZE bytes at a time
+                        while ((pos = src.position()) < end) {
+                            int size = Math.min((end - pos), MAX_BUFFER_SIZE);
+                            src.limit(pos + size);
+                            NioSocketImpl.this.write(src);
+                        }
+                        assert src.limit() == end && src.remaining() == 0;
+                    } catch (IOException ioe) {
+                        throw new SocketException(ioe.getMessage());
+                    }
+                }
+            }
+            @Override
+            public void close() throws IOException {
+                NioSocketImpl.this.close();
+            }
+        };
+    }
+
+    @Override
+    protected int available() throws IOException {
+        readLock.lock();
+        try {
+            ensureOpenAndConnected();
+            if (isInputClosed) {
+                return 0;
+            } else {
+                return Net.available(fd);
+            }
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    /**
+     * Closes the socket, signalling and waiting for blocking I/O operations
+     * to complete.
+     */
+    @Override
+    protected void close() throws IOException {
+        boolean interrupted = false;
+
+        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;
+            assert fd != null && closer != null;
+
+            // shutdown output when linger interval not set
+            try {
+                var SO_LINGER = StandardSocketOptions.SO_LINGER;
+                if ((int) Net.getSocketOption(fd, SO_LINGER) != 0) {
+                    Net.shutdown(fd, Net.SHUT_WR);
+                }
+            } catch (IOException ignore) { }
+
+            // interrupt and wait for kernel threads to complete I/O operations
+            long reader = readerThread;
+            long writer = writerThread;
+            if (reader != 0 || writer != 0) {
+                nd.preClose(fd);
+
+                if (reader != 0)
+                    NativeThread.signal(reader);
+                if (writer != 0)
+                    NativeThread.signal(writer);
+
+                // wait for blocking I/O operations to end
+                while (readerThread != 0 || writerThread != 0) {
+                    try {
+                        stateLock.wait();
+                    } catch (InterruptedException e) {
+                        interrupted = true;
+                    }
+                }
+            }
+
+            // close file descriptor
+            try {
+                closer.run();
+            } finally {
+                this.state = ST_CLOSED;
+            }
+        }
+
+        // restore interrupt status
+        if (interrupted)
+            Thread.currentThread().interrupt();
+    }
+
+    @Override
+    protected Set<SocketOption<?>> supportedOptions() {
+        Set<SocketOption<?>> options = new HashSet<>();
+        options.addAll(super.supportedOptions());
+        if (server) {
+            options.addAll(ExtendedSocketOptions.serverSocketOptions());
+        } else {
+            options.addAll(ExtendedSocketOptions.clientSocketOptions());
+        }
+        if (Net.isReusePortAvailable())
+            options.add(StandardSocketOptions.SO_REUSEPORT);
+        return Collections.unmodifiableSet(options);
+    }
+
+    private boolean booleanValue(Object value, String desc) {
+        if (!(value instanceof Boolean))
+            throw new IllegalArgumentException("Bad value for " + desc);
+        return (boolean) value;
+    }
+
+    private int intValue(Object value, String desc) {
+        if (!(value instanceof Integer))
+            throw new IllegalArgumentException("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");
+                    if (stream && Net.isIPv6Available()) {
+                        // IP_TOS is not specified for stream sockets when IPv6 enabled
+                    } else {
+                        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");
+                    Net.setSocketOption(fd, StandardSocketOptions.SO_SNDBUF, i);
+                    break;
+                }
+                case SO_RCVBUF: {
+                    int i = intValue(value, "SO_RCVBUF");
+                    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 UnsupportedOperationException("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 (IOException ioe) {
+                throw new SocketException(ioe.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:
+                    if (stream && Net.isIPv6Available()) {
+                        // IP_TOS is not specified for stream sockets when IPv6 enabled
+                        return 0;
+                    } else {
+                        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 UnsupportedOperationException("SO_REUSEPORT not supported");
+                    return Net.getSocketOption(fd, StandardSocketOptions.SO_REUSEPORT);
+                default:
+                    throw new SocketException("Unknown option " + opt);
+                }
+            } catch (IOException ioe) {
+                throw new SocketException(ioe.getMessage());
+            }
+        }
+    }
+
+    @Override
+    protected <T> void setOption(SocketOption<T> opt, T value) throws IOException {
+        synchronized (stateLock) {
+            ensureOpen();
+            if (supportedOptions().contains(opt)) {
+                ExtendedSocketOptions extended = ExtendedSocketOptions.getInstance();
+                if (extended.isOptionSupported(opt)) {
+                    extended.setOption(fd, opt, value);
+                } else {
+                    super.setOption(opt, value);
+                }
+            } else {
+                throw new UnsupportedOperationException(opt.name());
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T> T getOption(SocketOption<T> opt) throws IOException {
+        synchronized (stateLock) {
+            ensureOpen();
+            if (supportedOptions().contains(opt)) {
+                ExtendedSocketOptions extended = ExtendedSocketOptions.getInstance();
+                if (extended.isOptionSupported(opt)) {
+                    return (T) extended.getOption(fd, opt);
+                } else {
+                    return super.getOption(opt);
+                }
+            } else {
+                throw new UnsupportedOperationException(opt.name());
+            }
+        }
+    }
+
+    @Override
+    protected void shutdownInput() throws IOException {
+        synchronized (stateLock) {
+            ensureOpenAndConnected();
+            if (!isInputClosed) {
+                Net.shutdown(fd, Net.SHUT_RD);
+                long reader = readerThread;
+                if (reader != 0)
+                    NativeThread.signal(reader);
+                isInputClosed = true;
+            }
+        }
+    }
+
+    @Override
+    protected void shutdownOutput() throws IOException {
+        synchronized (stateLock) {
+            ensureOpenAndConnected();
+            if (!isOutputClosed) {
+                Net.shutdown(fd, Net.SHUT_WR);
+                long writer = writerThread;
+                if (writer != 0)
+                    NativeThread.signal(writer);
+                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 {
+                maybeConfigureNonBlocking(fd, 0);
+                do {
+                    n = Net.sendOOB(fd, (byte) data);
+                } while (n == IOStatus.INTERRUPTED && isOpen());
+                if (n == IOStatus.UNAVAILABLE) {
+                    throw new RuntimeException("not implemented yet");
+                }
+            } 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 RuntimeException(ioe);
+                } finally {
+                    if (!stream) {
+                        // decrement 
+                        ResourceManager.afterUdpClose();
+                    }
+                }
+            }
+        }
+
+        boolean disable() {
+            return CLOSED.compareAndSet(this, false, true);
+        }
+    }
+
+    /**
+     * Returns true if the error code is UNAVAILABLE or INTERRUPTED, the
+     * error codes to indicate that an I/O operation should be retried.
+     */
+    private static boolean statusImpliesRetry(int n) {
+        return n == IOStatus.UNAVAILABLE || n == IOStatus.INTERRUPTED;
+    }
+
+    /**
+     * Returns the socket protocol family
+     */
+    private static ProtocolFamily family() {
+        if (Net.isIPv6Available()) {
+            return StandardProtocolFamily.INET6;
+        } else {
+            return StandardProtocolFamily.INET;
+        }
+    }
+
+    /**
+     * Returns the native file descriptor
+     */
+    private static int fdVal(FileDescriptor fd) {
+        int fdVal = SharedSecrets.getJavaIOFileDescriptorAccess().get(fd);
+        assert fdVal == IOUtil.fdVal(fd);
+        return fdVal;
+    }
+
+    /**
+     * Sets the SocketImpl fields to the given values.
+     */
+    private static void setSocketImplFields(SocketImpl si,
+                                            FileDescriptor fd,
+                                            int localport,
+                                            InetAddress address,
+                                            int port)
+    {
+        PrivilegedExceptionAction<Void> pa = () -> {
+            setSocketImplField(si, "fd", fd);
+            setSocketImplField(si, "localport", localport);
+            setSocketImplField(si, "address", address);
+            setSocketImplField(si, "port", port);
+            return null;
+        };
+        try {
+            AccessController.doPrivileged(pa);
+        } catch (PrivilegedActionException pae) {
+            throw new InternalError(pae);
+        }
+    }
+
+    private static void setSocketImplField(SocketImpl si, String name, Object value)
+        throws Exception
+    {
+        Field field = SocketImpl.class.getDeclaredField(name);
+        field.setAccessible(true);
+        field.set(si, value);
+    }
+}
--- a/src/java.base/share/classes/sun/nio/ch/ServerSocketChannelImpl.java	Wed Jan 23 19:56:28 2019 +0100
+++ b/src/java.base/share/classes/sun/nio/ch/ServerSocketChannelImpl.java	Wed Jan 23 19:30:59 2019 +0000
@@ -529,7 +529,7 @@
     // Returns 1 on success, or IOStatus.UNAVAILABLE (if non-blocking and no
     // connections are pending) or IOStatus.INTERRUPTED.
     //
-    private native int accept0(FileDescriptor ssfd,
+    static native int accept0(FileDescriptor ssfd,
                                FileDescriptor newfd,
                                InetSocketAddress[] isaa)
         throws IOException;
--- a/test/jdk/ProblemList.txt	Wed Jan 23 19:56:28 2019 +0100
+++ b/test/jdk/ProblemList.txt	Wed Jan 23 19:30:59 2019 +0000
@@ -565,6 +565,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
--- a/test/jdk/java/net/Socket/asyncClose/BrokenPipe.java	Wed Jan 23 19:56:28 2019 +0100
+++ b/test/jdk/java/net/Socket/asyncClose/BrokenPipe.java	Wed Jan 23 19:30:59 2019 +0000
@@ -69,7 +69,7 @@
              * replace this by catching a more specific exception.
              */
             String text = ioe.getMessage();
-            if (text.toLowerCase().indexOf("closed") >= 0) {
+            if (text.toLowerCase().indexOf("Socket closed") >= 0) {
                 throw ioe;
             }
         } finally {