8343791: Socket.connect API should document whether the socket will be closed when hostname resolution fails or another error occurs

Reviewed-by: dfuchs, alanb
This commit is contained in:
Volkan Yazıcı 2024-12-03 06:59:06 +00:00 committed by Jaikiran Pai
parent 4ac2e477b9
commit 3eb5461578
5 changed files with 226 additions and 42 deletions

View file

@ -454,6 +454,7 @@ public class Socket implements java.io.Closeable {
throws IOException
{
Objects.requireNonNull(address);
assert address instanceof InetSocketAddress;
// create the SocketImpl and the underlying socket
SocketImpl impl = createImpl();
@ -463,16 +464,13 @@ public class Socket implements java.io.Closeable {
this.state = SOCKET_CREATED;
try {
if (localAddr != null)
if (localAddr != null) {
bind(localAddr);
connect(address);
} catch (IOException | IllegalArgumentException e) {
try {
close();
} catch (IOException ce) {
e.addSuppressed(ce);
}
throw e;
connect(address);
} catch (Throwable throwable) {
closeSuppressingExceptions(throwable);
throw throwable;
}
}
@ -571,6 +569,10 @@ public class Socket implements java.io.Closeable {
/**
* Connects this socket to the server.
*
* <p> If the endpoint is an unresolved {@link InetSocketAddress}, or the
* connection cannot be established, then the socket is closed, and an
* {@link IOException} is thrown.
*
* <p> This method is {@linkplain Thread#interrupt() interruptible} in the
* following circumstances:
* <ol>
@ -589,6 +591,8 @@ public class Socket implements java.io.Closeable {
* @param endpoint the {@code SocketAddress}
* @throws IOException if an error occurs during the connection, the socket
* is already connected or the socket is closed
* @throws UnknownHostException if the endpoint is an unresolved
* {@link InetSocketAddress}
* @throws java.nio.channels.IllegalBlockingModeException
* if this socket has an associated channel,
* and the channel is in non-blocking mode
@ -605,6 +609,11 @@ public class Socket implements java.io.Closeable {
* A timeout of zero is interpreted as an infinite timeout. The connection
* will then block until established or an error occurs.
*
* <p> If the endpoint is an unresolved {@link InetSocketAddress}, the
* connection cannot be established, or the timeout expires before the
* connection is established, then the socket is closed, and an
* {@link IOException} is thrown.
*
* <p> This method is {@linkplain Thread#interrupt() interruptible} in the
* following circumstances:
* <ol>
@ -625,6 +634,8 @@ public class Socket implements java.io.Closeable {
* @throws IOException if an error occurs during the connection, the socket
* is already connected or the socket is closed
* @throws SocketTimeoutException if timeout expires before connecting
* @throws UnknownHostException if the endpoint is an unresolved
* {@link InetSocketAddress}
* @throws java.nio.channels.IllegalBlockingModeException
* if this socket has an associated channel,
* and the channel is in non-blocking mode
@ -644,26 +655,25 @@ public class Socket implements java.io.Closeable {
if (isClosed(s))
throw new SocketException("Socket is closed");
if (isConnected(s))
throw new SocketException("already connected");
throw new SocketException("Already connected");
if (!(endpoint instanceof InetSocketAddress epoint))
throw new IllegalArgumentException("Unsupported address type");
if (epoint.isUnresolved()) {
var uhe = new UnknownHostException(epoint.getHostName());
closeSuppressingExceptions(uhe);
throw uhe;
}
InetAddress addr = epoint.getAddress();
int port = epoint.getPort();
checkAddress(addr, "connect");
try {
getImpl().connect(epoint, timeout);
} catch (SocketTimeoutException e) {
throw e;
} catch (InterruptedIOException e) {
Thread thread = Thread.currentThread();
if (thread.isVirtual() && thread.isInterrupted()) {
close();
throw new SocketException("Closed by interrupt");
}
throw e;
} catch (IOException error) {
closeSuppressingExceptions(error);
throw error;
}
// connect will bind the socket if not previously bound
@ -1589,6 +1599,14 @@ public class Socket implements java.io.Closeable {
return ((Boolean) (getImpl().getOption(SocketOptions.SO_REUSEADDR))).booleanValue();
}
private void closeSuppressingExceptions(Throwable parentException) {
try {
close();
} catch (IOException exception) {
parentException.addSuppressed(exception);
}
}
/**
* Closes this socket.
* <p>

View file

@ -40,6 +40,7 @@ import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.net.UnknownHostException;
import java.nio.channels.AlreadyBoundException;
import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NotYetBoundException;
import java.nio.channels.NotYetConnectedException;
@ -166,6 +167,8 @@ public class Net {
nx = newSocketException("Socket is not connected");
else if (x instanceof AlreadyBoundException)
nx = newSocketException("Already bound");
else if (x instanceof AlreadyConnectedException)
nx = newSocketException("Already connected");
else if (x instanceof NotYetBoundException)
nx = newSocketException("Socket is not bound yet");
else if (x instanceof UnsupportedAddressTypeException)
@ -190,32 +193,12 @@ public class Net {
return new SocketException(msg);
}
static void translateException(Exception x,
boolean unknownHostForUnresolved)
throws IOException
{
static void translateException(Exception x) throws IOException {
if (x instanceof IOException ioe)
throw ioe;
// Throw UnknownHostException from here since it cannot
// be thrown as a SocketException
if (unknownHostForUnresolved &&
(x instanceof UnresolvedAddressException))
{
throw new UnknownHostException();
}
translateToSocketException(x);
}
static void translateException(Exception x)
throws IOException
{
translateException(x, false);
}
private static InetSocketAddress getLoopbackAddress(int port) {
return new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
}
private static final InetAddress ANY_LOCAL_INET4ADDRESS;
private static final InetAddress ANY_LOCAL_INET6ADDRESS;
private static final InetAddress INET4_LOOPBACK_ADDRESS;

View file

@ -599,8 +599,11 @@ public final class NioSocketImpl extends SocketImpl implements PlatformSocketImp
}
} catch (IOException ioe) {
close();
if (ioe instanceof InterruptedIOException) {
if (ioe instanceof SocketTimeoutException) {
throw ioe;
} else if (ioe instanceof InterruptedIOException) {
assert Thread.currentThread().isVirtual();
throw new SocketException("Closed by interrupt");
} else {
throw SocketExceptions.of(ioe, isa);
}

View file

@ -35,6 +35,7 @@ import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketOption;
import java.net.StandardSocketOptions;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel;
import java.util.Set;
@ -85,6 +86,14 @@ class SocketAdaptor
public void connect(SocketAddress remote, int timeout) throws IOException {
if (remote == null)
throw new IllegalArgumentException("connect: The address can't be null");
if (remote instanceof InetSocketAddress isa && isa.isUnresolved()) {
if (!sc.isOpen())
throw new SocketException("Socket is closed");
if (sc.isConnected())
throw new SocketException("Already connected");
close();
throw new UnknownHostException(remote.toString());
}
if (timeout < 0)
throw new IllegalArgumentException("connect: timeout can't be negative");
try {
@ -95,7 +104,7 @@ class SocketAdaptor
sc.blockingConnect(remote, Long.MAX_VALUE);
}
} catch (Exception e) {
Net.translateException(e, true);
Net.translateException(e);
}
}