Fix keep alive ping send timeout disconnect handling (#2253)

Fix keep alive ping send timeout disconnect
This commit is contained in:
Suhas Holla Karkada Chandrashekar
2026-06-21 16:19:45 +02:00
committed by GitHub
parent bef2ec6fba
commit 52c8f00ef8
2 changed files with 120 additions and 3 deletions
@@ -5,8 +5,12 @@
using System.Buffers;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
@@ -454,6 +458,34 @@ public sealed class MqttClient_Tests : BaseTestClass
}
}
[TestMethod]
public async Task KeepAlive_Ping_Send_Timeout_Raises_Disconnected()
{
var adapterFactory = new KeepAliveTimeoutAdapterFactory();
var disconnected = new TaskCompletionSource<MqttClientDisconnectedEventArgs>(TaskCreationOptions.RunContinuationsAsynchronously);
using var client = new MqttClientFactory().CreateMqttClient(adapterFactory);
client.DisconnectedAsync += eventArgs =>
{
disconnected.TrySetResult(eventArgs);
return CompletedTask.Instance;
};
var options = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.WithKeepAlivePeriod(TimeSpan.FromSeconds(1))
.Build();
await client.ConnectAsync(options);
await adapterFactory.Adapter.PingRequestReceived.Task.WaitAsync(TimeSpan.FromSeconds(3));
var disconnectedEventArgs = await disconnected.Task.WaitAsync(TimeSpan.FromSeconds(3));
Assert.IsTrue(disconnectedEventArgs.ClientWasConnected);
Assert.IsInstanceOfType<OperationCanceledException>(disconnectedEventArgs.Exception);
Assert.IsFalse(client.IsConnected);
}
[TestMethod]
public async Task Publish_QoS_1_In_ApplicationMessageReceiveHandler()
{
@@ -975,4 +1007,84 @@ public sealed class MqttClient_Tests : BaseTestClass
Assert.IsTrue(publisher.IsConnected);
Assert.AreEqual(Threads * MessagesPerThread, receivedCount);
}
}
sealed class KeepAliveTimeoutAdapterFactory : IMqttClientAdapterFactory
{
public KeepAliveTimeoutAdapter Adapter { get; private set; }
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
{
Adapter = new KeepAliveTimeoutAdapter(options.ProtocolVersion, options.WriterBufferSize, options.WriterBufferSizeMax);
return Adapter;
}
}
sealed class KeepAliveTimeoutAdapter : IMqttChannelAdapter
{
int _connAckReturned;
public KeepAliveTimeoutAdapter(MqttProtocolVersion protocolVersion, int writerBufferSize, int writerBufferSizeMax)
{
PacketFormatterAdapter = new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(writerBufferSize, writerBufferSizeMax));
}
public long BytesReceived { get; }
public long BytesSent { get; }
public X509Certificate2 ClientCertificate { get; }
public EndPoint RemoteEndPoint { get; } = new DnsEndPoint("localhost", 1883);
public EndPoint LocalEndPoint { get; } = new DnsEndPoint("localhost", 0);
public bool IsSecureConnection { get; }
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public TaskCompletionSource PingRequestReceived { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
public Task ConnectAsync(CancellationToken cancellationToken)
{
return CompletedTask.Instance;
}
public Task DisconnectAsync(CancellationToken cancellationToken)
{
return CompletedTask.Instance;
}
public void Dispose()
{
}
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
{
if (Interlocked.Exchange(ref _connAckReturned, 1) == 0)
{
return new MqttConnAckPacket
{
ReturnCode = MqttConnectReturnCode.ConnectionAccepted
};
}
await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
return null;
}
public void ResetStatistics()
{
}
public Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
{
if (packet is MqttPingReqPacket)
{
PingRequestReceived.TrySetResult();
return Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
}
return CompletedTask.Instance;
}
}
}
+7 -2
View File
@@ -1064,7 +1064,12 @@ public sealed class MqttClient : Disposable, IMqttClient
switch (exception)
{
case OperationCanceledException:
return;
if (cancellationToken.IsCancellationRequested)
{
return;
}
break;
case MqttCommunicationException:
_logger.Warning(exception, "Communication error while sending/receiving keep alive packets");
break;
@@ -1080,4 +1085,4 @@ public sealed class MqttClient : Disposable, IMqttClient
_logger.Verbose("Stopped sending keep alive packets");
}
}
}
}