Address review comments niosocketimpl-branch
authoralanb
Thu, 09 May 2019 17:49:28 +0100
branchniosocketimpl-branch
changeset 57355 ceb5c3fd71d2
parent 57354 feba42009db9
child 57358 f0a1d9760c5e
Address review comments
src/java.base/share/classes/java/net/SocketImpl.java
src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java
test/jdk/java/net/Socket/Timeouts.java
test/jdk/java/net/Socket/UdpSocket.java
test/jdk/java/net/SocketImpl/CompareSocketOptions.java
--- a/src/java.base/share/classes/java/net/SocketImpl.java	Mon May 06 09:43:48 2019 +0100
+++ b/src/java.base/share/classes/java/net/SocketImpl.java	Thu May 09 17:49:28 2019 +0100
@@ -43,21 +43,21 @@
  * create both client and server sockets.
  *
  * @implNote Client and server sockets created with the {@code Socket} and
- * {@code SocketServer} public constructors create a system/platform default
+ * {@code SocketServer} public constructors create a system-default
  * {@code SocketImpl}. The JDK historically used a {@code SocketImpl}
  * implementation type named "PlainSocketImpl" that has since been replaced by a
  * newer implementation. The JDK continues to ship with the older implementation
  * to allow code to run that depends on unspecified behavior that differs between
  * the old and new implementations. The old implementation will be used if the
  * Java virtual machine is started with the system property {@systemProperty
- * jdk.net.usePlainSocketImpl} set on the command line. It may also be set in
- * the JDK's network configuration file, located in {@code
- * ${java.home}/conf/net.properties}, for cases where it needs to be set without
- * changing the command line. The value of the system property is the string
+ * jdk.net.usePlainSocketImpl} set to use the old implementation. It may also be
+ * set in the JDK's network configuration file, located in {@code
+ * ${java.home}/conf/net.properties}. The value of the property is the string
  * representation of a boolean. If set without a value then it defaults to {@code
  * true}, hence running with {@code -Djdk.net.usePlainSocketImpl} or {@code
  * -Djdk.net.usePlainSocketImpl=true} will configure the Java virtual machine
- * to use the old implementation.
+ * to use the old implementation. The property and old implementation will be
+ * removed in a future version.
  *
  * @author  unascribed
  * @since   1.0
--- a/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java	Mon May 06 09:43:48 2019 +0100
+++ b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java	Thu May 09 17:49:28 2019 +0100
@@ -29,6 +29,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.UncheckedIOException;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.VarHandle;
 import java.net.InetAddress;
@@ -107,7 +108,7 @@
     private boolean stream;
     private FileDescriptorCloser closer;
 
-    // set by configureNonBlockingForever when the socket changed to non-blocking
+    // set to true when the socket is in non-blocking mode
     private volatile boolean nonBlocking;
 
     // used by connect/read/write/accept, protected by stateLock
@@ -191,29 +192,28 @@
     }
 
     /**
-     * Configures the socket's blocking mode except when socket has been
-     * configured non-blocking by {@code configureNonBlockingForever}.
+     * Configures the socket to blocking mode. This method is a no-op if the
+     * socket is already in blocking mode.
      * @throws IOException if closed or there is an I/O error changing the mode
      */
-    private void configureBlocking(FileDescriptor fd, boolean block) throws IOException {
-        assert readLock.isHeldByCurrentThread() || writeLock.isHeldByCurrentThread();
-        if (!nonBlocking) {
+    private void configureBlocking(FileDescriptor fd) throws IOException {
+        assert readLock.isHeldByCurrentThread();
+        if (nonBlocking) {
             synchronized (stateLock) {
-                if (!nonBlocking) {
-                    ensureOpen();
-                    IOUtil.configureBlocking(fd, block);
-                }
+                ensureOpen();
+                IOUtil.configureBlocking(fd, true);
+                nonBlocking = false;
             }
         }
     }
 
     /**
-     * Configures the socket to be non-blocking. Once configured to non-blocking
-     * by this method then the blocking mode cannot be changed back to blocking.
+     * Configures the socket to non-blocking mode. This method is a no-op if the
+     * socket is already in non-blocking mode.
      * @throws IOException if closed or there is an I/O error changing the mode
      */
-    private void configureNonBlockingForever(FileDescriptor fd) throws IOException {
-        assert readLock.isHeldByCurrentThread() || writeLock.isHeldByCurrentThread();
+    private void configureNonBlocking(FileDescriptor fd) throws IOException {
+        assert readLock.isHeldByCurrentThread();
         if (!nonBlocking) {
             synchronized (stateLock) {
                 ensureOpen();
@@ -244,7 +244,7 @@
             readerThread = 0;
             int state = this.state;
             if (state == ST_CLOSING)
-                tryClose();
+                tryFinishClose();
             if (!completed && state >= ST_CLOSING)
                 throw new SocketException("Socket closed");
         }
@@ -306,7 +306,7 @@
             int timeout = this.timeout;
             if (timeout > 0) {
                 // read with timeout
-                configureNonBlockingForever(fd);
+                configureNonBlocking(fd);
                 n = timedRead(fd, b, off, len, MILLISECONDS.toNanos(timeout));
             } else {
                 // read, no timeout
@@ -379,7 +379,7 @@
             writerThread = 0;
             int state = this.state;
             if (state == ST_CLOSING)
-                tryClose();
+                tryFinishClose();
             if (!completed && state >= ST_CLOSING)
                 throw new SocketException("Socket closed");
         }
@@ -405,7 +405,7 @@
     /**
      * 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
+     * @throws SocketException if the socket is closed or a socket I/O error occurs
      */
     private int implWrite(byte[] b, int off, int len) throws IOException {
         int n = 0;
@@ -426,7 +426,7 @@
 
     /**
      * 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
+     * @throws SocketException if the socket is closed or a socket I/O error occurs
      */
     private void write(byte[] b, int off, int len) throws IOException {
         Objects.checkFromIndexSize(off, len, b.length);
@@ -523,7 +523,7 @@
             readerThread = 0;
             int state = this.state;
             if (state == ST_CLOSING)
-                tryClose();
+                tryFinishClose();
             if (completed && state == ST_CONNECTING) {
                 this.state = ST_CONNECTED;
                 localport = Net.localAddress(fd).getPort();
@@ -582,7 +582,7 @@
 
                     // configure socket to non-blocking mode when there is a timeout
                     if (millis > 0) {
-                        configureBlocking(fd, false);
+                        configureNonBlocking(fd);
                     }
 
                     int n = Net.connect(fd, address, port);
@@ -608,7 +608,7 @@
 
                     // restore socket to blocking mode
                     if (connected && millis > 0) {
-                        configureBlocking(fd, true);
+                        configureBlocking(fd);
                     }
 
                 } finally {
@@ -684,7 +684,7 @@
             int state = this.state;
             readerThread = 0;
             if (state == ST_CLOSING)
-                tryClose();
+                tryFinishClose();
             if (!completed && state >= ST_CLOSING)
                 throw new SocketException("Socket closed");
         }
@@ -748,7 +748,7 @@
             try {
                 if (remainingNanos > 0) {
                     // accept with timeout
-                    configureNonBlockingForever(fd);
+                    configureNonBlocking(fd);
                     n = timedAccept(fd, newfd, isaa, remainingNanos);
                 } else {
                     // accept, no timeout
@@ -844,14 +844,15 @@
     }
 
     /**
-     * Closes the socket, and returns true, if there are no I/O operations in
-     * progress.
+     * Closes the socket if there are no I/O operations in progress.
      */
-    private boolean tryClose() {
+    private boolean tryClose() throws IOException {
         assert Thread.holdsLock(stateLock) && state == ST_CLOSING;
         if (readerThread == 0 && writerThread == 0) {
             try {
                 closer.run();
+            } catch (UncheckedIOException ioe) {
+                throw ioe.getCause();
             } finally {
                 state = ST_CLOSED;
             }
@@ -862,6 +863,17 @@
     }
 
     /**
+     * Invokes tryClose to attempt to close the socket.
+     *
+     * This method is used for deferred closing by I/O operations.
+     */
+    private void tryFinishClose() {
+        try {
+            tryClose();
+        } catch (IOException ignore) { }
+    }
+
+    /**
      * Closes the socket. If there are I/O operations in progress then the
      * socket is pre-closed and the threads are signalled. The socket will be
      * closed when the last I/O operation aborts.
@@ -1218,7 +1230,7 @@
                 try {
                     nd.close(fd);
                 } catch (IOException ioe) {
-                    throw new RuntimeException(ioe);
+                    throw new UncheckedIOException(ioe);
                 } finally {
                     if (!stream) {
                         // decrement
--- a/test/jdk/java/net/Socket/Timeouts.java	Mon May 06 09:43:48 2019 +0100
+++ b/test/jdk/java/net/Socket/Timeouts.java	Thu May 09 17:49:28 2019 +0100
@@ -96,10 +96,8 @@
     public void testTimedConnect4() throws IOException {
         try (ServerSocket ss = boundServerSocket()) {
             try (Socket s = new Socket()) {
-                try {
-                    s.connect(ss.getLocalSocketAddress(), -1);
-                    assertTrue(false);
-                } catch (IllegalArgumentException expected) { }
+                expectThrows(IllegalArgumentException.class,
+                             () -> s.connect(ss.getLocalSocketAddress(), -1));
             }
         }
     }
@@ -135,13 +133,9 @@
         withConnection((s1, s2) -> {
             s2.setSoTimeout(2000);
             long startMillis = millisTime();
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketTimeoutException expected) {
-                int timeout = s2.getSoTimeout();
-                checkDuration(startMillis, timeout-100, timeout+2000);
-            }
+            expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read());
+            int timeout = s2.getSoTimeout();
+            checkDuration(startMillis, timeout-100, timeout+2000);
         });
     }
 
@@ -151,10 +145,7 @@
     public void testTimedRead4() throws IOException {
         withConnection((s1, s2) -> {
             s2.setSoTimeout(2000);
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketTimeoutException e) { }
+            expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read());
             s1.getOutputStream().write(99);
             int b = s2.getInputStream().read();
             assertTrue(b == 99);
@@ -168,10 +159,7 @@
     public void testTimedRead5() throws IOException {
         withConnection((s1, s2) -> {
             s2.setSoTimeout(2000);
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketTimeoutException e) { }
+            expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read());
             s2.setSoTimeout(30*3000);
             scheduleWrite(s1.getOutputStream(), 99, 2000);
             int b = s2.getInputStream().read();
@@ -185,10 +173,7 @@
     public void testTimedRead6() throws IOException {
         withConnection((s1, s2) -> {
             s2.setSoTimeout(2000);
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketTimeoutException e) { }
+            expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read());
             s1.getOutputStream().write(99);
             s2.setSoTimeout(0);
             int b = s2.getInputStream().read();
@@ -203,10 +188,7 @@
     public void testTimedRead7() throws IOException {
         withConnection((s1, s2) -> {
             s2.setSoTimeout(2000);
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketTimeoutException e) { }
+            expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read());
             scheduleWrite(s1.getOutputStream(), 99, 2000);
             s2.setSoTimeout(0);
             int b = s2.getInputStream().read();
@@ -221,10 +203,7 @@
         withConnection((s1, s2) -> {
             s2.setSoTimeout(30*1000);
             scheduleClose(s2, 2000);
-            try {
-                s2.getInputStream().read();
-                assertTrue(false);
-            } catch (SocketException expected) { }
+            expectThrows(SocketException.class, () -> s2.getInputStream().read());
         });
     }
 
@@ -323,7 +302,7 @@
             try {
                 Socket s = ss.accept();
                 s.close();
-                assertTrue(false);
+                fail();
             } catch (SocketTimeoutException expected) {
                 int timeout = ss.getSoTimeout();
                 checkDuration(startMillis, timeout-100, timeout+2000);
@@ -341,7 +320,7 @@
             try {
                 Socket s = ss.accept();
                 s.close();
-                assertTrue(false);
+                fail();
             } catch (SocketTimeoutException expected) { }
             try (Socket s1 = new Socket()) {
                 s1.connect(ss.getLocalSocketAddress());
@@ -361,7 +340,7 @@
             try {
                 Socket s = ss.accept();
                 s.close();
-                assertTrue(false);
+                fail();
             } catch (SocketTimeoutException expected) { }
             ss.setSoTimeout(0);
             try (Socket s1 = new Socket()) {
@@ -382,7 +361,7 @@
             try {
                 Socket s = ss.accept();
                 s.close();
-                assertTrue(false);
+                fail();
             } catch (SocketTimeoutException expected) { }
             ss.setSoTimeout(0);
             scheduleConnect(ss.getLocalSocketAddress(), 2000);
@@ -402,7 +381,7 @@
             long startMillis = millisTime();
             try {
                 ss.accept().close();
-                assertTrue(false);
+                fail();
             } catch (SocketException expected) {
                 checkDuration(startMillis, delay-100, delay+2000);
             }
@@ -420,7 +399,7 @@
             try {
                 Socket s = ss.accept();
                 s.close();
-                assertTrue(false);
+                fail();
             } catch (SocketTimeoutException expected) {
                 // accept should have blocked for 2 seconds
                 int timeout = ss.getSoTimeout();
@@ -444,7 +423,7 @@
             try {
                 Socket s = ss.accept();   // should block for 4 seconds
                 s.close();
-                assertTrue(false);
+                fail();
             } catch (SocketTimeoutException expected) {
                 // accept should have blocked for 4 seconds
                 int timeout = ss.getSoTimeout();
--- a/test/jdk/java/net/Socket/UdpSocket.java	Mon May 06 09:43:48 2019 +0100
+++ b/test/jdk/java/net/Socket/UdpSocket.java	Thu May 09 17:49:28 2019 +0100
@@ -77,7 +77,8 @@
                 byte[] array2 = new byte[100];
                 int n = s.getInputStream().read(array2);
                 assertTrue(n == MESSAGE.length(), "Unexpected size");
-                assertTrue(Arrays.equals(array1, 0, n, array2, 0, n), "Unexpected contents");
+                assertEquals(Arrays.copyOf(array1, n), Arrays.copyOf(array2, n),
+                            "Unexpected contents");
             }
         }
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/SocketImpl/CompareSocketOptions.java	Thu May 09 17:49:28 2019 +0100
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8221481
+ * @modules java.base/java.net:+open java.base/sun.nio.ch:+open
+ * @run testng CompareSocketOptions
+ * @summary Compare the set of socket options supported by the old and new SocketImpls
+ */
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketImpl;
+
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+@Test
+public class CompareSocketOptions {
+
+    /**
+     * Test that the old and new platform SocketImpl support the same set of
+     * client socket options.
+     */
+    public void testClientSocketSupportedOptions() throws IOException {
+        Socket s1 = new Socket(createOldSocketImpl(false)) { };
+        Socket s2 = new Socket(createNewSocketImpl(false)) { };
+        assertEquals(s1.supportedOptions(), s2.supportedOptions());
+    }
+
+    /**
+     * Test that the old and new platform SocketImpl support the same set of
+     * server socket options.
+     */
+    public void testServerSocketSupportedOptions() throws IOException {
+        ServerSocket ss1 = new ServerSocket(createOldSocketImpl(true)) { };
+        ServerSocket ss2 = new ServerSocket(createNewSocketImpl(true)) { };
+        assertEquals(ss1.supportedOptions(), ss2.supportedOptions());
+    }
+
+    private static SocketImpl createOldSocketImpl(boolean server) {
+        return newPlatformSocketImpl("java.net.PlainSocketImpl", server);
+    }
+
+    private static SocketImpl createNewSocketImpl(boolean server) {
+        return newPlatformSocketImpl("sun.nio.ch.NioSocketImpl", server);
+    }
+
+    private static SocketImpl newPlatformSocketImpl(String name, boolean server) {
+        try {
+            var ctor = Class.forName(name).getDeclaredConstructor(boolean.class);
+            ctor.setAccessible(true);
+            return (SocketImpl) ctor.newInstance(server);
+        } catch (Exception e) {
+            fail("Should not get here", e);
+            return null;
+        }
+    }
+}
\ No newline at end of file