Refactor subscribe and unsubscribe to support MQTTv5.

This commit is contained in:
Christian Kratky
2019-01-21 21:38:39 +01:00
parent 0d6e244f84
commit ebf0e91ab8
39 changed files with 1183 additions and 476 deletions
@@ -0,0 +1,14 @@
using System;
using MQTTnet.Packets;
namespace MQTTnet.PacketDispatcher
{
public interface IMqttPacketAwaiter : IDisposable
{
void Complete(MqttBasePacket packet);
void Fail(Exception exception);
void Cancel();
}
}
@@ -0,0 +1,59 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Packets;
namespace MQTTnet.PacketDispatcher
{
public sealed class MqttPacketAwaiter<TPacket> : IMqttPacketAwaiter where TPacket : MqttBasePacket
{
private readonly TaskCompletionSource<MqttBasePacket> _taskCompletionSource = new TaskCompletionSource<MqttBasePacket>();
private readonly ushort? _packetIdentifier;
private readonly MqttPacketDispatcher _owningPacketDispatcher;
public MqttPacketAwaiter(ushort? packetIdentifier, MqttPacketDispatcher owningPacketDispatcher)
{
_packetIdentifier = packetIdentifier;
_owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher));
}
public async Task<TPacket> WaitOneAsync(TimeSpan timeout)
{
using (var timeoutToken = new CancellationTokenSource(timeout))
{
timeoutToken.Token.Register(() => _taskCompletionSource.TrySetCanceled());
var packet = await _taskCompletionSource.Task.ConfigureAwait(false);
return (TPacket)packet;
}
}
public void Complete(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
// To prevent deadlocks it is required to call the _TrySetResult_ method
// from a new thread because the awaiting code will not(!) be executed in
// a new thread automatically (due to await). Furthermore _this_ thread will
// do it. But _this_ thread is also reading incoming packets -> deadlock.
Task.Run(() => _taskCompletionSource.TrySetResult(packet));
}
public void Fail(Exception exception)
{
if (exception == null) throw new ArgumentNullException(nameof(exception));
Task.Run(() => _taskCompletionSource.TrySetException(exception));
}
public void Cancel()
{
Task.Run(() => _taskCompletionSource.TrySetCanceled());
}
public void Dispose()
{
_owningPacketDispatcher.RemovePacketAwaiter<TPacket>(_packetIdentifier);
}
}
}
@@ -0,0 +1,105 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using MQTTnet.Packets;
namespace MQTTnet.PacketDispatcher
{
public class MqttPacketDispatcher
{
private readonly ConcurrentDictionary<Tuple<ushort, Type>, IMqttPacketAwaiter> _packetAwaiters = new ConcurrentDictionary<Tuple<ushort, Type>, IMqttPacketAwaiter>();
private BlockingCollection<MqttBasePacket> _inboundPackagesQueue = new BlockingCollection<MqttBasePacket>();
public void Dispatch(Exception exception)
{
foreach (var awaiter in _packetAwaiters)
{
awaiter.Value.Fail(exception);
}
_packetAwaiters.Clear();
}
public void Dispatch(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
ushort identifier = 0;
if (packet is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue)
{
identifier = packetWithIdentifier.PacketIdentifier.Value;
}
var type = packet.GetType();
var key = new Tuple<ushort, Type>(identifier, type);
if (_packetAwaiters.TryRemove(key, out var awaiter))
{
awaiter.Complete(packet);
return;
}
lock (_inboundPackagesQueue)
{
_inboundPackagesQueue.Add(packet);
}
}
public MqttBasePacket Take(CancellationToken cancellationToken)
{
BlockingCollection<MqttBasePacket> inboundPackagesQueue;
lock (_inboundPackagesQueue)
{
inboundPackagesQueue = _inboundPackagesQueue;
}
return inboundPackagesQueue.Take(cancellationToken);
}
public void Reset()
{
foreach (var awaiter in _packetAwaiters)
{
awaiter.Value.Cancel();
}
lock (_inboundPackagesQueue)
{
_inboundPackagesQueue?.Dispose();
_inboundPackagesQueue = new BlockingCollection<MqttBasePacket>();
}
_packetAwaiters.Clear();
}
public MqttPacketAwaiter<TResponsePacket> AddPacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
{
if (!identifier.HasValue)
{
identifier = 0;
}
var awaiter = new MqttPacketAwaiter<TResponsePacket>(identifier, this);
var key = new Tuple<ushort, Type>(identifier.Value, typeof(TResponsePacket));
if (!_packetAwaiters.TryAdd(key, awaiter))
{
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{key.Item2.Name}' with identifier {key.Item1}.");
}
return awaiter;
}
public void RemovePacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
{
if (!identifier.HasValue)
{
identifier = 0;
}
var key = new Tuple<ushort, Type>(identifier.Value, typeof(TResponsePacket));
_packetAwaiters.TryRemove(key, out _);
}
}
}