mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Observe packet task failures to prevent unobserved overflow exceptions Server dispatch and session enqueue operations create packet bus items without waiting for delivery. When the pending-message queue overflows, Fail faults the item's completion task, which can later raise UnobservedTaskException even if the application handles the dropped-message notification. Observe the completion task's exception inside MqttPacketBusItem.Fail. This keeps the task faulted and preserves the original exception for existing and future WaitAsync callers, including DeliverApplicationMessageAsync. Add a garbage-collection regression test for failures without a waiter and verify that requesting the task before or after failure preserves the original overflow exception. Re-enable unobserved-exception tracking in the existing overflow integration tests for both drop strategies. Before the fix, the focused run failed the new unobserved-exception test and both enqueue overflow tests. After the fix, all nine focused cases and all 489 tests in the .NET 10 Release test suite pass. Fixes https://github.com/dotnet/MQTTnet/issues/2271
106 lines
3.4 KiB
C#
106 lines
3.4 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System.Runtime.CompilerServices;
|
|
using MQTTnet.Internal;
|
|
using MQTTnet.Packets;
|
|
using MQTTnet.Server;
|
|
using MQTTnet.Server.Exceptions;
|
|
|
|
namespace MQTTnet.Tests.Internal;
|
|
|
|
// ReSharper disable InconsistentNaming
|
|
[TestClass]
|
|
public sealed class MqttPacketBusItem_Tests
|
|
{
|
|
[TestMethod]
|
|
public void Fail_Without_Waiter_Does_Not_Raise_Unobserved_Exception()
|
|
{
|
|
var exception = new MqttPendingMessagesOverflowException("test", MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage);
|
|
var unobservedExceptions = 0;
|
|
|
|
void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
|
|
{
|
|
if (args.Exception.Flatten().InnerExceptions.Contains(exception))
|
|
{
|
|
Interlocked.Increment(ref unobservedExceptions);
|
|
args.SetObserved();
|
|
}
|
|
}
|
|
|
|
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
|
try
|
|
{
|
|
var taskReference = CreateFailedPacketBusItem(exception);
|
|
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
GC.Collect();
|
|
|
|
Assert.IsFalse(taskReference.IsAlive, "The faulted task must be collected to exercise unobserved exception reporting.");
|
|
Assert.AreEqual(0, unobservedExceptions);
|
|
}
|
|
finally
|
|
{
|
|
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
[DataRow(false)]
|
|
[DataRow(true)]
|
|
public async Task Wait_Failed_Packet_Bus_Item_Preserves_Exception(bool waitBeforeFailure)
|
|
{
|
|
var item = new MqttPacketBusItem(new MqttPublishPacket());
|
|
var exception = new MqttPendingMessagesOverflowException("test", MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage);
|
|
var task = waitBeforeFailure ? item.WaitAsync() : null;
|
|
|
|
item.Fail(exception);
|
|
|
|
task ??= item.WaitAsync();
|
|
var thrownException = await Assert.ThrowsExactlyAsync<MqttPendingMessagesOverflowException>(async () => await task);
|
|
|
|
Assert.AreSame(exception, thrownException);
|
|
Assert.IsTrue(task.IsFaulted);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Fire_Completed_Event()
|
|
{
|
|
var eventFired = false;
|
|
|
|
var item = new MqttPacketBusItem(new MqttPublishPacket());
|
|
item.Completed += (_, _) =>
|
|
{
|
|
eventFired = true;
|
|
};
|
|
|
|
item.Complete();
|
|
|
|
Assert.IsTrue(eventFired);
|
|
}
|
|
|
|
[TestMethod]
|
|
public Task Wait_Packet_Bus_Item_After_Already_Canceled()
|
|
{
|
|
return Assert.ThrowsExactlyAsync<TaskCanceledException>(async () =>
|
|
{
|
|
var item = new MqttPacketBusItem(new MqttPublishPacket());
|
|
|
|
// Finish the item before the actual
|
|
item.Cancel();
|
|
|
|
await item.WaitAsync();
|
|
});
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
static WeakReference CreateFailedPacketBusItem(Exception exception)
|
|
{
|
|
// Keep strong references out of the collecting method, including in debug builds.
|
|
var item = new MqttPacketBusItem(new MqttPublishPacket());
|
|
item.Fail(exception);
|
|
return new WeakReference(item.WaitAsync());
|
|
}
|
|
} |