Files
MQTTnet/Source/MQTTnet/IMqttClient.cs
T
c1b46b0ae7 Client: Tolerate late acknowledgement packets (#2078, #2079) (#2247)
When a publish, subscribe or unsubscribe request was cancelled or
timed out client-side, the corresponding awaitable was removed from
the packet dispatcher. A subsequent PUBACK / PUBCOMP / SUBACK /
UNSUBACK arriving from the broker then fell through to the default
case and raised an MqttProtocolViolationException, which forced a
disconnect. Under high publish load (issue #2079) and from multiple
threads (issue #2078) this manifested as sporadic connection drops.

Match these acknowledgement packets explicitly and log a warning
when no awaiter is registered, instead of treating it as a protocol
violation. The connection is now preserved.

Also documents the thread-safety guarantees of IMqttClient: publish,
subscribe, unsubscribe and ping operations are safe to invoke
concurrently. Connect / disconnect must still be coordinated.

Co-authored-by: meisa <meisa@MAFDEL>
Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2026-06-16 20:28:40 +02:00

49 lines
2.2 KiB
C#

using MQTTnet.Diagnostics.PacketInspection;
namespace MQTTnet;
/// <summary>
/// Represents an MQTT client.
/// <para>
/// Instances of <see cref="IMqttClient" /> are safe for concurrent use: <see cref="PublishAsync" />,
/// <see cref="SubscribeAsync" />, <see cref="UnsubscribeAsync" /> and <see cref="PingAsync" /> may be
/// invoked from multiple threads at the same time. Outgoing packets are serialized internally so
/// that they reach the network in well-formed order, and incoming acknowledgements are routed back
/// to the corresponding caller via the packet identifier.
/// </para>
/// <para>
/// <see cref="ConnectAsync" /> and <see cref="DisconnectAsync" /> must not be called concurrently
/// with each other or with publish/subscribe operations. Coordinate connection lifecycle changes on
/// a single thread.
/// </para>
/// </summary>
public interface IMqttClient : IDisposable
{
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
bool IsConnected { get; }
MqttClientOptions Options { get; }
Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default);
Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default);
Task PingAsync(CancellationToken cancellationToken = default);
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
Task SendEnhancedAuthenticationExchangeDataAsync(MqttEnhancedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default);
}