dotnet / MQTTnet

MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). The implementation is based on the documentation from http://mqtt.org/.
MIT License
4.47k stars 1.07k forks source link

Client ConnectAsync does not provide sensible exception when mqtt broker is offline #249

Closed dpsenner closed 5 years ago

dpsenner commented 6 years ago

Expected behavior

When the mqtt broker is offline and ConnectAsync is invoked, an exception should be thrown that makes it clear that it cannot connect to the mqtt broker because it is unreachable.

Actual behavior

Interestingly, we observe a wide variety of exceptions and most of them are unrelated to the actual problem. Here's a list of the exceptions that we observed so far, the first being the most sensible of the following list:

MQTTnet.Exceptions.MqttCommunicationTimedOutException: Exception of type 'MQTTnet.Exceptions.MqttCommunicationTimedOutException' was thrown.
   at MQTTnet.Internal.TaskExtensions.<TimeoutAfter>d__1`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttPacketDispatcher.<WaitForPacketAsync>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<SendAndReceiveAsync>d__41`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<AuthenticateAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<ConnectAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at MQTTnet.Client.MqttClient.<ConnectAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Subscriber.SubscriberApplicationContext.<ConnectAsync>d__23.MoveNext() in C:\Workspace\Development\studies\evaluate-mqtt\src\clients\MQTTnet\DotNETCore\Subscriber\SubscriberApplicationContext.cs:line 87
System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<StartReceivingPacketsAsync>d__45.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<ConnectAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at MQTTnet.Client.MqttClient.<ConnectAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Subscriber.SubscriberApplicationContext.<ConnectAsync>d__23.MoveNext() in C:\Workspace\Development\studies\evaluate-mqtt\src\clients\MQTTnet\DotNETCore\Subscriber\SubscriberApplicationContext.cs:line 87
MQTTnet.Exceptions.MqttCommunicationException: The semaphore has been disposed. ---> System.ObjectDisposedException: The semaphore has been disposed.
   at System.Threading.SemaphoreSlim.CheckDispose()
   at System.Threading.SemaphoreSlim.Release(Int32 releaseCount)
   at MQTTnet.Adapter.MqttChannelAdapter.<>c__DisplayClass12_0.<<SendPacketsAsync>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Adapter.MqttChannelAdapter.<ExecuteAndWrapExceptionAsync>d__15.MoveNext()
   --- End of inner exception stack trace ---
   at MQTTnet.Adapter.MqttChannelAdapter.<ExecuteAndWrapExceptionAsync>d__15.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<SendAndReceiveAsync>d__41`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<AuthenticateAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<ConnectAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at MQTTnet.Client.MqttClient.<ConnectAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Subscriber.SubscriberApplicationContext.<ConnectAsync>d__23.MoveNext() in C:\Workspace\Development\studies\evaluate-mqtt\src\clients\MQTTnet\DotNETCore\Subscriber\SubscriberApplicationContext.cs:line 87
System.OperationCanceledException: The operation was canceled.
   at MQTTnet.Adapter.MqttChannelAdapter.<ExecuteAndWrapExceptionAsync>d__15.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<SendAndReceiveAsync>d__41`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<AuthenticateAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<ConnectAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at MQTTnet.Client.MqttClient.<ConnectAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Subscriber.SubscriberApplicationContext.<ConnectAsync>d__23.MoveNext() in C:\Workspace\Development\studies\evaluate-mqtt\src\clients\MQTTnet\DotNETCore\Subscriber\SubscriberApplicationContext.cs:line 87
System.NullReferenceException: Object reference not set to an instance of an object.
   at MQTTnet.Client.MqttClient.SendAsync(MqttBasePacket[] packets)
   at MQTTnet.Client.MqttClient.<SendAndReceiveAsync>d__41`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<AuthenticateAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MQTTnet.Client.MqttClient.<ConnectAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at MQTTnet.Client.MqttClient.<ConnectAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Subscriber.SubscriberApplicationContext.<ConnectAsync>d__23.MoveNext() in C:\Workspace\Development\studies\evaluate-mqtt\src\clients\MQTTnet\DotNETCore\Subscriber\SubscriberApplicationContext.cs:line 87
dpsenner commented 6 years ago

This is the minimal example showing the above exception messages:

public async Task RunAsync(CancellationToken cancellationToken)
{
    var mqttFactory = new MqttFactory();
    using (var mqttClient = mqttFactory.CreateMqttClient())
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            // attempt to connect if not connected
            if (await ConnectAsync(mqttClient))
            {
                // connection succeeded, do nothing for now
               while (!cancellationToken.IsCancellationRequested && mqttClient.IsConnected)
               {
                    await DelayAsync(TimeSpan.FromSeconds(1), cancellationToken);
               }
            }
        }

        // disconnect when cancellation is requested
        await mqttClient.DisconnectAsync();
    }
}

private async Task<bool> ConnectAsync(IMqttClient mqttClient)
{
    try
    {
        var mqttClientOptions = BuildMqttClientOptions();
        var connectResult = await mqttClient.ConnectAsync(mqttClientOptions);
        return true;
    }
    catch (Exception ex)
    {
        // ignore this exception
        Console.WriteLine($"Could not connect to {Server}:{Port}: {ex}");
        return false;
    }
}

private IMqttClientOptions BuildMqttClientOptions()
{
    return new MqttClientOptions()
    {
        ClientId = ClientId,
        ProtocolVersion = MQTTnet.Serializer.MqttProtocolVersion.V311,
        CleanSession = false,
        WillMessage = new MqttApplicationMessageBuilder()
            .WithTopic("NDEATH")
            .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce)
            .WithRetainFlag(true)
            .Build(),
        KeepAlivePeriod = TimeSpan.FromSeconds(15),
        CommunicationTimeout = TimeSpan.FromSeconds(5),
        KeepAliveSendInterval = TimeSpan.FromSeconds(5),
        ChannelOptions = new MqttClientTcpOptions()
        {
            Server = Server,
            Port = Port,
            BufferSize = 4096,
            TlsOptions = new MqttClientTlsOptions()
            {
                AllowUntrustedCertificates = true,
                IgnoreCertificateChainErrors = true,
                IgnoreCertificateRevocationErrors = true,
                UseTls = false,
            }
        },
    };
}
chkr1011 commented 6 years ago

Hi, which version do you use? I fixed this or a similar issue in 2.7.5. Best regards Christian

dpsenner commented 6 years ago

MQTTnet 2.7.5, the latest available on nuget.

dpsenner commented 6 years ago

Interestingly, by configuring the CommunicationTimeout to be 1 second instead of 5 seconds the behavior is completely different and as far as I can tell the exception is always MqttCommunicationTimedOutException.

AtosNicoS commented 6 years ago

I am also seeing this problem with 2.7.5. I am connecting to broker.hivemq.com with most settings set to default. I see the 2nd exception you posted within the Disconnect event. e.Exception contains System.Threading.Tasks.TaskCanceledException: 'A task was canceled.'.

I can reproduce this every time I am running my program for about 10-20 seconds. My client reconnects and will disconnect after a few seconds again. Today I observed that this behavior stops after some time, but I am not 100% sure about this behavior.

This is how I created the MQTTClient:

public Actor(string topic)
{
    Topic = topic;

    // Last will & testament
    lwt = new MqttApplicationMessage()
    {
        Topic = Topic + "status",
        Payload = Encoding.UTF8.GetBytes("disconnect"),
        QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
        Retain = true
    };

    // MQTT client options
    options = new MqttClientOptions()
    {
        ClientId = ClientId, // Guid.NewGuid().ToString();
        WillMessage = lwt,
        ChannelOptions = new MqttClientTcpOptions()
        {
            Server = Broker // "broker.hivemq.com"
        }
    };

    // Create a new MQTT client.
    var factory = new MqttFactory();
    _mqttClient = factory.CreateMqttClient();

    // Message received callback
    _mqttClient.ApplicationMessageReceived += MqttClientOnApplicationMessageReceived;
    _mqttClient.Connected += MqttClientOnConnected;
    _mqttClient.Disconnected += MqttClientOnDisconnected;
}
chkr1011 commented 5 years ago

Please try again with the latest dev branch or a preview version of 3.0.0 and reopen this ticket if it still fails.