Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8344527

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

XMLWordPrintable

    • Icon: CSR CSR
    • Resolution: Approved
    • Icon: P3 P3
    • 24
    • core-libs
    • None
    • behavioral
    • low
    • Hide
      Code relying on JDK 15+ behavior and catching `UnknownHostException`, then attempting to re-use the `Socket` to connect to a different address will be impacted as the `Socket` will be closed when `connect()` throws `UnknownHostException`. It is highly unlikely anything is dependent on the (previously) undocumented behavior.

      The `connect` method of a `Socket` obtained from a `SocketChannel` will now throw the specified exception when the channel's socket is connected. It's a bug to attempt to connect when already connected and it seems highly unlikely that anything could be dependent on behavior that differs to how `Socket` behaves.
      Show
      Code relying on JDK 15+ behavior and catching `UnknownHostException`, then attempting to re-use the `Socket` to connect to a different address will be impacted as the `Socket` will be closed when `connect()` throws `UnknownHostException`. It is highly unlikely anything is dependent on the (previously) undocumented behavior. The `connect` method of a `Socket` obtained from a `SocketChannel` will now throw the specified exception when the channel's socket is connected. It's a bug to attempt to connect when already connected and it seems highly unlikely that anything could be dependent on behavior that differs to how `Socket` behaves.
    • Java API
    • SE

      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

      1. When a connection cannot be established, the underlying SocketImpl closes the socket but the Socket remains open, meaning Socket::isClosed() returns false, even though the underlying socket is closed. This broken behavior dates from JDK 1.4 when support for unconnected sockets was added.

      2. 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 the Socket open, and throw UnknownHostException. So same broken state as (1). In JDK 13, the behavior of this case changed so that SocketImpl, socket, and Socket remain open after UHE is thrown. The change in behavior was an oversight as it was never specified.

      3. Socket::connect is specified (and implemented) to throw IOException if the socket is already connected. However, a Socket obtained from a SocketChannel doesn't behave as specified as connect method incorrectly throws AlreadyConnectedException (an IllegalStateException) 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

            vyazici Volkan Yazici
            shadowbug Shadow Bug
            Alan Bateman, Daniel Fuchs
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: