--- 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();
+ }
+ }
+}