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.4k stars 1.05k forks source link

MQTT-over-Websocket connection reconnects every 30 seconds (MAUI regression) #1983

Open janusw opened 4 months ago

janusw commented 4 months ago

Describe the bug

I have a mobile app (previously Xamarin.Forms, now MAUI) which connects to an ActiveMQ Artemis broker via MQTT over (secure) WebSocket, using MQTTNet v4.3.3. With Xamarin.Forms this always worked well, but after switching to MAUI (.NET 7 or 8), I see the following error every 30 seconds, leading to a reconnection:

MQTTnet.Exceptions.MqttCommunicationException: The remote party closed the WebSocket connection without completing the close handshake.
 ---> System.Net.WebSockets.WebSocketException (2): The remote party closed the WebSocket connection without completing the close handshake.
   at System.Net.WebSockets.ManagedWebSocket.ThrowEOFUnexpected()
   at System.Net.WebSockets.ManagedWebSocket.EnsureBufferContainsAsync(Int32 minimumRequiredBytes, CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Net.WebSockets.ManagedWebSocket.<EnsureBufferContainsAsync>d__75, System.Net.WebSockets, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at System.Net.WebSockets.ManagedWebSocket.<ReceiveAsyncPrivate>d__64`1[[System.Net.WebSockets.WebSocketReceiveResult, System.Net.WebSockets, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].MoveNext()
   at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1[[System.Net.WebSockets.WebSocketReceiveResult, System.Net.WebSockets, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a],[System.Net.WebSockets.ManagedWebSocket.<ReceiveAsyncPrivate>d__64`1[[System.Net.WebSockets.WebSocketReceiveResult, System.Net.WebSockets, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]], System.Net.WebSockets, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
   at System.Threading.Tasks.ValueTask`1.ValueTaskSourceAsTask.<>c[[System.Net.WebSockets.WebSocketReceiveResult, System.Net.WebSockets, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].<.cctor>b__4_0(Object state)
--- End of stack trace from previous location ---
   at MQTTnet.Implementations.MqttWebSocketChannel.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
   at MQTTnet.Adapter.MqttChannelAdapter.ReadFixedHeaderAsync(CancellationToken cancellationToken)
   at MQTTnet.Adapter.MqttChannelAdapter.ReceiveAsync(CancellationToken cancellationToken)
   at MQTTnet.Adapter.MqttChannelAdapter.ReceivePacketAsync(CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at MQTTnet.Adapter.MqttChannelAdapter.WrapAndThrowException(Exception exception)
   at MQTTnet.Adapter.MqttChannelAdapter.ReceivePacketAsync(CancellationToken cancellationToken)
   at MQTTnet.Client.MqttClient.Receive(CancellationToken cancellationToken)
   at MQTTnet.Client.MqttClient.ReceivePacketsLoop(CancellationToken cancellationToken)

I have observed such errors when running on Android or iOS. They also occur if I continuously transmit data (either sending or receiving).

Which component is your bug related to?

Code example

I'm basically setting up the client like this:

            options = new ManagedMqttClientOptionsBuilder()
                .WithAutoReconnectDelay(TimeSpan.FromSeconds(10))
                .WithClientOptions(new MqttClientOptionsBuilder()
                    .WithWebSocketServer(o => o.WithUri(url))
                    .WithCredentials(user, token)
                    .WithTlsOptions(o => o.UseTls().WithSslProtocols(SslProtocols.None))
                    .Build())
                .Build();

            client = new MqttFactory().CreateManagedMqttClient();

            await client.StartAsync(options);

Workaround

I found that the problem goes away if I use .WithKeepAliveInterval(TimeSpan.Zero) on the WebSocket. It somehow seems to be related to the WebSocket keepalive interval (which is 30s by default).

janusw commented 4 months ago

This issue is somewhat similar to #1691: The error message is the same, but the reconnection interval and also the workaround is different, so I assume it has a different root cause.

However, in https://github.com/dotnet/MQTTnet/issues/1691#issuecomment-1473677280 @logicaloud reports that he can reproduce the problem, but only the KeepAliveInterval workaround works for him, not the MaxServicePointIdleTime one.

I really wonder what could cause this strange phenomenon, and why it's different between Xamarin/NetStandard and MAUI/.NET8. Maybe some behavior of System.Net.Websockets has changed?

Further ideas, anyone? Anything else I can do to debug this? @chkr1011