Handle unobserved tasks exceptions (#1871)

* Access exception so that it will not treated as unhandled

* Refactor code

* Improve unit tests

* Fix null reference exception

* Update ReleaseNotes.md
This commit is contained in:
Christian
2023-11-04 13:39:16 +01:00
committed by GitHub
parent 2599b3dfdc
commit ec637f7946
10 changed files with 289 additions and 206 deletions
+23 -7
View File
@@ -26,9 +26,7 @@ namespace MQTTnet.Adapter
readonly IMqttChannel _channel;
readonly byte[] _fixedHeaderBuffer = new byte[2];
readonly MqttNetSourceLogger _logger;
readonly byte[] _singleByteBuffer = new byte[1];
readonly AsyncLock _syncRoot = new AsyncLock();
Statistics _statistics; // mutable struct, don't make readonly!
@@ -47,6 +45,8 @@ namespace MQTTnet.Adapter
_logger = logger.WithSource(nameof(MqttChannelAdapter));
}
public bool AllowPacketFragmentation { get; set; } = true;
public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived);
public long BytesSent => Volatile.Read(ref _statistics._bytesSent);
@@ -63,8 +63,6 @@ namespace MQTTnet.Adapter
public MqttPacketInspector PacketInspector { get; set; }
public bool AllowPacketFragmentation { get; set; } = true;
public async Task ConnectAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -78,12 +76,30 @@ namespace MQTTnet.Adapter
* block forever. Even a cancellation token is not supported properly.
*/
var connectTask = _channel.ConnectAsync(cancellationToken);
var timeout = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => timeout.TrySetResult(null)))
{
var connectTask = Task.Run(
async () =>
{
try
{
await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false);
}
catch
{
// If the timeout is already reached the exception is no longer of interest and
// must be catched. Otherwise it will arrive at the TaskScheduler.UnobservedTaskException.
if (!timeout.Task.IsCompleted)
{
throw;
}
}
},
CancellationToken.None);
await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false);
if (timeout.Task.IsCompleted && !connectTask.IsCompleted)
{
throw new OperationCanceledException("MQTT connect canceled.", cancellationToken);
@@ -204,7 +220,7 @@ namespace MQTTnet.Adapter
PacketInspector?.BeginSendPacket(packetBuffer);
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation)
{
await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false);
+10 -5
View File
@@ -708,6 +708,9 @@ namespace MQTTnet.Client
await eventArgs.AcknowledgeAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (ObjectDisposedException)
{
}
catch (OperationCanceledException)
{
}
@@ -787,7 +790,7 @@ namespace MQTTnet.Client
{
try
{
_logger.Verbose("Start receiving packets.");
_logger.Verbose("Start receiving packets");
while (!cancellationToken.IsCancellationRequested)
{
@@ -825,20 +828,22 @@ namespace MQTTnet.Client
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while receiving packets.");
_logger.Warning(exception, "Communication error while receiving packets");
}
else
{
_logger.Error(exception, "Error while receiving packets.");
_logger.Error(exception, "Error while receiving packets");
}
_packetDispatcher.FailAll(exception);
// The packet dispatcher is set to null when the client is being disposed so it may
// already being gone!
_packetDispatcher?.FailAll(exception);
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped receiving packets.");
_logger.Verbose("Stopped receiving packets");
}
}
@@ -19,7 +19,6 @@ namespace MQTTnet.LowLevelClient
readonly IMqttClientAdapterFactory _clientAdapterFactory;
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
readonly MqttNetSourceLogger _logger;
readonly IMqttNetLogger _rootLogger;
IMqttChannelAdapter _adapter;