# HG changeset patch # User alanb # Date 1559197159 -3600 # Node ID 59567035d2799166a811ff4f790c77ef8cb11f83 # Parent c41783eb76ebf9f3f8562c69ccc7195ebbd3be56 8221481: Reimplement the Legacy Socket API Reviewed-by: michaelm, chegar diff -r c41783eb76eb -r 59567035d279 src/java.base/share/classes/java/net/SocketImpl.java --- a/src/java.base/share/classes/java/net/SocketImpl.java Wed May 29 22:17:48 2019 -0400 +++ b/src/java.base/share/classes/java/net/SocketImpl.java Thu May 30 07:19:19 2019 +0100 @@ -25,33 +25,63 @@ 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.Objects; 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. - *

- * A "plain" socket implements these methods exactly as - * described, without attempting to go through a firewall or proxy. + * + * @implNote Client and server sockets created with the {@code Socket} and + * {@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 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. The property and old implementation will be + * removed in a future version. * * @author unascribed * @since 1.0 */ public abstract class SocketImpl implements SocketOptions { + private static final boolean USE_PLAINSOCKETIMPL = usePlainSocketImpl(); + + private static boolean usePlainSocketImpl() { + PrivilegedAction 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 createPlatformSocketImpl(boolean server) { - return (S) new PlainSocketImpl(server); + if (USE_PLAINSOCKETIMPL) { + return (S) new PlainSocketImpl(server); + } else { + return (S) new NioSocketImpl(server); + } } /** diff -r c41783eb76eb -r 59567035d279 src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java Thu May 30 07:19:19 2019 +0100 @@ -0,0 +1,1283 @@ +/* + * 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.io.UncheckedIOException; +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.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 the connect method is used to establish a connection with a + * timeout then the socket is configured non-blocking for the connect attempt, + * and then restored to blocking mode when the connection is established. + * If the accept or read methods are used with a timeout then the socket is + * configured non-blocking and is never restored. When in non-blocking mode, + * operations that don't complete immediately will poll the socket and preserve + * the semantics of blocking operations. + */ + +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 Object stateLock = new Object(); + private static final int ST_NEW = 0; + private static final int ST_UNCONNECTED = 1; + private static final int ST_CONNECTING = 2; + private static final int ST_CONNECTED = 3; + private static final int ST_CLOSING = 4; + private static final int ST_CLOSED = 5; + private volatile int state; // need stateLock to change + + // set by SocketImpl.create, protected by stateLock + private boolean stream; + private FileDescriptorCloser closer; + + // set to true when the socket is in non-blocking mode + 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; + + // 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 = NANOSECONDS.toMillis(nanos); + } + 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 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) throws IOException { + assert readLock.isHeldByCurrentThread(); + if (nonBlocking) { + synchronized (stateLock) { + ensureOpen(); + IOUtil.configureBlocking(fd, true); + nonBlocking = false; + } + } + } + + /** + * 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 configureNonBlocking(FileDescriptor fd) throws IOException { + assert readLock.isHeldByCurrentThread(); + if (!nonBlocking) { + synchronized (stateLock) { + ensureOpen(); + IOUtil.configureBlocking(fd, false); + nonBlocking = true; + } + } + } + + /** + * Marks the beginning of a read operation that might block. + * @throws SocketException if the socket is closed or not connected + */ + private FileDescriptor beginRead() throws SocketException { + synchronized (stateLock) { + ensureOpenAndConnected(); + readerThread = NativeThread.current(); + return fd; + } + } + + /** + * Marks the end of a read operation that may have blocked. + * @throws SocketException is the socket is closed + */ + private void endRead(boolean completed) throws SocketException { + synchronized (stateLock) { + readerThread = 0; + int state = this.state; + if (state == ST_CLOSING) + tryFinishClose(); + if (!completed && state >= ST_CLOSING) + throw new SocketException("Socket closed"); + } + } + + /** + * 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 + { + long startNanos = System.nanoTime(); + int n = tryRead(fd, b, off, len); + while (n == IOStatus.UNAVAILABLE && isOpen()) { + long remainingNanos = nanos - (System.nanoTime() - startNanos); + if (remainingNanos <= 0) { + throw new SocketTimeoutException("Read timed out"); + } + park(fd, Net.POLLIN, remainingNanos); + n = tryRead(fd, b, off, len); + } + 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) { + // read with timeout + configureNonBlocking(fd); + n = timedRead(fd, b, off, len, MILLISECONDS.toNanos(timeout)); + } else { + // read, no timeout + n = tryRead(fd, b, off, len); + while (IOStatus.okayToRetry(n) && isOpen()) { + park(fd, Net.POLLIN); + n = tryRead(fd, b, off, len); + } + } + 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 { + synchronized (stateLock) { + ensureOpenAndConnected(); + writerThread = NativeThread.current(); + return fd; + } + } + + /** + * Marks the end of a write operation that may have blocked. + * @throws SocketException is the socket is closed + */ + private void endWrite(boolean completed) throws SocketException { + synchronized (stateLock) { + writerThread = 0; + int state = this.state; + if (state == ST_CLOSING) + tryFinishClose(); + if (!completed && state >= ST_CLOSING) + throw new SocketException("Socket closed"); + } + } + + /** + * 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 a 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 a 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 { + synchronized (stateLock) { + 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; + } + } + + /** + * Marks the beginning of a connect operation that might block. + * @throws SocketException if the socket is closed or already connected + */ + private FileDescriptor beginConnect(InetAddress address, int port) + throws IOException + { + synchronized (stateLock) { + int state = this.state; + if (state != ST_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; + } + } + + /** + * 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 { + synchronized (stateLock) { + readerThread = 0; + int state = this.state; + if (state == ST_CLOSING) + tryFinishClose(); + 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"); + } + } + } + + /** + * Waits for a connection attempt to finish with a timeout + * @throws SocketTimeoutException if the connect timeout elapses + */ + private boolean timedFinishConnect(FileDescriptor fd, long nanos) throws IOException { + long startNanos = System.nanoTime(); + boolean polled = Net.pollConnectNow(fd); + while (!polled && isOpen()) { + long remainingNanos = nanos - (System.nanoTime() - startNanos); + if (remainingNanos <= 0) { + throw new SocketTimeoutException("Connect timed out"); + } + park(fd, Net.POLLOUT, remainingNanos); + polled = Net.pollConnectNow(fd); + } + return 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 { + + // configure socket to non-blocking mode when there is a timeout + if (millis > 0) { + configureNonBlocking(fd); + } + + int n = Net.connect(fd, address, port); + if (n > 0) { + // connection established + connected = true; + } else { + assert IOStatus.okayToRetry(n); + if (millis > 0) { + // finish connect with timeout + long nanos = MILLISECONDS.toNanos(millis); + connected = timedFinishConnect(fd, nanos); + } else { + // finish connect, no timeout + boolean polled = false; + while (!polled && isOpen()) { + park(fd, Net.POLLOUT); + polled = Net.pollConnectNow(fd); + } + connected = polled && isOpen(); + } + } + + // restore socket to blocking mode + if (connected && millis > 0) { + configureBlocking(fd); + } + + } 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 { + synchronized (stateLock) { + ensureOpen(); + if (localport != 0) + throw new SocketException("Already bound"); + NetHooks.beforeTcpBind(fd, host, port); + Net.bind(fd, host, port); + // set the address field to the 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(); + } + } + + @Override + protected void listen(int backlog) throws IOException { + synchronized (stateLock) { + ensureOpen(); + if (localport == 0) + throw new SocketException("Not bound"); + Net.listen(fd, backlog < 1 ? 50 : backlog); + } + } + + /** + * Marks the beginning of an accept operation that might block. + * @throws SocketException if the socket is closed + */ + private FileDescriptor beginAccept() throws SocketException { + synchronized (stateLock) { + ensureOpen(); + if (!stream) + throw new SocketException("Not a stream socket"); + if (localport == 0) + throw new SocketException("Not bound"); + readerThread = NativeThread.current(); + return fd; + } + } + + /** + * Marks the end of an accept operation that may have blocked. + * @throws SocketException is the socket is closed + */ + private void endAccept(boolean completed) throws SocketException { + synchronized (stateLock) { + int state = this.state; + readerThread = 0; + if (state == ST_CLOSING) + tryFinishClose(); + if (!completed && state >= ST_CLOSING) + throw new SocketException("Socket closed"); + } + } + + /** + * 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 + { + long startNanos = System.nanoTime(); + int n = Net.accept(fd, newfd, isaa); + while (n == IOStatus.UNAVAILABLE && isOpen()) { + long remainingNanos = nanos - (System.nanoTime() - startNanos); + if (remainingNanos <= 0) { + throw new SocketTimeoutException("Accept timed out"); + } + park(fd, Net.POLLIN, remainingNanos); + n = Net.accept(fd, newfd, isaa); + } + 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) { + // accept with timeout + configureNonBlocking(fd); + n = timedAccept(fd, newfd, isaa, remainingNanos); + } else { + // accept, no timeout + n = Net.accept(fd, newfd, isaa); + while (IOStatus.okayToRetry(n) && isOpen()) { + park(fd, Net.POLLIN); + n = Net.accept(fd, newfd, isaa); + } + } + } 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 + synchronized (nsi.stateLock) { + 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; + } + } + + @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 { + synchronized (stateLock) { + ensureOpenAndConnected(); + if (isInputClosed) { + return 0; + } else { + return Net.available(fd); + } + } + } + + /** + * Closes the socket if there are no I/O operations in progress. + */ + 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; + } + return true; + } else { + return false; + } + } + + /** + * 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. + */ + @Override + protected void close() throws IOException { + synchronized (stateLock) { + int state = this.state; + if (state >= ST_CLOSING) + return; + if (state == ST_NEW) { + // stillborn + this.state = ST_CLOSED; + return; + } + this.state = ST_CLOSING; + + // shutdown output when linger interval not set to 0 + try { + var SO_LINGER = StandardSocketOptions.SO_LINGER; + if ((int) Net.getSocketOption(fd, SO_LINGER) != 0) { + Net.shutdown(fd, Net.SHUT_WR); + } + } catch (IOException ignore) { } + + // attempt to close the socket. If there are I/O operations in progress + // then the socket is pre-closed and the thread(s) signalled. The + // last thread will close the file descriptor. + if (!tryClose()) { + nd.preClose(fd); + long reader = readerThread; + if (reader != 0) + NativeThread.signal(reader); + long writer = writerThread; + if (writer != 0) + NativeThread.signal(writer); + } + } + } + + // the socket options supported by client and server sockets + private static volatile Set> clientSocketOptions; + private static volatile Set> serverSocketOptions; + + @Override + protected Set> supportedOptions() { + Set> 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 void setOption(SocketOption opt, T value) throws IOException { + if (!supportedOptions().contains(opt)) + throw new UnsupportedOperationException("'" + opt + "' not supported"); + if (!opt.type().isInstance(value)) + throw new IllegalArgumentException("Invalid value '" + value + "'"); + synchronized (stateLock) { + ensureOpen(); + if (opt == StandardSocketOptions.IP_TOS) { + // maps to IP_TOS or IPV6_TCLASS + Net.setSocketOption(fd, family(), opt, value); + } 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); + } + } + } + + @SuppressWarnings("unchecked") + protected T getOption(SocketOption opt) throws IOException { + if (!supportedOptions().contains(opt)) + throw new UnsupportedOperationException("'" + opt + "' not supported"); + synchronized (stateLock) { + ensureOpen(); + if (opt == StandardSocketOptions.IP_TOS) { + return (T) Net.getSocketOption(fd, family(), opt); + } 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); + } + } + } + + 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 { + synchronized (stateLock) { + ensureOpen(); + try { + switch (opt) { + case SO_LINGER: { + // the value is "false" to disable, or linger interval to enable + int i; + if (value instanceof Boolean && ((boolean) value) == false) { + i = -1; + } else { + i = intValue(value, "SO_LINGER"); + } + Net.setSocketOption(fd, StandardSocketOptions.SO_LINGER, i); + break; + } + case SO_TIMEOUT: { + int i = intValue(value, "SO_TIMEOUT"); + if (i < 0) + throw new IllegalArgumentException("timeout < 0"); + timeout = i; + break; + } + case IP_TOS: { + int i = intValue(value, "IP_TOS"); + Net.setSocketOption(fd, family(), StandardSocketOptions.IP_TOS, i); + break; + } + case TCP_NODELAY: { + boolean b = booleanValue(value, "TCP_NODELAY"); + Net.setSocketOption(fd, StandardSocketOptions.TCP_NODELAY, b); + break; + } + case SO_SNDBUF: { + int i = intValue(value, "SO_SNDBUF"); + 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()); + } + } + } + + @Override + public Object getOption(int opt) throws SocketException { + synchronized (stateLock) { + ensureOpen(); + try { + switch (opt) { + case SO_TIMEOUT: + return timeout; + case TCP_NODELAY: + return Net.getSocketOption(fd, StandardSocketOptions.TCP_NODELAY); + case SO_OOBINLINE: + return Net.getSocketOption(fd, ExtendedSocketOption.SO_OOBINLINE); + case SO_LINGER: { + // return "false" when disabled, linger interval when enabled + int i = (int) Net.getSocketOption(fd, StandardSocketOptions.SO_LINGER); + if (i == -1) { + return Boolean.FALSE; + } else { + return i; + } + } + case SO_REUSEADDR: + if (Net.useExclusiveBind()) { + return isReuseAddress; + } else { + return Net.getSocketOption(fd, StandardSocketOptions.SO_REUSEADDR); + } + case SO_BINDADDR: + return Net.localAddress(fd).getAddress(); + case SO_SNDBUF: + return Net.getSocketOption(fd, StandardSocketOptions.SO_SNDBUF); + case SO_RCVBUF: + return Net.getSocketOption(fd, StandardSocketOptions.SO_RCVBUF); + case IP_TOS: + return Net.getSocketOption(fd, family(), StandardSocketOptions.IP_TOS); + case SO_KEEPALIVE: + return Net.getSocketOption(fd, StandardSocketOptions.SO_KEEPALIVE); + case SO_REUSEPORT: + if (!Net.isReusePortAvailable()) + throw new 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()); + } + } + } + + @Override + protected void shutdownInput() throws IOException { + synchronized (stateLock) { + ensureOpenAndConnected(); + if (!isInputClosed) { + Net.shutdown(fd, Net.SHUT_RD); + isInputClosed = true; + } + } + } + + @Override + protected void shutdownOutput() throws IOException { + synchronized (stateLock) { + ensureOpenAndConnected(); + if (!isOutputClosed) { + Net.shutdown(fd, Net.SHUT_WR); + isOutputClosed = true; + } + } + } + + @Override + protected boolean supportsUrgentData() { + return true; + } + + @Override + protected void sendUrgentData(int data) throws IOException { + writeLock.lock(); + try { + int n = 0; + FileDescriptor fd = beginWrite(); + try { + 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 Thread.holdsLock(impl.stateLock); + var closer = new FileDescriptorCloser(impl.fd, impl.stream); + CleanerFactory.cleaner().register(impl, closer); + return closer; + } + + @Override + public void run() { + if (CLOSED.compareAndSet(this, false, true)) { + try { + nd.close(fd); + } catch (IOException ioe) { + throw new UncheckedIOException(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; + } + } +} diff -r c41783eb76eb -r 59567035d279 test/jdk/ProblemList.txt --- a/test/jdk/ProblemList.txt Wed May 29 22:17:48 2019 -0400 +++ b/test/jdk/ProblemList.txt Thu May 30 07:19:19 2019 +0100 @@ -600,6 +600,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 @@ -862,6 +865,7 @@ 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 ############################################################################ diff -r c41783eb76eb -r 59567035d279 test/jdk/com/sun/net/httpserver/Test1.java --- a/test/jdk/com/sun/net/httpserver/Test1.java Wed May 29 22:17:48 2019 -0400 +++ b/test/jdk/com/sun/net/httpserver/Test1.java Thu May 30 07:19:19 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -27,6 +27,7 @@ * @library /test/lib * @build jdk.test.lib.net.SimpleSSLContext * @run main/othervm Test1 + * @run main/othervm -Djdk.net.usePlainSocketImpl Test1 * @run main/othervm -Dsun.net.httpserver.maxReqTime=10 Test1 * @run main/othervm -Dsun.net.httpserver.nodelay=true Test1 * @summary Light weight HTTP server diff -r c41783eb76eb -r 59567035d279 test/jdk/java/net/ServerSocket/AcceptCauseFileDescriptorLeak.java --- a/test/jdk/java/net/ServerSocket/AcceptCauseFileDescriptorLeak.java Wed May 29 22:17:48 2019 -0400 +++ b/test/jdk/java/net/ServerSocket/AcceptCauseFileDescriptorLeak.java Thu May 30 07:19:19 2019 +0100 @@ -38,6 +38,7 @@ * jdk.test.lib.process.* * AcceptCauseFileDescriptorLeak * @run main/othervm AcceptCauseFileDescriptorLeak root + * @run main/othervm -Djdk.net.usePlainSocketImpl AcceptCauseFileDescriptorLeak root */ import java.io.IOException; diff -r c41783eb76eb -r 59567035d279 test/jdk/java/net/ServerSocket/UnreferencedSockets.java --- a/test/jdk/java/net/ServerSocket/UnreferencedSockets.java Wed May 29 22:17:48 2019 -0400 +++ b/test/jdk/java/net/ServerSocket/UnreferencedSockets.java Thu May 30 07:19:19 2019 +0100 @@ -27,6 +27,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 */ diff -r c41783eb76eb -r 59567035d279 test/jdk/java/net/Socket/ConnectionReset.java --- a/test/jdk/java/net/Socket/ConnectionReset.java Wed May 29 22:17:48 2019 -0400 +++ b/test/jdk/java/net/Socket/ConnectionReset.java Thu May 30 07:19:19 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 */ diff -r c41783eb76eb -r 59567035d279 test/jdk/java/net/Socket/Timeouts.java --- a/test/jdk/java/net/Socket/Timeouts.java Wed May 29 22:17:48 2019 -0400 +++ b/test/jdk/java/net/Socket/Timeouts.java Thu May 30 07:19:19 2019 +0100 @@ -23,9 +23,10 @@ /* * @test + * @bug 8221481 * @library /test/lib * @build jdk.test.lib.Utils - * @run testng Timeouts + * @run testng/timeout=180 Timeouts * @summary Test Socket timeouts */ @@ -34,12 +35,17 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; 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.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -54,7 +60,7 @@ * Test timed connect where connection is established */ public void testTimedConnect1() throws IOException { - try (ServerSocket ss = new ServerSocket(0)) { + try (ServerSocket ss = boundServerSocket()) { try (Socket s = new Socket()) { s.connect(ss.getLocalSocketAddress(), 2000); } @@ -77,7 +83,7 @@ * Test connect with a timeout of Integer.MAX_VALUE */ public void testTimedConnect3() throws IOException { - try (ServerSocket ss = new ServerSocket(0)) { + try (ServerSocket ss = boundServerSocket()) { try (Socket s = new Socket()) { s.connect(ss.getLocalSocketAddress(), Integer.MAX_VALUE); } @@ -88,12 +94,10 @@ * Test connect with a negative timeout. */ public void testTimedConnect4() throws IOException { - try (ServerSocket ss = new ServerSocket(0)) { + 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)); } } } @@ -128,10 +132,10 @@ public void testTimedRead3() throws IOException { withConnection((s1, s2) -> { s2.setSoTimeout(2000); - try { - s2.getInputStream().read(); - assertTrue(false); - } catch (SocketTimeoutException expected) { } + long startMillis = millisTime(); + expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read()); + int timeout = s2.getSoTimeout(); + checkDuration(startMillis, timeout-100, timeout+2000); }); } @@ -141,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); @@ -158,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(); @@ -175,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(); @@ -193,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(); @@ -211,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()); }); } @@ -280,7 +269,7 @@ public void testTimedAccept1() throws IOException { Socket s1 = null; Socket s2 = null; - try (ServerSocket ss = new ServerSocket(0)) { + try (ServerSocket ss = boundServerSocket()) { s1 = new Socket(); s1.connect(ss.getLocalSocketAddress()); ss.setSoTimeout(30*1000); @@ -295,7 +284,7 @@ * Test timed accept where a connection is established after a short delay */ public void testTimedAccept2() throws IOException { - try (ServerSocket ss = new ServerSocket(0)) { + try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(30*1000); scheduleConnect(ss.getLocalSocketAddress(), 2000); Socket s = ss.accept(); @@ -307,13 +296,17 @@ * Test timed accept where the accept times out */ public void testTimedAccept3() throws IOException { - try (ServerSocket ss = new ServerSocket(0)) { + try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(2000); + long startMillis = millisTime(); try { Socket s = ss.accept(); s.close(); - assertTrue(false); - } catch (SocketTimeoutException expected) { } + fail(); + } catch (SocketTimeoutException expected) { + int timeout = ss.getSoTimeout(); + checkDuration(startMillis, timeout-100, timeout+2000); + } } } @@ -322,12 +315,12 @@ * previous accept timed out. */ public void testTimedAccept4() throws IOException { - try (ServerSocket ss = new ServerSocket(0)) { + try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(2000); try { Socket s = ss.accept(); s.close(); - assertTrue(false); + fail(); } catch (SocketTimeoutException expected) { } try (Socket s1 = new Socket()) { s1.connect(ss.getLocalSocketAddress()); @@ -342,12 +335,12 @@ * accept timed out */ public void testTimedAccept5() throws IOException { - try (ServerSocket ss = new ServerSocket(0)) { + try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(2000); try { Socket s = ss.accept(); s.close(); - assertTrue(false); + fail(); } catch (SocketTimeoutException expected) { } ss.setSoTimeout(0); try (Socket s1 = new Socket()) { @@ -363,12 +356,12 @@ * accept timed out and after a short delay */ public void testTimedAccept6() throws IOException { - try (ServerSocket ss = new ServerSocket(0)) { + try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(2000); try { Socket s = ss.accept(); s.close(); - assertTrue(false); + fail(); } catch (SocketTimeoutException expected) { } ss.setSoTimeout(0); scheduleConnect(ss.getLocalSocketAddress(), 2000); @@ -381,13 +374,134 @@ * Test async close of a timed accept */ public void testTimedAccept7() throws IOException { - try (ServerSocket ss = new ServerSocket(0)) { + try (ServerSocket ss = boundServerSocket()) { 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) { } + fail(); + } 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 = boundServerSocket()) { + ss.setSoTimeout(2000); + Thread.currentThread().interrupt(); + long startMillis = millisTime(); + try { + Socket s = ss.accept(); + s.close(); + fail(); + } 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 = boundServerSocket()) { + 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(); + fail(); + } 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 = boundServerSocket()) { + ss.setSoTimeout(4000); + + long startMillis = millisTime(); + + Future result1 = pool.submit(ss::accept); + Future 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 = boundServerSocket()) { + ss.setSoTimeout(4000); + + long startMillis = millisTime(); + + Future result1 = pool.submit(ss::accept); + Future 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(); } } @@ -411,6 +525,19 @@ } } + /** + * Returns a ServerSocket bound to a port on the loopback address + */ + static ServerSocket boundServerSocket() throws IOException { + var loopback = InetAddress.getLoopbackAddress(); + ServerSocket ss = new ServerSocket(); + ss.bind(new InetSocketAddress(loopback, 0)); + return ss; + } + + /** + * An operation that accepts two arguments and may throw IOException + */ interface ThrowingBiConsumer { void accept(T t, U u) throws IOException; } @@ -423,7 +550,7 @@ { Socket s1 = null; Socket s2 = null; - try (ServerSocket ss = new ServerSocket(0)) { + try (ServerSocket ss = boundServerSocket()) { s1 = new Socket(); s1.connect(ss.getLocalSocketAddress()); s2 = ss.accept(); @@ -446,6 +573,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 +616,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; + } } diff -r c41783eb76eb -r 59567035d279 test/jdk/java/net/Socket/UdpSocket.java --- a/test/jdk/java/net/Socket/UdpSocket.java Wed May 29 22:17:48 2019 -0400 +++ b/test/jdk/java/net/Socket/UdpSocket.java Thu May 30 07:19:19 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,71 @@ // 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"); + assertEquals(Arrays.copyOf(array1, n), Arrays.copyOf(array2, 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 sockets = new ArrayDeque<>(); + try { + // create the maximum number of sockets + for (int i=0; i(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 void closeAll(Deque sockets) throws IOException { + Socket s; + while ((s = sockets.poll()) != null) { + s.close(); + } + } } diff -r c41783eb76eb -r 59567035d279 test/jdk/java/net/Socket/asyncClose/AsyncClose.java --- a/test/jdk/java/net/Socket/asyncClose/AsyncClose.java Wed May 29 22:17:48 2019 -0400 +++ b/test/jdk/java/net/Socket/asyncClose/AsyncClose.java Thu May 30 07:19:19 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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 @@ -35,6 +35,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 { diff -r c41783eb76eb -r 59567035d279 test/jdk/java/net/SocketImpl/BadUsages.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/net/SocketImpl/BadUsages.java Thu May 30 07:19:19 2019 +0100 @@ -0,0 +1,490 @@ +/* + * 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 + * @compile/module=java.base java/net/PlatformSocketImpl.java + * @run testng/othervm BadUsages + * @summary Test the platform SocketImpl when used in unintended ways + */ + +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(); + var impl = new PlatformSocketImpl(false)) { + var loopback = InetAddress.getLoopbackAddress(); + ss.bind(new InetSocketAddress(loopback, 0)); + impl.create(true); + int port = ss.getLocalPort(); + impl.connect(loopback, port); + expectThrows(IOException.class, () -> impl.connect(loopback, 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(); + var impl = new PlatformSocketImpl(false)) { + var loopback = InetAddress.getLoopbackAddress(); + ss.bind(new InetSocketAddress(loopback, 0)); + impl.create(true); + impl.connect(ss.getLocalSocketAddress(), 0); + 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() { + @Override public String name() { return "birthday"; } + @Override public Class 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() { + @Override public String name() { return "birthday"; } + @Override public Class 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)); + } +} diff -r c41783eb76eb -r 59567035d279 test/jdk/java/net/SocketImpl/CompareSocketOptions.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/net/SocketImpl/CompareSocketOptions.java Thu May 30 07:19:19 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 diff -r c41783eb76eb -r 59567035d279 test/jdk/java/net/SocketImpl/java.base/java/net/PlatformSocketImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/net/SocketImpl/java.base/java/net/PlatformSocketImpl.java Thu May 30 07:19:19 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> supportedOptions() { + return impl.supportedOptions(); + } + + @Override + public void setOption(SocketOption opt, T value) throws IOException { + impl.setOption(opt, value); + } + + @Override + public T getOption(SocketOption 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); + } +} diff -r c41783eb76eb -r 59567035d279 test/jdk/java/net/SocketOption/OptionsTest.java --- a/test/jdk/java/net/SocketOption/OptionsTest.java Wed May 29 22:17:48 2019 -0400 +++ b/test/jdk/java/net/SocketOption/OptionsTest.java Thu May 30 07:19:19 2019 +0100 @@ -38,23 +38,24 @@ public class OptionsTest { - static class Test { - Test(SocketOption option, Object testValue) { + static class Test { + final SocketOption option; + final T value; + Test(SocketOption option, T value) { this.option = option; - this.testValue = testValue; + this.value = value; } - static Test create (SocketOption option, Object testValue) { - return new Test(option, testValue); + static Test create(SocketOption option, T value) { + return new Test(option, value); } - Object option; - Object testValue; + } // The tests set the option using the new API, read back the set value // which could be diferent, and then use the legacy get API to check // these values are the same - static Test[] socketTests = new Test[] { + static Test[] socketTests = new Test[] { Test.create(StandardSocketOptions.SO_KEEPALIVE, Boolean.TRUE), Test.create(StandardSocketOptions.SO_SNDBUF, Integer.valueOf(10 * 100)), Test.create(StandardSocketOptions.SO_RCVBUF, Integer.valueOf(8 * 100)), @@ -66,7 +67,7 @@ Test.create(StandardSocketOptions.IP_TOS, Integer.valueOf(255)) //upper-bound }; - static Test[] serverSocketTests = new Test[] { + static Test[] serverSocketTests = new Test[] { Test.create(StandardSocketOptions.SO_RCVBUF, Integer.valueOf(8 * 100)), Test.create(StandardSocketOptions.SO_REUSEADDR, Boolean.FALSE), Test.create(StandardSocketOptions.SO_REUSEPORT, Boolean.FALSE), @@ -75,7 +76,7 @@ Test.create(StandardSocketOptions.IP_TOS, Integer.valueOf(255)) //upper-bound }; - static Test[] dgSocketTests = new Test[] { + static Test[] datagramSocketTests = new Test[] { Test.create(StandardSocketOptions.SO_SNDBUF, Integer.valueOf(10 * 100)), Test.create(StandardSocketOptions.SO_RCVBUF, Integer.valueOf(8 * 100)), Test.create(StandardSocketOptions.SO_REUSEADDR, Boolean.FALSE), @@ -85,7 +86,7 @@ Test.create(StandardSocketOptions.IP_TOS, Integer.valueOf(255)) //upper-bound }; - static Test[] mcSocketTests = new Test[] { + static Test[] multicastSocketTests = new Test[] { Test.create(StandardSocketOptions.IP_MULTICAST_IF, getNetworkInterface()), Test.create(StandardSocketOptions.IP_MULTICAST_TTL, Integer.valueOf(0)), // lower-bound Test.create(StandardSocketOptions.IP_MULTICAST_TTL, Integer.valueOf(10)), @@ -97,7 +98,7 @@ try { Enumeration nifs = NetworkInterface.getNetworkInterfaces(); while (nifs.hasMoreElements()) { - NetworkInterface ni = (NetworkInterface)nifs.nextElement(); + NetworkInterface ni = nifs.nextElement(); if (ni.supportsMulticast()) { return ni; } @@ -107,99 +108,110 @@ return null; } + static boolean okayToTest(Socket s, SocketOption option) { + if (option == StandardSocketOptions.SO_REUSEPORT) { + // skip SO_REUSEPORT if option is not supported + return s.supportedOptions().contains(StandardSocketOptions.SO_REUSEPORT); + } + if (option == StandardSocketOptions.IP_TOS && s.isConnected()) { + // skip IP_TOS if connected + return false; + } + return true; + } + + static void testEqual(SocketOption option, T value1, T value2) { + if (!value1.equals(value2)) { + throw new RuntimeException("Test of " + option.name() + " failed: " + + value1 + " != " + value2); + } + } + + static void test(Socket s, Test test) throws Exception { + SocketOption option = test.option; + s.setOption(option, test.value); + T value1 = s.getOption(test.option); + T value2 = (T) legacyGetOption(Socket.class, s, test.option); + testEqual(option, value1, value2); + } + + static void test(ServerSocket ss, Test test) throws Exception { + SocketOption option = test.option; + ss.setOption(option, test.value); + T value1 = ss.getOption(test.option); + T value2 = (T) legacyGetOption(ServerSocket.class, ss, test.option); + testEqual(option, value1, value2); + } + + static void test(DatagramSocket ds, Test test) throws Exception { + SocketOption option = test.option; + ds.setOption(option, test.value); + T value1 = ds.getOption(test.option); + T value2 = (T) legacyGetOption(ds.getClass(), ds, test.option); + testEqual(option, value1, value2); + } + + @SuppressWarnings("try") static void doSocketTests() throws Exception { - try ( - ServerSocket srv = new ServerSocket(0, 50, InetAddress.getLoopbackAddress()); - Socket c = new Socket(InetAddress.getLoopbackAddress(), srv.getLocalPort()); - Socket s = srv.accept(); - ) { - Set> options = c.supportedOptions(); - boolean reuseport = options.contains(StandardSocketOptions.SO_REUSEPORT); - for (int i=0; i test : socketTests) { + if (okayToTest(s, test.option)) { + test(s, test); } } } - } - static void doDgSocketTests() throws Exception { - try ( - DatagramSocket c = new DatagramSocket(0); - ) { - Set> options = c.supportedOptions(); - boolean reuseport = options.contains(StandardSocketOptions.SO_REUSEPORT); - for (int i=0; i test : socketTests) { + if (okayToTest(s1, test.option)) { + test(s1, test); + } } } } } } - static void doMcSocketTests() throws Exception { - try ( - MulticastSocket c = new MulticastSocket(0); - ) { - for (int i=0; i> options = ss.supportedOptions(); + boolean reuseport = options.contains(StandardSocketOptions.SO_REUSEPORT); + for (Test test : serverSocketTests) { + if (!(test.option == StandardSocketOptions.SO_REUSEPORT && !reuseport)) { + test(ss, test); } } } } - static void doServerSocketTests() throws Exception { - try ( - ServerSocket c = new ServerSocket(0); - ) { - Set> options = c.supportedOptions(); + static void doDatagramSocketTests() throws Exception { + try (DatagramSocket ds = new DatagramSocket(0)) { + Set> options = ds.supportedOptions(); boolean reuseport = options.contains(StandardSocketOptions.SO_REUSEPORT); - for (int i=0; i test : datagramSocketTests) { if (!(test.option == StandardSocketOptions.SO_REUSEPORT && !reuseport)) { - c.setOption((SocketOption)test.option, test.testValue); - Object getval = c.getOption((SocketOption)test.option); - Object legacyget = legacyGetOption( - ServerSocket.class, c, test.option - ); - if (!getval.equals(legacyget)) { - Formatter f = new Formatter(); - f.format("SS Err %d: %s/%s", i, getval, legacyget); - throw new RuntimeException(f.toString()); - } + test(ds, test); } } } } - static Object legacyGetOption( - Class type, Object s, Object option) + static void doMulticastSocketTests() throws Exception { + try (MulticastSocket ms = new MulticastSocket(0)) { + for (Test test : multicastSocketTests) { + test(ms, test); + } + } + } - throws Exception - { + static Object legacyGetOption(Class type, Object s, Object option) throws Exception { if (type.equals(Socket.class)) { Socket socket = (Socket)s; Set> options = socket.supportedOptions(); @@ -291,8 +303,8 @@ IPSupport.throwSkippedExceptionIfNonOperational(); doSocketTests(); doServerSocketTests(); - doDgSocketTests(); - doMcSocketTests(); + doDatagramSocketTests(); + doMulticastSocketTests(); } // Reflectively access jdk.net.Sockets.getOption so that the test can run diff -r c41783eb76eb -r 59567035d279 test/jdk/java/net/ipv6tests/TcpTest.java --- a/test/jdk/java/net/ipv6tests/TcpTest.java Wed May 29 22:17:48 2019 -0400 +++ b/test/jdk/java/net/ipv6tests/TcpTest.java Thu May 30 07:19:19 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -32,6 +32,7 @@ * @build jdk.test.lib.NetworkConfiguration * jdk.test.lib.Platform * @run main TcpTest -d + * @run main/othervm -Djdk.net.usePlainSocketImpl TcpTest -d */ import java.net.*; diff -r c41783eb76eb -r 59567035d279 test/jdk/sun/security/ssl/SSLSocketImpl/NewSocketMethods.java --- a/test/jdk/sun/security/ssl/SSLSocketImpl/NewSocketMethods.java Wed May 29 22:17:48 2019 -0400 +++ b/test/jdk/sun/security/ssl/SSLSocketImpl/NewSocketMethods.java Thu May 30 07:19:19 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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 @@ -197,14 +197,19 @@ /** * test some new methods of java.net.Socket added to merlin. */ - socket.setTrafficClass(8); - socket.setReuseAddress(true); - System.out.println("Client getTrafficClass(): " - + socket.getTrafficClass()); System.out.println("Client isInputShutdown() " + socket.isInputShutdown()); + socket.setReuseAddress(true); System.out.println("Client getReuseAddress(): " + socket.getReuseAddress()); + + // Solaris does not support set/get of IPV6_TCLASS when connected + if (!"SunOS".equals(System.getProperty("os.name"))) { + socket.setTrafficClass(8); + System.out.println("Client getTrafficClass(): " + + socket.getTrafficClass()); + } + os.write(237); os.flush(); System.out.println("Client read: " + is.read());