More cleanup and tests niosocketimpl-branch
authoralanb
Fri, 22 Feb 2019 17:03:09 +0000
branchniosocketimpl-branch
changeset 57207 30695f27d7ea
parent 57203 a2529b040b48
child 57208 7a45c67e73d0
More cleanup and tests
src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java
test/jdk/java/net/Socket/ConnectionReset.java
test/jdk/java/net/Socket/Timeouts.java
--- a/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java	Thu Feb 21 20:20:11 2019 +0000
+++ b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java	Fri Feb 22 17:03:09 2019 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 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
@@ -248,6 +248,30 @@
     }
 
     /**
+     * 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, int millis)
+        throws IOException
+    {
+        long nanos = NANOSECONDS.convert(millis, TimeUnit.MILLISECONDS);
+        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
      * @throws IOException if the socket is closed or an I/O occurs
@@ -256,28 +280,18 @@
     private int read(byte[] b, int off, int len) throws IOException {
         readLock.lock();
         try {
-            int timeout = this.timeout;
             int n = 0;
             FileDescriptor fd = beginRead();
             try {
                 if (isInputClosed)
                     return IOStatus.EOF;
+                int timeout = this.timeout;
                 configureNonBlockingIfNeeded(fd, timeout);
                 n = tryRead(fd, b, off, len);
                 if (IOStatus.okayToRetry(n) && isOpen()) {
                     if (timeout > 0) {
                         // read with timeout
-                        long nanos = NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS);
-                        do {
-                            long startTime = System.nanoTime();
-                            park(fd, Net.POLLIN, nanos);
-                            n = tryRead(fd, b, off, len);
-                            if (n == IOStatus.UNAVAILABLE) {
-                                nanos -= System.nanoTime() - startTime;
-                                if (nanos <= 0)
-                                    throw new SocketTimeoutException("Read timed out");
-                            }
-                        } while (n == IOStatus.UNAVAILABLE && isOpen());
+                        n = timedRead(fd, b, off, len, timeout);
                     } else {
                         // read, no timeout
                         do {
@@ -526,6 +540,28 @@
     }
 
     /**
+     * Waits for a connection attempt to finish with a timeout
+     * @throws SocketTimeoutException if the connect timeout elapses
+     */
+    private int timedFinishConnect(FileDescriptor fd, int millis) throws IOException {
+        long nanos = NANOSECONDS.convert(millis, TimeUnit.MILLISECONDS);
+        long remainingNanos = nanos;
+        long startNanos = System.nanoTime();
+        int n;
+        do {
+            park(fd, Net.POLLOUT, remainingNanos);
+            n = Net.pollConnectNow(fd);
+            if (n == 0) {
+                remainingNanos = nanos - (System.nanoTime() - startNanos);
+                if (remainingNanos <= 0) {
+                    throw new SocketTimeoutException("Connect timed out");
+                }
+            }
+        } while (n == 0 && isOpen());
+        return n;
+    }
+
+    /**
      * Connect the socket. Closes the socket if connection cannot be established.
      * @throws IllegalArgumentException if the address is not an InetSocketAddress
      * @throws UnknownHostException if the InetSocketAddress is not resolved
@@ -555,21 +591,10 @@
                     int n = Net.connect(fd, address, port);
                     if (IOStatus.okayToRetry(n) && isOpen()) {
                         if (millis > 0) {
-                            // connect with timeout
-                            assert nonBlocking;
-                            long nanos = NANOSECONDS.convert(millis, MILLISECONDS);
-                            do {
-                                long startTime = System.nanoTime();
-                                park(fd, Net.POLLOUT, nanos);
-                                n = Net.pollConnectNow(fd);
-                                if (n == 0) {
-                                    nanos -= System.nanoTime() - startTime;
-                                    if (nanos <= 0)
-                                        throw new SocketTimeoutException("Connect timed out");
-                                }
-                            } while (n == 0 && isOpen());
+                            // finish connect with timeout
+                            n = timedFinishConnect(fd, millis);
                         } else {
-                            // connect, no timeout
+                            // finish connect, no timeout
                             do {
                                 park(fd, Net.POLLOUT);
                                 n = Net.pollConnectNow(fd);
@@ -661,6 +686,33 @@
         }
     }
 
+    /**
+     * Accepts a new connection with a timeout
+     * @throws SocketTimeoutException if the accept timeout elapses
+     */
+    private int timedAccept(FileDescriptor fd,
+                            FileDescriptor newfd,
+                            InetSocketAddress[] isaa,
+                            int millis)
+        throws IOException
+    {
+        long nanos = NANOSECONDS.convert(millis, TimeUnit.MILLISECONDS);
+        long remainingNanos = nanos;
+        long startNanos = System.nanoTime();
+        int n;
+        do {
+            park(fd, Net.POLLIN, remainingNanos);
+            n = ServerSocketChannelImpl.accept0(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;
+    }
+
     @Override
     protected void accept(SocketImpl si) throws IOException {
         // accept a connection
@@ -679,18 +731,7 @@
                 if (IOStatus.okayToRetry(n) && isOpen()) {
                     if (timeout > 0) {
                         // accept with timeout
-                        assert nonBlocking;
-                        long nanos = NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS);
-                        do {
-                            long startTime = System.nanoTime();
-                            park(fd, Net.POLLIN, nanos);
-                            n = ServerSocketChannelImpl.accept0(fd, newfd, isaa);
-                            if (n == IOStatus.UNAVAILABLE) {
-                                nanos -= System.nanoTime() - startTime;
-                                if (nanos <= 0)
-                                    throw new SocketTimeoutException("Accept timed out");
-                            }
-                        } while (n == IOStatus.UNAVAILABLE && isOpen());
+                        n = timedAccept(fd, newfd, isaa, timeout);
                     } else {
                         // accept, no timeout
                         do {
@@ -756,6 +797,7 @@
                 if (eof) {
                     return -1;
                 } else if (reset) {
+                    NioSocketImpl.this.ensureOpen();
                     throw new SocketException("Connection reset");
                 } else if (len == 0) {
                     return 0;
@@ -1153,7 +1195,7 @@
                     n = Net.sendOOB(fd, (byte) data);
                 } while (n == IOStatus.INTERRUPTED && isOpen());
                 if (n == IOStatus.UNAVAILABLE) {
-                    throw new RuntimeException("not implemented yet");
+                    throw new SocketException("No buffer space available");
                 }
             } finally {
                 endWrite(n > 0);
--- a/test/jdk/java/net/Socket/ConnectionReset.java	Thu Feb 21 20:20:11 2019 +0000
+++ b/test/jdk/java/net/Socket/ConnectionReset.java	Fri Feb 22 17:03:09 2019 +0000
@@ -156,6 +156,37 @@
         });
     }
 
+    /**
+     * Tests available and read on a socket closed after connection reset
+     */
+    public void testAfterClose() throws IOException {
+        System.out.println("testAfterClose");
+        acceptResetConnection(null, s -> {
+            InputStream in = s.getInputStream();
+            try {
+                in.read();
+                assertTrue(false);
+            } catch (IOException ioe) {
+                // expected
+            }
+            s.close();
+            try {
+                int bytesAvailable = in.available();
+                System.out.format("available => %d%n", bytesAvailable);
+                assertTrue(false);
+            } catch (IOException ioe) {
+                System.out.format("available => %s (expected)%n", ioe);
+            }
+            try {
+                int n = in.read();
+                System.out.format("read => %d%n", n);
+                assertTrue(false);
+            } catch (IOException ioe) {
+                System.out.format("read => %s (expected)%n", ioe);
+            }
+        });
+    }
+
     interface ThrowingConsumer<T> {
         void accept(T t) throws IOException;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/Socket/Timeouts.java	Fri Feb 22 17:03:09 2019 +0000
@@ -0,0 +1,332 @@
+/*
+ * 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
+ * @library /test/lib
+ * @build jdk.test.lib.Utils
+ * @run testng Timeouts
+ * @run testng/othervm -Djdk.net.usePlainSocketImpl Timeouts
+ * @summary Test Socket timeouts
+ */
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+import jdk.test.lib.Utils;
+
+@Test
+public class Timeouts {
+
+    /**
+     * Test timed connect where connection is established
+     */
+    public void testTimedConnect1() throws IOException {
+        try (ServerSocket ss = new ServerSocket(0)) {
+            try (Socket s = new Socket()) {
+                s.connect(ss.getLocalSocketAddress(), 3000);
+            }
+        }
+    }
+
+    /**
+     * Test timed connect where connection is refused
+     */
+    public void testTimedConnect2() throws IOException {
+        try (Socket s = new Socket()) {
+            SocketAddress remote = Utils.refusingEndpoint();
+            try {
+                s.connect(remote, 2000);
+            } catch (ConnectException expected) { }
+        }
+    }
+
+    /**
+     * Test timed read where the read succeeds
+     */
+    public void testTimedRead1() throws IOException {
+        withConnection((s1, s2) -> {
+            s1.getOutputStream().write(99);
+            s2.setSoTimeout(3000);
+            int b = s2.getInputStream().read();
+            assertTrue(b == 99);
+        });
+    }
+
+    /**
+     * Test timed read where the read times out
+     */
+    public void testTimedRead2() throws IOException {
+        withConnection((s1, s2) -> {
+            s2.setSoTimeout(3000);
+            try {
+                s2.getInputStream().read();
+                assertTrue(false);
+            } catch (SocketTimeoutException expected) { }
+        });
+    }
+
+    /**
+     * Test timed read that succeeds after a previous read has timed out
+     */
+    public void testTimedRead3() throws IOException {
+        withConnection((s1, s2) -> {
+            s2.setSoTimeout(3000);
+            try {
+                s2.getInputStream().read();
+                assertTrue(false);
+            } catch (SocketTimeoutException e) { }
+            s1.getOutputStream().write(99);
+            int b = s2.getInputStream().read();
+            assertTrue(b == 99);
+        });
+    }
+
+    /**
+     * Test non-timed read that succeeds after a previous read has timed out
+     */
+    public void testTimedRead4() throws IOException {
+        withConnection((s1, s2) -> {
+            s2.setSoTimeout(3000);
+            try {
+                s2.getInputStream().read();
+                assertTrue(false);
+            } catch (SocketTimeoutException e) { }
+            s1.getOutputStream().write(99);
+            s2.setSoTimeout(0);
+            int b = s2.getInputStream().read();
+            assertTrue(b == 99);
+        });
+    }
+
+    /**
+     * Test async close of timed read
+     */
+    public void testTimedRead5() throws IOException {
+        withConnection((s1, s2) -> {
+            s2.setSoTimeout(30000);
+            scheduleClose(s2, 2000);
+            try {
+                s2.getInputStream().read();
+                assertTrue(false);
+            } catch (SocketException expected) { }
+        });
+    }
+
+    /**
+     * Test writing after a timed read. The timed read changes the underlying
+     * socket to non-blocking.
+     */
+    public void testTimedWrite1() throws IOException {
+        withConnection((s1, s2) -> {
+            s1.getOutputStream().write(99);
+            s2.setSoTimeout(3000);
+            int b = s2.getInputStream().read();
+            assertTrue(b == 99);
+
+            // schedule thread to read s1 to EOF
+            scheduleReadToEOF(s1, 3000);
+
+            // write a lot so that write blocks
+            byte[] data = new byte[128*1024];
+            for (int i = 0; i < 100; i++) {
+                s2.getOutputStream().write(data);
+            }
+        });
+    }
+
+    /**
+     * Test async close of writer (after a timed read). The timed read changes
+     * the underlying socket to non-blocking.
+     */
+    public void testTimedWrite2() throws IOException {
+        withConnection((s1, s2) -> {
+            s1.getOutputStream().write(99);
+            s2.setSoTimeout(3000);
+            int b = s2.getInputStream().read();
+            assertTrue(b == 99);
+
+            // schedule s2 to be be closed
+            scheduleClose(s2, 3000);
+
+            // write a lot so that write blocks
+            byte[] data = new byte[128*1024];
+            try {
+                while (true) {
+                    s2.getOutputStream().write(data);
+                }
+            } catch (SocketException expected) { }
+        });
+    }
+
+    /**
+     * Test timed accept where a connection is established
+     */
+    public void testTimedAccept1() throws IOException {
+        Socket s1 = null;
+        Socket s2 = null;
+        try (ServerSocket ss = new ServerSocket(0)) {
+            s1 = new Socket();
+            s1.connect(ss.getLocalSocketAddress());
+            ss.setSoTimeout(3000);
+            s2 = ss.accept();
+        } finally {
+            if (s1 != null) s1.close();
+            if (s2 != null) s2.close();
+        }
+    }
+
+    /**
+     * Test timed accept where the accept times out
+     */
+    public void testTimedAccept2() throws IOException {
+        try (ServerSocket ss = new ServerSocket(0)) {
+            ss.setSoTimeout(2000);
+            try {
+                ss.accept().close();
+                assertTrue(false);
+            } catch (SocketTimeoutException expected) { }
+        }
+    }
+
+    /**
+     * Test timed accept where a connection is established after a previous
+     * accept timed out.
+     */
+    public void testTimedAccept3() throws IOException {
+        try (ServerSocket ss = new ServerSocket(0)) {
+            ss.setSoTimeout(3000);
+            try {
+                ss.accept().close();
+                assertTrue(false);
+            } catch (SocketTimeoutException expected) { }
+            try (Socket s1 = new Socket()) {
+                s1.connect(ss.getLocalSocketAddress());
+                Socket s2 = ss.accept();
+                s2.close();
+            }
+        }
+    }
+
+    /**
+     * Test async close of a timed accept
+     */
+    public void testTimedAccept4() throws IOException {
+        try (ServerSocket ss = new ServerSocket(0)) {
+            ss.setSoTimeout(30000);
+            scheduleClose(ss, 2000);
+            try {
+                ss.accept().close();
+                assertTrue(false);
+            } catch (SocketException expected) { }
+        }
+    }
+
+    @Test(expectedExceptions = { IllegalArgumentException.class })
+    public void testBadTimeout1() throws IOException {
+        try (Socket s = new Socket()) {
+            s.setSoTimeout(-1);
+        }
+    }
+
+    @Test(expectedExceptions = { IllegalArgumentException.class })
+    public void testBadTimeout2() throws IOException {
+        try (ServerSocket ss = new ServerSocket(0)) {
+            try (Socket s = new Socket()) {
+                s.connect(ss.getLocalSocketAddress(), -1);
+            }
+        }
+    }
+
+    @Test(expectedExceptions = { IllegalArgumentException.class })
+    public void testBadTimeout3() throws IOException {
+        try (ServerSocket ss = new ServerSocket()) {
+            ss.setSoTimeout(-1);
+        }
+    }
+
+    interface ThrowingBiConsumer<T, U> {
+        void accept(T t, U u) throws IOException;
+    }
+
+    /**
+     * Invokes the consumer with a connected pair of sockets
+     */
+    static void withConnection(ThrowingBiConsumer<Socket, Socket> consumer)
+        throws IOException
+    {
+        Socket s1 = null;
+        Socket s2 = null;
+        try (ServerSocket ss = new ServerSocket(0)) {
+            s1 = new Socket();
+            s1.connect(ss.getLocalSocketAddress());
+            s2 = ss.accept();
+            consumer.accept(s1, s2);
+        } finally {
+            if (s1 != null) s1.close();
+            if (s2 != null) s2.close();
+        }
+    }
+
+    /**
+     * Schedule c to be closed after {@code delay} milliseconds
+     */
+    static void scheduleClose(Closeable c, long delay) {
+        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+        try {
+            Runnable task = () -> { try { c.close(); } catch (IOException ioe) { } };
+            executor.schedule(task, delay, TimeUnit.MILLISECONDS);
+        } finally {
+            executor.shutdown();
+        }
+    }
+
+    /**
+     * Schedule a thread to read to EOF {@code delay} milliseconds
+     */
+    static void scheduleReadToEOF(Socket socket, long delay) {
+        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+        try {
+            Runnable task = () -> {
+                byte[] bytes = new byte[8192];
+                try {
+                    while (socket.getInputStream().read(bytes) != -1) { }
+                } catch (IOException ioe) { }
+            };
+            executor.schedule(task, delay, TimeUnit.MILLISECONDS);
+        } finally {
+            executor.shutdown();
+        }
+    }
+}