JDK-8236076 : DatagramSocket::connect and DatagramSocket::disconnect should allow an implementation to throw UncheckedIOException
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.net
  • Priority: P4
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 15
  • Submitted: 2019-12-17
  • Updated: 2020-01-22
  • Resolved: 2020-01-22
Related Reports
CSR :  
Description
Summary
-------
`DatagramSocket`'s `connect` and `disconnect` methods do not throw an `IOException` while `DatagramChannel`'s corresponding methods do. The potential failure behaviour of the `DatagramSocket`'s methods should be clarified.

Problem
-------

The behaviour of `DatagramSocket` `connect` and `disconnect` methods when an IO error occurs is not clear.  For instance, the two-args `DatagramSocket::connect` method currently might throw an Error, which is undocumented behavior and not user-friendly.

Solution
--------

Update the specification of `DatagramSocket::connect` and `DatagramSocket::disconnect` to document that an implementation may throw `UncheckedIOException`. In addition, and similar to what was done for `DatagramChannel`, an @apiNote will be added to `DatagramSocket::disconnect` to recommend closing the socket if an IO exception occurs, as the underlying socket might have been left in an inconsistent and unspecified state. The two-args `DatagramSocket::connect` implementation used to throw an `Error` if a `SocketException` occurred (for instance, if the socket was not bound and the implicit bind failed). This is undocumented behavior, and this will now be changed to throw the documented `UncheckedIOException` instead. Similarly the `DatagramChannel` socket adapter used to throw an Error if connect (two-args) or disconnect failed, and will now be changed to throw `UncheckedIOException` instead, in conformance to the updated specification.

As it appears that the current omission of `throws IOException` from the declaration of these methods was likely an oversight - one that cannot be easily fixed now since it would be source incompatible - an UncheckedIOException is now being thrown.

Specification
-------------

DatagramSocket::connect

    /**
         * Connects the socket to a remote address for this socket. When a
         * socket is connected to a remote address, packets may only be
         * sent to or received from that address. By default a datagram
    -    * socket is not connected.
    +    * socket is not connected. If the socket is already closed,
    +    * then this method has no effect.
         *
    -    * <p>If the remote destination to which the socket is connected does not
    -    * exist, or is otherwise unreachable, and if an ICMP destination unreachable
    -    * packet has been received for that address, then a subsequent call to
    -    * send or receive may throw a PortUnreachableException. Note, there is no
    -    * guarantee that the exception will be thrown.
    +    * <p> If this socket is not bound then this method will first cause the
    +    * socket to be bound to an address that is assigned automatically,
    +    * as if invoking the {@link #bind bind} method with a parameter of
    +    * {@code null}. If the remote destination to which the socket is connected
    +    * does not exist, or is otherwise unreachable, and if an ICMP destination
    +    * unreachable packet has been received for that address, then a subsequent
    +    * call to send or receive may throw a PortUnreachableException. Note,
    +    * there is no guarantee that the exception will be thrown.
         *
         * ...
         *
         * @param address the remote address for the socket
         *
         * @param port the remote port for the socket.
         *
         * @throws IllegalArgumentException
         *         if the address is null, or the port is out of range.
         *
         * @throws SecurityException
         *         if a security manager has been installed and it does
         *         not permit access to the given remote address
         *
    +    * @throws UncheckedIOException
    +    *         may be thrown if connect fails, for example, if the
    +    *         destination address is non-routable
         *
         * @see #disconnect
    +    * @since 1.2
         */
        public void connect(InetAddress address, int port) {


          * Connects this socket to a remote socket address (IP address + port number).
          *
          * <p> If given an {@link InetSocketAddress InetSocketAddress}, this method
    -     * behaves as if invoking {@link #connect(InetAddress,int) connect(InetAddress,int)}
    -     * with the given socket addresses IP address and port number.
    +     * <p> If given an {@link InetSocketAddress InetSocketAddress}, this method
    +     * behaves as if invoking {@link #connect(InetAddress,int) connect(InetAddress,int)},
    +     * except that {@code SocketException} that may be raised are not wrapped in
    +     * {@code UncheckedIOException}.   
    ...
    public void connect(SocketAddress addr) throws SocketException {

DatagramSocket::disconnect


         /**
          * Disconnects the socket. If the socket is closed or not connected,
          * then this method has no effect.
          *
    +     * @apiNote If this method throws an UncheckedIOException, the socket
    +     *          may be left in an unspecified state. It is strongly
    +     *          recommended that the socket be closed when disconnect
    +     *          fails.
    +     *
    +     * @throws  UncheckedIOException
    +     *          may be thrown if it fails to dissolve the
    +     *          association and restore the socket to a consistent state
    +     *
          * @see #connect
    +     * @since 1.2
          */
         public void disconnect() {
Comments
Updated spec per Daniel's wording and moving to Approved.
22-01-2020

Understood. Maybe something like that then... 533 * <p> If given an {@link InetSocketAddress InetSocketAddress}, this method 534 * behaves as if invoking {@link #connect(InetAddress,int) connect(InetAddress,int)}, 535 * except that {@code SocketException} that may be raised are not wrapped in * {@code UncheckedIOException}.
22-01-2020

Okay on "may result in ..." Basically, I'd like to see the spec here clearly imply that connect(SocketAddress) will throw SocketException rather than UncheckedIOException in cases where connect(InetAddress address, int port) throws UncheckedIOException. Thanks.
22-01-2020

The underlying implementation is DatagramSocketImpl.connect where it is specifies "may be thrown if the socket cannot be connected to the remote destination". So Daniel is correct and we are forced to use "may" here. The long standing behavior (JDK implementation) does not throw, the new implementation throws if the connect fails.
22-01-2020

We have two different implementations of DatagramSocket and they behave slightly differently. One of them will revert to emulating connect in java if the native connect fails at the underlying os level. The other will throw an exception. Hence the wording "may". What exactly may cause connect to fail is both system dependent and implementation dependent.
22-01-2020

Slight revised wording suggested; instead of 533 * <p> If given an {@link InetSocketAddress InetSocketAddress}, this method 534 * behaves as if invoking {@link #connect(InetAddress,int) connect(InetAddress,int)}, 535 * where a failure to connect may result in a {@code SocketException}. recommend 533 * <p> If given an {@link InetSocketAddress InetSocketAddress}, this method 534 * behaves as if invoking {@link #connect(InetAddress,int) connect(InetAddress,int)}, 535 * except that a failure to connect results in a {@code SocketException} rather than an {@code UncheckedIOException}.
22-01-2020

Hi Joe and Alan, I've updated the spec for connect(SocketAddress) in this webrev: http://cr.openjdk.java.net/~pconcannon/8235783/webrevs/webrev.03/ Would that satisfy your concerns? If yes, I can update the CSR
22-01-2020

[~alanb], right; the "this method behaves as if invoking connect(InetAddress, port)" spec is what caught my eye as a possible inconsistency with respect to this change. If the connect���(SocketAddress addr) method has a different exception behave to preserve, I'd prefer to see an update like "this method behaves as if invoking connect(InetAddress, port), with any connection failure resulting in a SocketException..."
21-01-2020

I suspect Joe's comment is about the statement "this method behaves as if invoking connect(InetAddress, port)". If there is further work in this area then it might make sense to move the javadoc from the legacy 2-arg connect method to the newer connect(SocketAddress) method and re-specify the legacy method to call the new method. This doesn't change the `@throws` of course.
21-01-2020

connect���(SocketAddress addr) is expected to throw SocketException in that case. It already has: * @throws SocketException * if the connect fails Is there anything to add?
21-01-2020

If the void connect(InetAddress address, int port) method is getting a throws comment for UncheckedIOException, for completeness, adding a similar throws comment to connect���(SocketAddress addr) seems reasonable as the latter method is partially defined in terms of calling the former.
21-01-2020

Thanks for spotting this, Daniel. I must of forgot to change the specification after the webrev update. I've changed it now.
14-01-2020

I see that the specification above still have "by an implementation" in their throw clause. This has been removed in the later webrev (webrev.02) that is: @throws XXX may be thrown by an implementation if ... is actually - in the final proposal: @throws XXX may be thrown if ... all other things being equals.
14-01-2020

connect(SocketAddress) is already specified to throw SocketException if the connect fails. The old connect/disconnect (missing @since) methods just didn't envisage that the methods could fail.
14-01-2020

I see a release note is already planned for this change. Should the connect���(SocketAddress addr) method also be updated to with a @throws UncheckedIOException entry?
13-01-2020

The webrev for this fix can be found here: http://cr.openjdk.java.net/~pconcannon/8235783/webrevs/webrev.02/
10-01-2020