Merge niosocketimpl-branch
authoralanb
Sat, 13 Apr 2019 07:23:18 +0100
branchniosocketimpl-branch
changeset 57322 4744fdcf458c
parent 57321 eef9324f94cc (diff)
parent 54527 96d290a7e94f (current diff)
child 57336 766140c67efa
Merge
src/jdk.accessibility/windows/native/common/AccessBridgeStatusWindow.RC
test/hotspot/jtreg/runtime/containers/cgroup/PlainRead.java
test/hotspot/jtreg/runtime/containers/docker/AttemptOOM.java
test/hotspot/jtreg/runtime/containers/docker/CheckContainerized.java
test/hotspot/jtreg/runtime/containers/docker/DockerBasicTest.java
test/hotspot/jtreg/runtime/containers/docker/HelloDocker.java
test/hotspot/jtreg/runtime/containers/docker/JfrReporter.java
test/hotspot/jtreg/runtime/containers/docker/PrintContainerInfo.java
test/hotspot/jtreg/runtime/containers/docker/TEST.properties
test/hotspot/jtreg/runtime/containers/docker/TestCPUAwareness.java
test/hotspot/jtreg/runtime/containers/docker/TestCPUSets.java
test/hotspot/jtreg/runtime/containers/docker/TestJFREvents.java
test/hotspot/jtreg/runtime/containers/docker/TestMemoryAwareness.java
test/hotspot/jtreg/runtime/containers/docker/TestMisc.java
test/jdk/ProblemList.txt
test/jdk/sun/security/tools/jarsigner/AlgOptions.sh
test/jdk/sun/security/tools/jarsigner/PercentSign.sh
test/jdk/sun/security/tools/jarsigner/certpolicy.sh
test/jdk/sun/security/tools/jarsigner/checkusage.sh
test/jdk/sun/security/tools/jarsigner/collator.sh
test/jdk/sun/security/tools/jarsigner/concise_jarsigner.sh
test/jdk/sun/security/tools/jarsigner/crl.sh
test/jdk/sun/security/tools/jarsigner/default_options.sh
test/jdk/sun/security/tools/jarsigner/diffend.sh
test/jdk/sun/security/tools/jarsigner/ec.sh
test/jdk/sun/security/tools/jarsigner/emptymanifest.sh
test/jdk/sun/security/tools/jarsigner/jvindex.sh
test/jdk/sun/security/tools/jarsigner/nameclash.sh
test/jdk/sun/security/tools/jarsigner/newsize7.sh
test/jdk/sun/security/tools/jarsigner/oldsig.sh
test/jdk/sun/security/tools/jarsigner/onlymanifest.sh
test/jdk/sun/security/tools/jarsigner/passtype.sh
test/jdk/sun/security/tools/jarsigner/samename.sh
test/jdk/sun/security/tools/jarsigner/weaksize.sh
test/jdk/sun/security/tools/keytool/CloneKeyAskPassword.sh
test/jdk/sun/security/tools/keytool/NoExtNPE.sh
test/jdk/sun/security/tools/keytool/SecretKeyKS.sh
test/jdk/sun/security/tools/keytool/StandardAlgName.sh
test/jdk/sun/security/tools/keytool/StorePasswordsByShell.sh
test/jdk/sun/security/tools/keytool/default_options.sh
test/jdk/sun/security/tools/keytool/emptysubject.sh
test/jdk/sun/security/tools/keytool/file-in-help.sh
test/jdk/sun/security/tools/keytool/i18n.sh
test/jdk/sun/security/tools/keytool/importreadall.sh
test/jdk/sun/security/tools/keytool/keyalg.sh
test/jdk/sun/security/tools/keytool/newhelp.sh
test/jdk/sun/security/tools/keytool/resource.sh
test/jdk/sun/security/tools/keytool/selfissued.sh
test/jdk/sun/security/tools/keytool/trystore.sh
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/patches/NativeThread.patch	Sat Apr 13 07:23:18 2019 +0100
@@ -0,0 +1,138 @@
+diff -r 7d5a4a48e876 src/java.base/share/classes/java/lang/System.java
+--- a/src/java.base/share/classes/java/lang/System.java	Wed Mar 27 14:40:36 2019 -0700
++++ b/src/java.base/share/classes/java/lang/System.java	Wed Apr 03 09:46:50 2019 +0100
+@@ -2172,6 +2172,12 @@
+             public void blockedOn(Interruptible b) {
+                 Thread.blockedOn(b);
+             }
++            public void setNativeTid(long tid) {
++                Thread.setNativeTid(tid);
++            }
++            public long nativeTid() {
++                return Thread.nativeTid();
++            }
+             public void registerShutdownHook(int slot, boolean registerShutdownInProgress, Runnable hook) {
+                 Shutdown.add(slot, registerShutdownInProgress, hook);
+             }
+diff -r 7d5a4a48e876 src/java.base/share/classes/java/lang/Thread.java
+--- a/src/java.base/share/classes/java/lang/Thread.java	Wed Mar 27 14:40:36 2019 -0700
++++ b/src/java.base/share/classes/java/lang/Thread.java	Wed Apr 03 09:46:50 2019 +0100
+@@ -241,6 +241,18 @@
+     }
+ 
+     /**
++     * Native thread id, cached here for use for threads are blocked in I/O
++     * operations.
++     */
++    private long nativeTid;
++    static void setNativeTid(long tid) {
++        Thread.currentThread().nativeTid = tid;
++    }
++    static long nativeTid() {
++        return Thread.currentThread().nativeTid;
++    }
++
++    /**
+      * The minimum priority that a thread can have.
+      */
+     public static final int MIN_PRIORITY = 1;
+diff -r 7d5a4a48e876 src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java
+--- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java	Wed Mar 27 14:40:36 2019 -0700
++++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java	Wed Apr 03 09:46:50 2019 +0100
+@@ -107,6 +107,16 @@
+     void blockedOn(Interruptible b);
+ 
+     /**
++     * Set the current thread's native ID
++     */
++    void setNativeTid(long tid);
++
++    /**
++     * Returns the current thread's native ID
++     */
++    long nativeTid();
++
++    /**
+      * Registers a shutdown hook.
+      *
+      * It is expected that this method with registerShutdownInProgress=true
+diff -r 7d5a4a48e876 src/java.base/unix/classes/sun/nio/ch/NativeThread.java
+--- a/src/java.base/unix/classes/sun/nio/ch/NativeThread.java	Wed Mar 27 14:40:36 2019 -0700
++++ b/src/java.base/unix/classes/sun/nio/ch/NativeThread.java	Wed Apr 03 09:46:50 2019 +0100
+@@ -25,7 +25,6 @@
+ 
+ package sun.nio.ch;
+ 
+-
+ // Signalling operations on native threads
+ //
+ // On some operating systems (e.g., Linux), closing a channel while another
+@@ -33,23 +32,35 @@
+ // thread to be released.  This class provides access to the native threads
+ // upon which Java threads are built, and defines a simple signal mechanism
+ // that can be used to release a native thread from a blocking I/O operation.
+-// On systems that do not require this type of signalling, the current() method
+-// always returns -1 and the signal(long) method has no effect.
+ 
++import jdk.internal.access.JavaLangAccess;
++import jdk.internal.access.SharedSecrets;
+ 
+ public class NativeThread {
++    private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
+ 
+-    // Returns an opaque token representing the native thread underlying the
+-    // invoking Java thread.  On systems that do not require signalling, this
+-    // method always returns -1.
+-    //
+-    public static native long current();
++    /**
++     * Returns the current thread's ID.
++     */
++    public static long current() {
++        long tid = JLA.nativeTid();
++        if (tid == 0) {
++            tid = current0();
++            JLA.setNativeTid(tid);
++        }
++        return tid;
++    }
+ 
+-    // Signals the given native thread so as to release it from a blocking I/O
+-    // operation.  On systems that do not require signalling, this method has
+-    // no effect.
+-    //
+-    public static native void signal(long nt);
++    /**
++     * Signals the given thread.
++     */
++    public static void signal(long tid) {
++        signal0(tid);
++    }
++
++    private static native long current0();
++
++    private static native void signal0(long tid);
+ 
+     private static native void init();
+ 
+diff -r 7d5a4a48e876 src/java.base/unix/native/libnio/ch/NativeThread.c
+--- a/src/java.base/unix/native/libnio/ch/NativeThread.c	Wed Mar 27 14:40:36 2019 -0700
++++ b/src/java.base/unix/native/libnio/ch/NativeThread.c	Wed Apr 03 09:46:50 2019 +0100
+@@ -77,7 +77,7 @@
+ }
+ 
+ JNIEXPORT jlong JNICALL
+-Java_sun_nio_ch_NativeThread_current(JNIEnv *env, jclass cl)
++Java_sun_nio_ch_NativeThread_current0(JNIEnv *env, jclass cl)
+ {
+ #ifdef __solaris__
+     return (jlong)thr_self();
+@@ -87,7 +87,7 @@
+ }
+ 
+ JNIEXPORT void JNICALL
+-Java_sun_nio_ch_NativeThread_signal(JNIEnv *env, jclass cl, jlong thread)
++Java_sun_nio_ch_NativeThread_signal0(JNIEnv *env, jclass cl, jlong thread)
+ {
+     int ret;
+ #ifdef __solaris__
--- a/src/java.base/share/classes/java/net/ServerSocket.java	Fri Apr 12 14:13:31 2019 -0700
+++ b/src/java.base/share/classes/java/net/ServerSocket.java	Sat Apr 13 07:23:18 2019 +0100
@@ -34,6 +34,7 @@
 import java.security.PrivilegedExceptionAction;
 import java.util.Set;
 import java.util.Collections;
+import java.util.concurrent.locks.ReentrantLock;
 
 import jdk.internal.access.JavaNetSocketAccess;
 import jdk.internal.access.SharedSecrets;
@@ -64,7 +65,7 @@
     private boolean created = false;
     private boolean bound = false;
     private boolean closed = false;
-    private Object closeLock = new Object();
+    private final ReentrantLock closeLock = new ReentrantLock();
 
     /**
      * The implementation of this Socket.
@@ -686,12 +687,19 @@
      * @spec JSR-51
      */
     public void close() throws IOException {
-        synchronized(closeLock) {
-            if (isClosed())
-                return;
-            if (created)
-                impl.close();
-            closed = true;
+        closeLock.lock();
+        try {
+            if (!closed) {
+                try {
+                    SocketImpl impl = this.impl;
+                    if (impl != null)
+                        impl.close();
+                } finally {
+                    closed = true;
+                }
+            }
+        } finally {
+            closeLock.unlock();
         }
     }
 
@@ -733,8 +741,11 @@
      * @since 1.4
      */
     public boolean isClosed() {
-        synchronized(closeLock) {
+        closeLock.lock();
+        try {
             return closed;
+        } finally {
+            closeLock.unlock();
         }
     }
 
--- a/src/java.base/share/classes/java/net/Socket.java	Fri Apr 12 14:13:31 2019 -0700
+++ b/src/java.base/share/classes/java/net/Socket.java	Sat Apr 13 07:23:18 2019 +0100
@@ -35,6 +35,7 @@
 import java.security.PrivilegedAction;
 import java.util.Set;
 import java.util.Collections;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * This class implements client sockets (also called just
@@ -62,7 +63,7 @@
     private boolean bound = false;
     private boolean connected = false;
     private boolean closed = false;
-    private Object closeLock = new Object();
+    private final ReentrantLock closeLock = new ReentrantLock();
     private boolean shutIn = false;
     private boolean shutOut = false;
 
@@ -1468,9 +1469,8 @@
         try {
             getImpl().setOption(SocketOptions.IP_TOS, tc);
         } catch (SocketException se) {
-            // not supported if socket already connected
-            // Solaris returns error in such cases
-            if(!isConnected())
+            // may not be supported to change when socket is connected
+            if (!isConnected())
                 throw se;
         }
     }
@@ -1574,13 +1574,20 @@
      * @spec JSR-51
      * @see #isClosed
      */
-    public synchronized void close() throws IOException {
-        synchronized(closeLock) {
-            if (isClosed())
-                return;
-            if (created)
-                impl.close();
-            closed = true;
+    public void close() throws IOException {
+        closeLock.lock();
+        try {
+            if (!closed) {
+                try {
+                    SocketImpl impl = this.impl;
+                    if (impl != null)
+                        impl.close();
+                } finally {
+                    closed = true;
+                }
+            }
+        } finally {
+            closeLock.unlock();
         }
     }
 
@@ -1701,8 +1708,11 @@
      * @see #close
      */
     public boolean isClosed() {
-        synchronized(closeLock) {
+        closeLock.lock();
+        try {
             return closed;
+        } finally {
+            closeLock.unlock();
         }
     }
 
@@ -1848,7 +1858,14 @@
      * @since 9
      */
     public <T> Socket setOption(SocketOption<T> name, T value) throws IOException {
-        getImpl().setOption(name, value);
+        try {
+            getImpl().setOption(name, value);
+        } catch (SocketException se) {
+            // may not be supported to change when socket is connected
+            if (name != StandardSocketOptions.IP_TOS || !isConnected()) {
+                throw se;
+            }
+        }
         return this;
     }
 
--- a/src/java.base/share/classes/java/net/SocketImpl.java	Fri Apr 12 14:13:31 2019 -0700
+++ b/src/java.base/share/classes/java/net/SocketImpl.java	Sat Apr 13 07:23:18 2019 +0100
@@ -25,33 +25,57 @@
 
 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.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 The JDK historically used a {@code SocketImpl} implementation named
+ * <em>PlainSocketImpl</em>. This has been replaced by a newer implementation
+ * but the JDK continues to ship with the older implementation to allow code that
+ * depends on unspecified behavior, that differs between the old and new
+ * implementations, to continue to run. The old implementation will be used if
+ * the Java virtual machine is started with the system property {@systemProperty
+ * jdk.net.usePlainSocketImpl} set on the command line. The value of the system
+ * 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 use the old implementation.
  *
  * @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();
+        if (USE_PLAINSOCKETIMPL) {
+            return (S) new PlainSocketImpl();
+        } 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	Sat Apr 13 07:23:18 2019 +0100
@@ -0,0 +1,1324 @@
+/*
+ * 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.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.Condition;
+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 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.
+ */
+
+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 ReentrantLock stateLock = new ReentrantLock();
+    private final Condition stateCondition = stateLock.newCondition();
+    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, protected by stateLock
+    private boolean isReuseAddress;
+
+    // cached value of IPV6_TCLASS or IP_TOS socket option, protected by stateLock
+    private int trafficClass;
+
+    // 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 = 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(FileDescriptor fd, int event) throws IOException {
+        park(fd, event, 0);
+    }
+
+    /**
+     * Configures the socket to be non-blocking (if not already non-blocking)
+     * @throws IOException if there is an I/O error changing the blocking mode
+     */
+    private void configureNonBlocking(FileDescriptor fd) throws IOException {
+        if (!nonBlocking) {
+            assert readLock.isHeldByCurrentThread() || writeLock.isHeldByCurrentThread();
+            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 {
+        stateLock.lock();
+        try {
+            ensureOpenAndConnected();
+            readerThread = NativeThread.current();
+            return fd;
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    /**
+     * 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 {
+        stateLock.lock();
+        try {
+            readerThread = 0;
+            int state = this.state;
+            if (state == ST_CLOSING)
+                stateCondition.signalAll();
+            if (!completed && state >= ST_CLOSING)
+                throw new SocketException("Socket closed");
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    /**
+     * 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
+    {
+        assert nonBlocking;
+        long remainingNanos = nanos;
+        long startNanos = System.nanoTime();
+        int n;
+        do {
+            park(fd, Net.POLLIN, remainingNanos);
+            n = tryRead(fd, b, off, len);
+            if (n == IOStatus.UNAVAILABLE) {
+                remainingNanos = nanos - (System.nanoTime() - startNanos);
+                if (remainingNanos <= 0) {
+                    throw new SocketTimeoutException("Read timed out");
+                }
+            }
+        } while (n == IOStatus.UNAVAILABLE && isOpen());
+        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)
+                configureNonBlocking(fd);
+            n = tryRead(fd, b, off, len);
+            if (IOStatus.okayToRetry(n) && isOpen()) {
+                if (timeout > 0) {
+                    // read with timeout
+                    long nanos = NANOSECONDS.convert(timeout, MILLISECONDS);
+                    n = timedRead(fd, b, off, len, nanos);
+                } else {
+                    // read, no timeout
+                    do {
+                        park(fd, Net.POLLIN);
+                        n = tryRead(fd, b, off, len);
+                    } while (IOStatus.okayToRetry(n) && isOpen());
+                }
+            }
+            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 {
+        stateLock.lock();
+        try {
+            ensureOpenAndConnected();
+            writerThread = NativeThread.current();
+            return fd;
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    /**
+     * 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 {
+        stateLock.lock();
+        try {
+            writerThread = 0;
+            int state = this.state;
+            if (state == ST_CLOSING)
+                stateCondition.signalAll();
+            if (!completed && state >= ST_CLOSING)
+                throw new SocketException("Socket closed");
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    /**
+     * 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 an 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 an 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 {
+        stateLock.lock();
+        try {
+            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;
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    /**
+     * 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
+    {
+        stateLock.lock();
+        try {
+            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;
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    /**
+     * 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 {
+        stateLock.lock();
+        try {
+            readerThread = 0;
+            int state = this.state;
+            if (state == ST_CLOSING)
+                stateCondition.signalAll();
+            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");
+            }
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    /**
+     * Waits for a connection attempt to finish with a timeout
+     * @throws SocketTimeoutException if the connect timeout elapses
+     */
+    private void timedFinishConnect(FileDescriptor fd, long nanos) throws IOException {
+        long remainingNanos = nanos;
+        long startNanos = System.nanoTime();
+        boolean polled;
+        do {
+            park(fd, Net.POLLOUT, remainingNanos);
+            polled = Net.pollConnectNow(fd);
+            if (!polled) {
+                remainingNanos = nanos - (System.nanoTime() - startNanos);
+                if (remainingNanos <= 0) {
+                    throw new SocketTimeoutException("Connect timed out");
+                }
+            }
+        } while (!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 {
+                    if (millis > 0)
+                        configureNonBlocking(fd);
+                    int n = Net.connect(fd, address, port);
+                    if (isOpen()) {
+                        if (n > 0) {
+                            // connection established
+                            connected = true;
+                        } else if (IOStatus.okayToRetry(n)) {
+                            // not established or interrupted
+                            if (millis > 0) {
+                                // finish connect with timeout
+                                long nanos = NANOSECONDS.convert(millis, MILLISECONDS);
+                                timedFinishConnect(fd, nanos);
+                            } else {
+                                // finish connect, no timeout
+                                boolean polled;
+                                do {
+                                    park(fd, Net.POLLOUT);
+                                    polled = Net.pollConnectNow(fd);
+                                } while (!polled && isOpen());
+                            }
+                            connected = isOpen();
+                        }
+                    }
+                } 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 {
+        stateLock.lock();
+        try {
+            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();
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    @Override
+    protected void listen(int backlog) throws IOException {
+        stateLock.lock();
+        try {
+            ensureOpen();
+            if (localport == 0)
+                throw new SocketException("Not bound");
+            Net.listen(fd, backlog < 1 ? 50 : backlog);
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    /**
+     * Marks the beginning of an accept operation that might block.
+     * @throws SocketException if the socket is closed
+     */
+    private FileDescriptor beginAccept() throws SocketException {
+        stateLock.lock();
+        try {
+            ensureOpen();
+            if (!stream)
+                throw new SocketException("Not a stream socket");
+            if (localport == 0)
+                throw new SocketException("Not bound");
+            readerThread = NativeThread.current();
+            return fd;
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    /**
+     * 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 {
+        stateLock.lock(); 
+        try {
+            int state = this.state;
+            readerThread = 0;
+            if (state == ST_CLOSING)
+                stateCondition.signalAll();
+            if (!completed && state >= ST_CLOSING)
+                throw new SocketException("Socket closed");
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    /**
+     * 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
+    {
+        assert nonBlocking;
+        long remainingNanos = nanos;
+        long startNanos = System.nanoTime();
+        int n;
+        do {
+            park(fd, Net.POLLIN, remainingNanos);
+            n = Net.accept(fd, newfd, isaa);
+            if (n == IOStatus.UNAVAILABLE) {
+                remainingNanos = nanos - (System.nanoTime() - startNanos);
+                if (remainingNanos <= 0) {
+                    throw new SocketTimeoutException("Accept timed out");
+                }
+            }
+        } while (n == IOStatus.UNAVAILABLE && isOpen());
+        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)
+                    configureNonBlocking(fd);
+                n = Net.accept(fd, newfd, isaa);
+                if (IOStatus.okayToRetry(n) && isOpen()) {
+                    if (remainingNanos > 0) {
+                        // accept with timeout
+                        n = timedAccept(fd, newfd, isaa, remainingNanos);
+                    } else {
+                        // accept, no timeout
+                        do {
+                            park(fd, Net.POLLIN);
+                            n = Net.accept(fd, newfd, isaa);
+                        } while (IOStatus.okayToRetry(n) && isOpen());
+                    }
+                }
+            } 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
+        nsi.stateLock.lock();
+        try {
+            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;
+        } finally {
+            nsi.stateLock.unlock();
+        }
+    }
+
+    @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 {
+        stateLock.lock();
+        try {
+            ensureOpenAndConnected();
+            if (isInputClosed) {
+                return 0;
+            } else {
+                return Net.available(fd);
+            }
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    /**
+     * Closes the socket, signalling and waiting for blocking I/O operations
+     * to complete.
+     */
+    @Override
+    protected void close() throws IOException {
+        boolean interrupted = false;
+
+        stateLock.lock();
+        try {
+            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 {
+                        stateCondition.await();
+                    } catch (InterruptedException e) {
+                        interrupted = true;
+                    }
+                }
+            }
+
+            // close file descriptor
+            try {
+                closer.run();
+            } finally {
+                this.state = ST_CLOSED;
+            }
+        } finally {
+            stateLock.unlock();
+        }
+
+        // restore interrupt status
+        if (interrupted)
+            Thread.currentThread().interrupt();
+    }
+
+    // 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");
+        stateLock.lock();
+        try {
+            ensureOpen();
+            if (opt == StandardSocketOptions.IP_TOS) {
+                // maps to IP_TOS or IPV6_TCLASS
+                int i = (int) value;
+                Net.setSocketOption(fd, family(), opt, i);
+                trafficClass = i;
+            } 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);
+            }
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T> T getOption(SocketOption<T> opt) throws IOException {
+        if (!supportedOptions().contains(opt))
+            throw new UnsupportedOperationException("'" + opt + "' not supported");
+        stateLock.lock();
+        try {
+            ensureOpen();
+            if (opt == StandardSocketOptions.IP_TOS) {
+                return (T) Integer.valueOf(trafficClass);
+            } 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);
+            }
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    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 {
+        stateLock.lock(); 
+        try {
+            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);
+                    trafficClass = 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());
+            }
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    @Override
+    public Object getOption(int opt) throws SocketException {
+        stateLock.lock(); 
+        try {
+            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 trafficClass;
+                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());
+            }
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    @Override
+    protected void shutdownInput() throws IOException {
+        stateLock.lock();
+        try {
+            ensureOpenAndConnected();
+            if (!isInputClosed) {
+                Net.shutdown(fd, Net.SHUT_RD);
+                isInputClosed = true;
+            }
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    @Override
+    protected void shutdownOutput() throws IOException {
+        stateLock.lock();
+        try {
+            ensureOpenAndConnected();
+            if (!isOutputClosed) {
+                Net.shutdown(fd, Net.SHUT_WR);
+                isOutputClosed = true;
+            }
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    @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 impl.stateLock.isHeldByCurrentThread();
+            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();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 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/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java	Fri Apr 12 14:13:31 2019 -0700
+++ b/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java	Sat Apr 13 07:23:18 2019 +0100
@@ -792,13 +792,15 @@
                     boolean connected = false;
                     try {
                         beginFinishConnect(blocking);
+                        boolean polled;
                         if (blocking) {
                             do {
-                                connected = Net.pollConnect(fd, -1);
-                            } while (!connected && isOpen());
+                                polled = Net.pollConnect(fd, -1);
+                            } while (!polled && isOpen());
                         } else {
-                            connected = Net.pollConnect(fd, 0);
+                            polled = Net.pollConnect(fd, 0);
                         }
+                        connected = polled && isOpen();
                     } finally {
                         endFinishConnect(blocking, connected);
                     }
--- a/test/jdk/ProblemList.txt	Fri Apr 12 14:13:31 2019 -0700
+++ b/test/jdk/ProblemList.txt	Sat Apr 13 07:23:18 2019 +0100
@@ -594,6 +594,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
@@ -907,3 +910,5 @@
 
 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/java/net/ServerSocket/AcceptCauseFileDescriptorLeak.java	Fri Apr 12 14:13:31 2019 -0700
+++ b/test/jdk/java/net/ServerSocket/AcceptCauseFileDescriptorLeak.java	Sat Apr 13 07:23:18 2019 +0100
@@ -38,6 +38,7 @@
  *        jdk.test.lib.process.*
  *        AcceptCauseFileDescriptorLeak
  * @run main/othervm AcceptCauseFileDescriptorLeak root
+ * @run main/othervm -Djdk.net.usePlainSocketImpl=true AcceptCauseFileDescriptorLeak root
  */
 
 import java.io.IOException;
--- a/test/jdk/java/net/ServerSocket/UnreferencedSockets.java	Fri Apr 12 14:13:31 2019 -0700
+++ b/test/jdk/java/net/ServerSocket/UnreferencedSockets.java	Sat Apr 13 07:23:18 2019 +0100
@@ -26,6 +26,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	Fri Apr 12 14:13:31 2019 -0700
+++ b/test/jdk/java/net/Socket/ConnectionReset.java	Sat Apr 13 07:23:18 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	Fri Apr 12 14:13:31 2019 -0700
+++ b/test/jdk/java/net/Socket/Timeouts.java	Sat Apr 13 07:23:18 2019 +0100
@@ -25,7 +25,7 @@
  * @test
  * @library /test/lib
  * @build jdk.test.lib.Utils
- * @run testng Timeouts
+ * @run testng/timeout=180 Timeouts
  * @summary Test Socket timeouts
  */
 
@@ -40,6 +40,9 @@
 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;
 
@@ -128,10 +131,14 @@
     public void testTimedRead3() throws IOException {
         withConnection((s1, s2) -> {
             s2.setSoTimeout(2000);
+            long startMillis = millisTime();
             try {
                 s2.getInputStream().read();
                 assertTrue(false);
-            } catch (SocketTimeoutException expected) { }
+            } catch (SocketTimeoutException expected) {
+                int timeout = s2.getSoTimeout();
+                checkDuration(startMillis, timeout-100, timeout+2000);
+            }
         });
     }
 
@@ -309,11 +316,15 @@
     public void testTimedAccept3() throws IOException {
         try (ServerSocket ss = new ServerSocket(0)) {
             ss.setSoTimeout(2000);
+            long startMillis = millisTime();
             try {
                 Socket s = ss.accept();
                 s.close();
                 assertTrue(false);
-            } catch (SocketTimeoutException expected) { }
+            } catch (SocketTimeoutException expected) {
+                int timeout = ss.getSoTimeout();
+                checkDuration(startMillis, timeout-100, timeout+2000);
+            }
         }
     }
 
@@ -383,11 +394,132 @@
     public void testTimedAccept7() throws IOException {
         try (ServerSocket ss = new ServerSocket(0)) {
             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) { }
+            } 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 = new ServerSocket(0)) {
+            ss.setSoTimeout(2000);
+            Thread.currentThread().interrupt();
+            long startMillis = millisTime();
+            try {
+                Socket s = ss.accept();
+                s.close();
+                assertTrue(false);
+            } 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 = new ServerSocket(0)) {
+            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();
+                assertTrue(false);
+            } 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 = new ServerSocket(0)) {
+            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 = new ServerSocket(0)) {
+            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();
         }
     }
 
@@ -446,6 +578,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 +621,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	Fri Apr 12 14:13:31 2019 -0700
+++ b/test/jdk/java/net/Socket/UdpSocket.java	Sat Apr 13 07:23:18 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,70 @@
                 // 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");
+                assertTrue(Arrays.equals(array1, 0, n, array2, 0, 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 static 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	Fri Apr 12 14:13:31 2019 -0700
+++ b/test/jdk/java/net/Socket/asyncClose/AsyncClose.java	Sat Apr 13 07:23:18 2019 +0100
@@ -33,6 +33,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	Sat Apr 13 07:23:18 2019 +0100
@@ -0,0 +1,487 @@
+/*
+ * 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
+ * @compile/module=java.base java/net/PlatformSocketImpl.java
+ * @run testng/othervm BadUsages
+ * @summary Test the platform SocketImpl in illegal state/bad input scenarios
+ */
+
+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(0);
+             var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            String host = ss.getInetAddress().getHostAddress();
+            int port = ss.getLocalPort();
+            impl.connect(host, port);
+            expectThrows(IOException.class, () -> impl.connect(host, 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(0);
+             var impl = new PlatformSocketImpl(false)) {
+            impl.create(true);
+            impl.connect(ss.getLocalSocketAddress(), 0);
+            var loopback = InetAddress.getLoopbackAddress();
+            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/java.base/java/net/PlatformSocketImpl.java	Sat Apr 13 07:23:18 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/ipv6tests/TcpTest.java	Fri Apr 12 14:13:31 2019 -0700
+++ b/test/jdk/java/net/ipv6tests/TcpTest.java	Sat Apr 13 07:23:18 2019 +0100
@@ -29,7 +29,8 @@
  * @library /test/lib
  * @build jdk.test.lib.NetworkConfiguration
  *        jdk.test.lib.Platform
- * @run main TcpTest -d
+ * @run main TcpTest
+ * @run main/othervm -Djdk.net.usePlainSocketImpl TcpTest
  */
 
 import java.net.*;
--- a/test/micro/org/openjdk/bench/java/net/SocketReadWrite.java	Fri Apr 12 14:13:31 2019 -0700
+++ b/test/micro/org/openjdk/bench/java/net/SocketReadWrite.java	Sat Apr 13 07:23:18 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 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
@@ -22,86 +22,206 @@
  */
 package org.openjdk.bench.java.net;
 
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
-import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import org.openjdk.jmh.annotations.BenchmarkMode;
-import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.annotations.OutputTimeUnit;
-import org.openjdk.jmh.annotations.Scope;
-import org.openjdk.jmh.annotations.Setup;
-import org.openjdk.jmh.annotations.State;
-import org.openjdk.jmh.annotations.TearDown;
+/**
+ * Tests socket read/write.
+ */
 
-/**
- * Tests the overheads of I/O API.
- * This test is known to depend heavily on network conditions and paltform.
- */
 @BenchmarkMode(Mode.Throughput)
-@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@OutputTimeUnit(TimeUnit.SECONDS)
 @State(Scope.Thread)
 public class SocketReadWrite {
 
-    private OutputStream os;
-    private InputStream is;
-    private ServerSocket ss;
-    private Socket s1, s2;
-    private ReadThread rt;
+    static final InetAddress address = InetAddress.getLoopbackAddress();
+    public static final int TIMEOUT = 10000;
+
+    static class EchoServer implements Runnable {
+        final ServerSocket ss;
+        final int port;
+        final CountDownLatch startedLatch;
+        final int size;
+        final boolean timeout;
+        List<ServerThread> threads = new ArrayList<>();
+        volatile boolean isDone = false;
+
+        public EchoServer(CountDownLatch await, int size, boolean timeout) throws IOException {
+            this.size = size;
+            this.timeout = timeout;
+            ss = new ServerSocket(0);
+            port = ss.getLocalPort();
+            this.startedLatch = await;
+        }
+
+        @Override
+        public void run() {
+            startedLatch.countDown();
+            while (!isDone) {
+                try {
+                    Socket s = ss.accept();
+                    s.setTcpNoDelay(true);
+                    if (timeout) {
+                        s.setSoTimeout(TIMEOUT);
+                    }
+                    ServerThread st = new ServerThread(s, size);
+                    threads.add(st);
+                    new Thread(st).start();
+                } catch (IOException e) {
+                    if (!isDone) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+
+        synchronized void close() throws IOException {
+            if (!isDone) {
+                isDone = true;
+                ss.close();
+                for (ServerThread st : threads) {
+                    st.close();
+                }
+            }
+        }
+
+        static EchoServer instance = null;
+
+        static synchronized EchoServer startServer(int size, boolean timeout) throws IOException {
+            if (instance == null) {
+                CountDownLatch started = new CountDownLatch(1);
+                EchoServer s = new EchoServer(started, size, timeout);
+                new Thread(s).start();
+                try {
+                    started.await(); // wait until server thread started
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                instance = s;
+            }
+            return instance;
+        }
+
+        static class ServerThread implements Runnable {
+
+            final Socket s;
+            final InputStream in;
+            final OutputStream out;
+            final int size;
+            volatile boolean isDone = false;
+
+            ServerThread(Socket s, int size) throws IOException {
+                this.s = s;
+                this.size = size;
+                in = s.getInputStream();
+                out = s.getOutputStream();
+            }
+
+            @Override
+            public void run() {
+                if (size == 1) {
+                    while (!isDone) {
+                        try {
+                            int b = this.in.read();
+                            out.write(b);
+                        } catch (IOException e) {
+                            if (!isDone) {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                } else {
+                    byte[] a = new byte[size];
+                    while (!isDone) {
+                        try {
+                            readN(a, size, this.in);
+                            out.write(a);
+                        } catch (IOException e) {
+                            if (!isDone) {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                }
+            }
+
+            public void close() throws IOException {
+                isDone = true;
+                s.close();
+            }
+
+        }
+    }
+
+    static void readN(byte[] array, int size, InputStream in) throws IOException {
+        int nread = 0;
+        while (size > 0) {
+            int n = in.read(array, nread, size);
+            if (n < 0) throw new RuntimeException();
+            nread += n;
+            size -= n;
+        }
+    }
+
+    EchoServer server;
+
+    @Param({"1", "1024", "8192", "64000", "128000"})
+    public int size;
+
+    @Param({"false", "true"})
+    public boolean timeout;
+
+    Socket s;
+    InputStream in;
+    OutputStream out;
+    byte[] array;
 
     @Setup
-    public void beforeRun() throws IOException {
-        InetAddress iaddr = InetAddress.getLocalHost();
-
-        ss = new ServerSocket(0);
-        s1 = new Socket(iaddr, ss.getLocalPort());
-        s2 = ss.accept();
-
-        os = s1.getOutputStream();
-        is = s2.getInputStream();
-
-        rt = new ReadThread(is);
-        rt.start();
+    public void setup() throws IOException {
+        server = EchoServer.startServer(size, timeout);
+        int port = server.port;
+        s = new Socket(address, port);
+        s.setTcpNoDelay(true);
+        if (timeout) {
+            s.setSoTimeout(TIMEOUT);
+        }
+        in = s.getInputStream();
+        out = s.getOutputStream();
+        array = new byte[size];
     }
 
     @TearDown
-    public void afterRun() throws IOException, InterruptedException {
-        os.write(0);
-        os.close();
-        is.close();
-        s1.close();
-        s2.close();
-        ss.close();
-        rt.join();
+    public void tearDown() throws IOException {
+        server.close();
+        s.close();
     }
 
     @Benchmark
-    public void test() throws IOException {
-        os.write((byte) 4711);
-    }
-
-    static class ReadThread extends Thread {
-        private InputStream is;
-
-        public ReadThread(InputStream is) {
-            this.is = is;
+    public void echo() throws IOException {
+        if (size == 1) {
+            out.write((byte) 47);
+            int c = in.read();
+        } else {
+            out.write(array);
+            readN(array, size, in);
         }
 
-        public void run() {
-            try {
-                while (is.read() > 0);
-            } catch (SocketException ex) {
-                // ignore - most likely "socket closed", which means shutdown
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        }
     }
-
 }