runtime: Exception types for System.Net.Connections

Background and Motivation

We need an exception model for System.Net.Connections implementations, so that users can catch and react to common network events such as a connection reset.

Normally a SocketException would be used, but an abstracted connection may not be using a Socket and so that may not be appropriate. Furthermore it might confuse bugs in the connection’s code versus events parsed from the protocol that the connection would like to communicate.

This proposes an exception that implementations should throw, when appropriate.

Proposed API

The API is a reduction of the existing SocketException and SocketError model.

Option A

We define two new exception types:

  • ConnectionException, with a ConnectionError enum, for exceptions from System.Net.Connections APIs
  • NetworkException, with a NetworkError enum, for exceptions from Stream.Read/Write[Async].

(We don’t love the name NetworkException, but we don’t have a better alternative. This is intended to be thrown from connection-oriented Streams like NetworkStream that would be returned from System.Net.Connections APIs. Note, however, that these exceptions are thrown regardless whether the Stream was created via System.Net.Connections or not – we don’t want different semantics based on how the Stream was created.)

namespace System.Net.Connections
{
    public class ConnectionException : IOException
    {
        public ConnectionException(string message, ConnectionError connectionError = ConnectionError.Unknown, Exception innerException = null);
        // serialization constructor as usual

        public ConnectionError ConnectionError { get; }
    }

    public enum ConnectionError
    {
        Unknown,
        AddressInUse,       // SocketError.AddressAlreadyInUse
        InvalidAddress,     // SocketError.AddressFamilyNotSupported, SocketError.AddressNotAvailable, etc
        ConnectionRefused,  // SocketError.ConnectionRefused
        HostNotFound,       // SocketError.HostNotFound, SocketError.HostUnreachable, etc
    }
}

namespace System.IO
{
    public class NetworkException : IOException
    {
        public NetworkException(string message, NetworkError error, Exception innerException = null);
        // serialization constructor as usual

        public NetworkError NetworkError { get; }
    }

    public enum NetworkError
    {
        Aborted,
        Reset,
    }
}

Option B

We define one new exception type: NetworkException, with NetworkError enum. System.Net.Connections APIs are expected to throw this exception, as are Stream.Read/Write[Async] in the case of a connection abort or reset. The various differentiated conditions from Option A are merged into a single NetworkError enum.

(Again, we don’t love the name; see comments under Option A.)

namespace System.IO
{
    public class NetworkException : IOException
    {
        public NetworkException(string message, NetworkError error, Exception innerException = null);
        // serialization constructor as usual

        public NetworkError NetworkError { get; }
    }

    public enum NetworkError
    {
        Unknown,

        AddressInUse,       // SocketError.AddressAlreadyInUse
        InvalidAddress,     // SocketError.AddressFamilyNotSupported, SocketError.AddressNotAvailable, etc
        ConnectionRefused,  // SocketError.ConnectionRefused
        HostNotFound,       // SocketError.HostNotFound, SocketError.HostUnreachable, etc

        Aborted,
        Reset,
    }
}

Option C

We define no new exception type, but instead add an IOError enum property to IOException. System.Net.Connections APIs are expected to throw IOException with an appropriate IOError value, when applicable. Stream.Read/Write[Async] will continue to throw plain IOException, but set the IOError to the appropriate value in the case of a connection abort or reset.

The set of defined IOError values is currently focused on connection-oriented streams, but could be extended to other types of Streams (e.g. FileStream, MemoryStream) in the future.

namespace System.IO
{
    public class IOException : Exception
    {
        public IOException(string message, IOError error, Exception innerException = null);

        public IOError IOError { get; }
    }

    public enum IOError
    {
        Unknown,

        AddressInUse,       // SocketError.AddressAlreadyInUse
        InvalidAddress,     // SocketError.AddressFamilyNotSupported, SocketError.AddressNotAvailable, etc
        ConnectionRefused,  // SocketError.ConnectionRefused
        HostNotFound,       // SocketError.HostNotFound, SocketError.HostUnreachable, etc

        Aborted,
        Reset,
    }
}

Notes

NetworkStream currently throws IOException and would be updated to throw this.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 34 (34 by maintainers)

Most upvoted comments

We should avoid the need for exception filters and prefer concrete types for those exceptions where we expect users to do more than log/bubble.

Why? The moment you want to do the same thing for multiple values, it’s simpler and cleaner to use a filter.

Let me try to capture the above conversation as well as some offline discussion.

Goals

  1. Allow ASP.NET to detect and handle certain exceptional cases when using System.Net.Connections, in particular:
  • AddressInUse from ConnectionListenerFactory.ListenAsync
  • ConnectionReset from Stream.Read/Write[Async]. This indicates that the peer aborted the connection.
  • ConnectionAborted from Stream.Read/Write[Async]. This indicates that the connection was aborted locally.

Currently, ASP.NET detects these conditions by looking for specific SocketError codes on SocketException. Since System.Net.Connections is a general-purpose abstraction, we need to define a way to surface these conditions independent from SocketException.

  1. Define the expected exceptions from the System.Net.Connections APIs.

Currently, we are often throwing IOException from these APIs (which may be fine), but in some cases we may be throwing domain-specific exceptions (which is not fine). We need to codify what exceptions are expected here.

Option A

We define two new exception types:

  • ConnectionException, with a ConnectionError enum, for exceptions from System.Net.Connections APIs
  • NetworkException, with a NetworkError enum, for exceptions from Stream.Read/Write[Async].

(We don’t love the name NetworkException, but we don’t have a better alternative. This is intended to be thrown from connection-oriented Streams like NetworkStream that would be returned from System.Net.Connections APIs. Note, however, that these exceptions are thrown regardless whether the Stream was created via System.Net.Connections or not – we don’t want different semantics based on how the Stream was created.)

namespace System.Net.Connections
{
    public class ConnectionException : IOException
    {
        public ConnectionException(string message, ConnectionError connectionError = ConnectionError.Unknown, Exception innerException = null);
        // serialization constructor as usual

        public ConnectionError ConnectionError { get; }
    }

    public enum ConnectionError
    {
        Unknown,
        AddressInUse,       // SocketError.AddressAlreadyInUse
        InvalidAddress,     // SocketError.AddressFamilyNotSupported, SocketError.AddressNotAvailable, etc
        ConnectionRefused,  // SocketError.ConnectionRefused
        HostNotFound,       // SocketError.HostNotFound, SocketError.HostUnreachable, etc
    }
}

namespace System.IO
{
    public class NetworkException : IOException
    {
        public NetworkException(string message, NetworkError error, Exception innerException = null);
        // serialization constructor as usual

        public NetworkError NetworkError { get; }
    }

    public enum NetworkError
    {
        Aborted,
        Reset,
    }
}

Option B

We define one new exception type: NetworkException, with NetworkError enum. System.Net.Connections APIs are expected to throw this exception, as are Stream.Read/Write[Async] in the case of a connection abort or reset. The various differentiated conditions from Option A are merged into a single NetworkError enum.

(Again, we don’t love the name; see comments under Option A.)

namespace System.IO
{
    public class NetworkException : IOException
    {
        public NetworkException(string message, NetworkError error, Exception innerException = null);
        // serialization constructor as usual

        public NetworkError NetworkError { get; }
    }

    public enum NetworkError
    {
        Unknown,

        AddressInUse,       // SocketError.AddressAlreadyInUse
        InvalidAddress,     // SocketError.AddressFamilyNotSupported, SocketError.AddressNotAvailable, etc
        ConnectionRefused,  // SocketError.ConnectionRefused
        HostNotFound,       // SocketError.HostNotFound, SocketError.HostUnreachable, etc

        Aborted,
        Reset,
    }
}

Option C

We define no new exception type, but instead add an IOError enum property to IOException. System.Net.Connections APIs are expected to throw IOException with an appropriate IOError value, when applicable. Stream.Read/Write[Async] will continue to throw plain IOException, but set the IOError to the appropriate value in the case of a connection abort or reset.

The set of defined IOError values is currently focused on connection-oriented streams, but could be extended to other types of Streams (e.g. FileStream, MemoryStream) in the future.

namespace System.IO
{
    public class IOException : Exception
    {
        public IOException(string message, IOError error, Exception innerException = null);

        public IOError IOError { get; }
    }

    public enum IOError
    {
        Unknown,

        AddressInUse,       // SocketError.AddressAlreadyInUse
        InvalidAddress,     // SocketError.AddressFamilyNotSupported, SocketError.AddressNotAvailable, etc
        ConnectionRefused,  // SocketError.ConnectionRefused
        HostNotFound,       // SocketError.HostNotFound, SocketError.HostUnreachable, etc

        Aborted,
        Reset,
    }
}

I think this captures all the discussion so far; comments welcome. If there’s general agreement that these are the options worth considering, I’ll mark as ready for review and we can have an API review discussion of this.