diff --git a/projects/RabbitMQ.Client/client/api/ShutdownEventArgs.cs b/projects/RabbitMQ.Client/client/api/ShutdownEventArgs.cs index 22919dc0a4..985a989c91 100644 --- a/projects/RabbitMQ.Client/client/api/ShutdownEventArgs.cs +++ b/projects/RabbitMQ.Client/client/api/ShutdownEventArgs.cs @@ -41,11 +41,14 @@ namespace RabbitMQ.Client /// public class ShutdownEventArgs : EventArgs { + private readonly Exception _exception; + /// /// Construct a with the given parameters and /// 0 for and . /// - public ShutdownEventArgs(ShutdownInitiator initiator, ushort replyCode, string replyText, object cause = null) + public ShutdownEventArgs(ShutdownInitiator initiator, ushort replyCode, string replyText, + object cause = null) : this(initiator, replyCode, replyText, 0, 0, cause) { } @@ -64,6 +67,26 @@ public ShutdownEventArgs(ShutdownInitiator initiator, ushort replyCode, string r Cause = cause; } + /// + /// Construct a with the given parameters. + /// + public ShutdownEventArgs(ShutdownInitiator initiator, ushort replyCode, string replyText, Exception exception) + : this(initiator, replyCode, replyText, 0, 0) + { + _exception = exception ?? throw new ArgumentNullException(nameof(exception)); + } + + /// + /// Exception causing the shutdown, or null if none. + /// + public Exception Exception + { + get + { + return _exception; + } + } + /// /// Object causing the shutdown, or null if none. /// @@ -104,7 +127,8 @@ public override string ToString() + (ReplyText != null ? $", text='{ReplyText}'" : string.Empty) + $", classId={ClassId}" + $", methodId={MethodId}" - + (Cause != null ? $", cause={Cause}" : string.Empty); + + (Cause != null ? $", cause={Cause}" : string.Empty) + + (_exception != null ? $", exception={_exception}" : string.Empty); } } } diff --git a/projects/RabbitMQ.Client/client/impl/ChannelBase.cs b/projects/RabbitMQ.Client/client/impl/ChannelBase.cs index 4c9d965fd5..79be88582c 100644 --- a/projects/RabbitMQ.Client/client/impl/ChannelBase.cs +++ b/projects/RabbitMQ.Client/client/impl/ChannelBase.cs @@ -50,6 +50,7 @@ internal abstract class ChannelBase : IChannel, IRecoverable ///Only used to kick-start a connection open ///sequence. See internal TaskCompletionSource m_connectionStartCell; + private Exception m_connectionStartException = null; // AMQP only allows one RPC operation to be active at a time. protected readonly SemaphoreSlim _rpcSemaphore = new SemaphoreSlim(1, 1); @@ -171,6 +172,16 @@ public IBasicConsumer DefaultConsumer public ISession Session { get; private set; } + public Exception ConnectionStartException => m_connectionStartException; + + public void MaybeSetConnectionStartException(Exception ex) + { + if (m_connectionStartCell != null) + { + m_connectionStartException = ex; + } + } + protected void TakeOver(ChannelBase other) { _basicAcksWrapper.Takeover(other._basicAcksWrapper); diff --git a/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs b/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs index d277026d76..172c899a93 100644 --- a/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs +++ b/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs @@ -81,7 +81,8 @@ await _frameHandler.SendProtocolHeaderAsync() if (connectionStart is null) { - throw new IOException("connection.start was never received, likely due to a network timeout"); + const string msg = "connection.start was never received, likely due to a network timeout"; + throw new IOException(msg, _channel0.ConnectionStartException); } ServerProperties = connectionStart.m_serverProperties; diff --git a/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs b/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs index 777750fd9f..282ac29ea3 100644 --- a/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs +++ b/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs @@ -53,16 +53,34 @@ await ReceiveLoop() catch (EndOfStreamException eose) { // Possible heartbeat exception - HandleMainLoopException(new ShutdownEventArgs(ShutdownInitiator.Library, 0, "End of stream", eose)); + var ea = new ShutdownEventArgs(ShutdownInitiator.Library, + 0, "End of stream", + exception: eose); + HandleMainLoopException(ea); } catch (HardProtocolException hpe) { await HardProtocolExceptionHandler(hpe) .ConfigureAwait(false); } + catch (FileLoadException fileLoadException) + { + /* + * https://github.com/rabbitmq/rabbitmq-dotnet-client/issues/1434 + * Ensure that these exceptions eventually make it to application code + */ + var ea = new ShutdownEventArgs(ShutdownInitiator.Library, + Constants.InternalError, fileLoadException.Message, + exception: fileLoadException); + HandleMainLoopException(ea); + } catch (Exception ex) { - HandleMainLoopException(new ShutdownEventArgs(ShutdownInitiator.Library, Constants.InternalError, "Unexpected Exception", ex)); + var ea = new ShutdownEventArgs(ShutdownInitiator.Library, + Constants.InternalError, + $"Unexpected Exception: {ex.Message}", + exception: ex); + HandleMainLoopException(ea); } FinishClose(); @@ -146,15 +164,17 @@ private void HandleMainLoopException(ShutdownEventArgs reason) { if (!SetCloseReason(reason)) { - // TODO reason.Cause could be an Exception, should we use that? - LogCloseError("Unexpected Main Loop Exception while closing: " + reason, new Exception(reason.ToString())); + LogCloseError($"Unexpected Main Loop Exception while closing: {reason}", reason.Exception); return; } + _channel0.MaybeSetConnectionStartException(reason.Exception); + OnShutdown(reason); - LogCloseError($"Unexpected connection closure: {reason}", new Exception(reason.ToString())); + LogCloseError($"Unexpected connection closure: {reason}", reason.Exception); } + // TODO rename Async, add cancellation token? private async Task HardProtocolExceptionHandler(HardProtocolException hpe) { if (SetCloseReason(hpe.ShutdownReason)) diff --git a/projects/Test/Unit/APIApproval.Approve.verified.txt b/projects/Test/Unit/APIApproval.Approve.verified.txt index 1d9773af41..a3d25697c1 100644 --- a/projects/Test/Unit/APIApproval.Approve.verified.txt +++ b/projects/Test/Unit/APIApproval.Approve.verified.txt @@ -787,10 +787,12 @@ namespace RabbitMQ.Client } public class ShutdownEventArgs : System.EventArgs { + public ShutdownEventArgs(RabbitMQ.Client.ShutdownInitiator initiator, ushort replyCode, string replyText, System.Exception exception) { } public ShutdownEventArgs(RabbitMQ.Client.ShutdownInitiator initiator, ushort replyCode, string replyText, object cause = null) { } public ShutdownEventArgs(RabbitMQ.Client.ShutdownInitiator initiator, ushort replyCode, string replyText, ushort classId, ushort methodId, object cause = null) { } public object Cause { get; } public ushort ClassId { get; } + public System.Exception Exception { get; } public RabbitMQ.Client.ShutdownInitiator Initiator { get; } public ushort MethodId { get; } public ushort ReplyCode { get; }