Summary
The specification of the Socket::connect()
methods is changed to close the Socket
in the event that the connection cannot be established, the timeout expires before the connection is established, or the socket address is unresolved.
Problem
When a connection cannot be established, the underlying
SocketImpl
closes the socket but theSocket
remains open, meaningSocket::isClosed()
returnsfalse
, even though the underlying socket is closed. This broken behavior dates from JDK 1.4 when support for unconnected sockets was added.If code attempts to connect to an unresolved address then long standing behavior (JDK 1.4 to JDK 12) was to close the underlying
SocketImpl
and socket, leave theSocket
open, and throwUnknownHostException
. So same broken state as (1). In JDK 13, the behavior of this case changed so thatSocketImpl
, socket, andSocket
remain open afterUHE
is thrown. The change in behavior was an oversight as it was never specified.Socket::connect
is specified (and implemented) to throwIOException
if the socket is already connected. However, aSocket
obtained from aSocketChannel
doesn't behave as specified asconnect
method incorrectly throwsAlreadyConnectedException
(anIllegalStateException
) for this corner case.
Solution
Specify that the Socket
is closed when the connect()
fails to establish a connection or the address is unresolved. This is the only sane solution for the connection failed case. The unresolved address case is a user error but it would be incompatible to change Socket::connect()
to throw IAE
after 20+ years. Also, it's highly unlikely that anyone would consider trying to use the Socket
for a different connection, so we decided that closing the Socket
is the sanest thing to do, and mostly aligns with long standing behavior.
Change the SocketChannel
socket adaptor' so that itsconnect
method throws IOException
when the channel's socket is connected.
Specification
In src/java.base/share/classes/java/net/Socket.java
/**
* 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>
* <li> The socket is {@linkplain SocketChannel#socket() associated} with
* a {@link SocketChannel SocketChannel}.
* In that case, interrupting a thread establishing a connection will
* close the underlying channel and cause this method to throw
* {@link ClosedByInterruptException} with the interrupt status set.
* <li> The socket uses the system-default socket implementation and a
* {@linkplain Thread#isVirtual() virtual thread} is establishing a
* connection. In that case, interrupting the virtual thread will
* cause it to wakeup and close the socket. This method will then throw
* {@code SocketException} with the interrupt status set.
* </ol>
*
* @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
* @throws IllegalArgumentException if endpoint is null or is a
* SocketAddress subclass not supported by this socket
* @since 1.4
*/
public void connect(SocketAddress endpoint) throws IOException {
/**
* Connects this socket to the server with a specified timeout value.
* 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>
* <li> The socket is {@linkplain SocketChannel#socket() associated} with
* a {@link SocketChannel SocketChannel}.
* In that case, interrupting a thread establishing a connection will
* close the underlying channel and cause this method to throw
* {@link ClosedByInterruptException} with the interrupt status set.
* <li> The socket uses the system-default socket implementation and a
* {@linkplain Thread#isVirtual() virtual thread} is establishing a
* connection. In that case, interrupting the virtual thread will
* cause it to wakeup and close the socket. This method will then throw
* {@code SocketException} with the interrupt status set.
* </ol>
*
* @param endpoint the {@code SocketAddress}
* @param timeout the timeout value to be used in milliseconds.
* @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
* @throws IllegalArgumentException if endpoint is null or is a
* SocketAddress subclass not supported by this socket, or
* if {@code timeout} is negative
* @since 1.4
*/
public void connect(SocketAddress endpoint, int timeout) throws IOException
- csr of
-
JDK-8343791 Socket.connect API should document whether the socket will be closed when hostname resolution fails or another error occurs
-
- Closed
-