Fixed #11: Do not persist flow for dynamic queues
This commit is contained in:
parent
23f86b3597
commit
c63b821b87
@ -86,7 +86,7 @@ namespace Tapeti.Flow.Default
|
|||||||
var flowHandler = context.Config.DependencyResolver.Resolve<IFlowHandler>();
|
var flowHandler = context.Config.DependencyResolver.Resolve<IFlowHandler>();
|
||||||
return flowHandler.Execute(new FlowHandlerContext(context), new DelegateYieldPoint(async flowContext =>
|
return flowHandler.Execute(new FlowHandlerContext(context), new DelegateYieldPoint(async flowContext =>
|
||||||
{
|
{
|
||||||
await flowContext.Store();
|
await flowContext.Store(context.Binding.QueueType == QueueType.Durable);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ namespace Tapeti.Flow.Default
|
|||||||
private bool deleteCalled;
|
private bool deleteCalled;
|
||||||
|
|
||||||
|
|
||||||
public async Task Store()
|
public async Task Store(bool persistent)
|
||||||
{
|
{
|
||||||
storeCalled = true;
|
storeCalled = true;
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ namespace Tapeti.Flow.Default
|
|||||||
if (FlowStateLock == null) throw new ArgumentNullException(nameof(FlowStateLock));
|
if (FlowStateLock == null) throw new ArgumentNullException(nameof(FlowStateLock));
|
||||||
|
|
||||||
FlowState.Data = Newtonsoft.Json.JsonConvert.SerializeObject(HandlerContext.Controller);
|
FlowState.Data = Newtonsoft.Json.JsonConvert.SerializeObject(HandlerContext.Controller);
|
||||||
await FlowStateLock.StoreFlowState(FlowState);
|
await FlowStateLock.StoreFlowState(FlowState, persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Delete()
|
public async Task Delete()
|
||||||
|
@ -88,7 +88,7 @@ namespace Tapeti.Flow.Default
|
|||||||
ReplyTo = responseHandlerInfo.ReplyToQueue
|
ReplyTo = responseHandlerInfo.ReplyToQueue
|
||||||
};
|
};
|
||||||
|
|
||||||
await context.Store();
|
await context.Store(responseHandlerInfo.IsDurableQueue);
|
||||||
|
|
||||||
await publisher.Publish(message, properties, true);
|
await publisher.Publish(message, properties, true);
|
||||||
}
|
}
|
||||||
@ -153,7 +153,8 @@ namespace Tapeti.Flow.Default
|
|||||||
return new ResponseHandlerInfo
|
return new ResponseHandlerInfo
|
||||||
{
|
{
|
||||||
MethodName = MethodSerializer.Serialize(responseHandler.Method),
|
MethodName = MethodSerializer.Serialize(responseHandler.Method),
|
||||||
ReplyToQueue = binding.QueueName
|
ReplyToQueue = binding.QueueName,
|
||||||
|
IsDurableQueue = binding.QueueType == QueueType.Durable
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,6 +332,7 @@ namespace Tapeti.Flow.Default
|
|||||||
{
|
{
|
||||||
public string MethodName { get; set; }
|
public string MethodName { get; set; }
|
||||||
public string ReplyToQueue { get; set; }
|
public string ReplyToQueue { get; set; }
|
||||||
|
public bool IsDurableQueue { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,19 @@ namespace Tapeti.Flow.Default
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class FlowStore : IFlowStore
|
public class FlowStore : IFlowStore
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<Guid, FlowState> flowStates = new ConcurrentDictionary<Guid, FlowState>();
|
private class CachedFlowState
|
||||||
|
{
|
||||||
|
public readonly FlowState FlowState;
|
||||||
|
public readonly bool IsPersistent;
|
||||||
|
|
||||||
|
public CachedFlowState(FlowState flowState, bool isPersistent)
|
||||||
|
{
|
||||||
|
FlowState = flowState;
|
||||||
|
IsPersistent = isPersistent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<Guid, CachedFlowState> flowStates = new ConcurrentDictionary<Guid, CachedFlowState>();
|
||||||
private readonly ConcurrentDictionary<Guid, Guid> continuationLookup = new ConcurrentDictionary<Guid, Guid>();
|
private readonly ConcurrentDictionary<Guid, Guid> continuationLookup = new ConcurrentDictionary<Guid, Guid>();
|
||||||
private readonly LockCollection<Guid> locks = new LockCollection<Guid>(EqualityComparer<Guid>.Default);
|
private readonly LockCollection<Guid> locks = new LockCollection<Guid>(EqualityComparer<Guid>.Default);
|
||||||
|
|
||||||
@ -43,7 +55,7 @@ namespace Tapeti.Flow.Default
|
|||||||
|
|
||||||
foreach (var flowStateRecord in await repository.GetStates<FlowState>())
|
foreach (var flowStateRecord in await repository.GetStates<FlowState>())
|
||||||
{
|
{
|
||||||
flowStates.TryAdd(flowStateRecord.Key, flowStateRecord.Value);
|
flowStates.TryAdd(flowStateRecord.Key, new CachedFlowState(flowStateRecord.Value, true));
|
||||||
|
|
||||||
foreach (var continuation in flowStateRecord.Value.Continuations)
|
foreach (var continuation in flowStateRecord.Value.Continuations)
|
||||||
continuationLookup.GetOrAdd(continuation.Key, flowStateRecord.Key);
|
continuationLookup.GetOrAdd(continuation.Key, flowStateRecord.Key);
|
||||||
@ -80,7 +92,7 @@ namespace Tapeti.Flow.Default
|
|||||||
{
|
{
|
||||||
private readonly FlowStore owner;
|
private readonly FlowStore owner;
|
||||||
private volatile IDisposable flowLock;
|
private volatile IDisposable flowLock;
|
||||||
private FlowState flowState;
|
private CachedFlowState cachedFlowState;
|
||||||
|
|
||||||
public Guid FlowID { get; }
|
public Guid FlowID { get; }
|
||||||
|
|
||||||
@ -91,7 +103,7 @@ namespace Tapeti.Flow.Default
|
|||||||
FlowID = flowID;
|
FlowID = flowID;
|
||||||
this.flowLock = flowLock;
|
this.flowLock = flowLock;
|
||||||
|
|
||||||
owner.flowStates.TryGetValue(flowID, out flowState);
|
owner.flowStates.TryGetValue(flowID, out cachedFlowState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@ -106,10 +118,10 @@ namespace Tapeti.Flow.Default
|
|||||||
if (flowLock == null)
|
if (flowLock == null)
|
||||||
throw new ObjectDisposedException("FlowStateLock");
|
throw new ObjectDisposedException("FlowStateLock");
|
||||||
|
|
||||||
return Task.FromResult(flowState?.Clone());
|
return Task.FromResult(cachedFlowState.FlowState?.Clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StoreFlowState(FlowState newFlowState)
|
public async Task StoreFlowState(FlowState newFlowState, bool persistent)
|
||||||
{
|
{
|
||||||
if (flowLock == null)
|
if (flowLock == null)
|
||||||
throw new ObjectDisposedException("FlowStateLock");
|
throw new ObjectDisposedException("FlowStateLock");
|
||||||
@ -118,30 +130,41 @@ namespace Tapeti.Flow.Default
|
|||||||
newFlowState = newFlowState.Clone();
|
newFlowState = newFlowState.Clone();
|
||||||
|
|
||||||
// Update the lookup dictionary for the ContinuationIDs
|
// Update the lookup dictionary for the ContinuationIDs
|
||||||
if (flowState != null)
|
if (cachedFlowState != null)
|
||||||
{
|
{
|
||||||
foreach (var removedContinuation in flowState.Continuations.Keys.Where(k => !newFlowState.Continuations.ContainsKey(k)))
|
foreach (var removedContinuation in cachedFlowState.FlowState.Continuations.Keys.Where(k => !newFlowState.Continuations.ContainsKey(k)))
|
||||||
owner.continuationLookup.TryRemove(removedContinuation, out _);
|
owner.continuationLookup.TryRemove(removedContinuation, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var addedContinuation in newFlowState.Continuations.Where(c => flowState == null || !flowState.Continuations.ContainsKey(c.Key)))
|
foreach (var addedContinuation in newFlowState.Continuations.Where(c => cachedFlowState == null || !cachedFlowState.FlowState.Continuations.ContainsKey(c.Key)))
|
||||||
{
|
{
|
||||||
owner.continuationLookup.TryAdd(addedContinuation.Key, FlowID);
|
owner.continuationLookup.TryAdd(addedContinuation.Key, FlowID);
|
||||||
}
|
}
|
||||||
|
|
||||||
var isNew = flowState == null;
|
var isNew = cachedFlowState == null;
|
||||||
flowState = newFlowState;
|
var wasPersistent = cachedFlowState?.IsPersistent ?? false;
|
||||||
owner.flowStates[FlowID] = newFlowState;
|
|
||||||
|
|
||||||
// Storing the flowstate in the underlying repository
|
cachedFlowState = new CachedFlowState(newFlowState, persistent);
|
||||||
if (isNew)
|
owner.flowStates[FlowID] = cachedFlowState;
|
||||||
|
|
||||||
|
if (persistent)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
// Storing the flowstate in the underlying repository
|
||||||
await owner.repository.CreateState(FlowID, flowState, now);
|
if (isNew)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
await owner.repository.CreateState(FlowID, cachedFlowState.FlowState, now);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await owner.repository.UpdateState(FlowID, cachedFlowState.FlowState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (wasPersistent)
|
||||||
{
|
{
|
||||||
await owner.repository.UpdateState(FlowID, flowState);
|
// We transitioned from a durable queue to a dynamic queue,
|
||||||
|
// remove the persistent state but keep the in-memory version
|
||||||
|
await owner.repository.DeleteState(FlowID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,18 +173,16 @@ namespace Tapeti.Flow.Default
|
|||||||
if (flowLock == null)
|
if (flowLock == null)
|
||||||
throw new ObjectDisposedException("FlowStateLock");
|
throw new ObjectDisposedException("FlowStateLock");
|
||||||
|
|
||||||
if (flowState != null)
|
if (cachedFlowState != null)
|
||||||
{
|
{
|
||||||
foreach (var removedContinuation in flowState.Continuations.Keys)
|
foreach (var removedContinuation in cachedFlowState.FlowState.Continuations.Keys)
|
||||||
owner.continuationLookup.TryRemove(removedContinuation, out _);
|
owner.continuationLookup.TryRemove(removedContinuation, out _);
|
||||||
|
|
||||||
owner.flowStates.TryRemove(FlowID, out _);
|
owner.flowStates.TryRemove(FlowID, out var removedFlowState);
|
||||||
|
cachedFlowState = null;
|
||||||
|
|
||||||
if (flowState != null)
|
if (removedFlowState.IsPersistent)
|
||||||
{
|
|
||||||
flowState = null;
|
|
||||||
await owner.repository.DeleteState(FlowID);
|
await owner.repository.DeleteState(FlowID);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,8 @@ namespace Tapeti.Flow
|
|||||||
/// Stores the new flow state.
|
/// Stores the new flow state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="flowState"></param>
|
/// <param name="flowState"></param>
|
||||||
Task StoreFlowState(FlowState flowState);
|
/// <param name="persistent"></param>
|
||||||
|
Task StoreFlowState(FlowState flowState, bool persistent);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes of the flow state corresponding to this Flow ID.
|
/// Disposes of the flow state corresponding to this Flow ID.
|
||||||
|
@ -17,6 +17,9 @@ namespace Tapeti.Transient
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string QueueName { get; private set; }
|
public string QueueName { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public QueueType QueueType => QueueType.Dynamic;
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TransientGenericBinding(TransientRouter router, string dynamicQueuePrefix)
|
public TransientGenericBinding(TransientRouter router, string dynamicQueuePrefix)
|
||||||
|
@ -3,6 +3,23 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Tapeti.Config
|
namespace Tapeti.Config
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the type of queue the binding registers
|
||||||
|
/// </summary>
|
||||||
|
public enum QueueType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The consumed queue is durable
|
||||||
|
/// </summary>
|
||||||
|
Durable,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The consumed queue is dynamic
|
||||||
|
/// </summary>
|
||||||
|
Dynamic
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a registered binding to handle incoming messages.
|
/// Represents a registered binding to handle incoming messages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -14,6 +31,12 @@ namespace Tapeti.Config
|
|||||||
string QueueName { get; }
|
string QueueName { get; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the type of queue the binding registers
|
||||||
|
/// </summary>
|
||||||
|
QueueType QueueType { get; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called after a connection is established to set up the binding.
|
/// Called after a connection is established to set up the binding.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -83,6 +83,9 @@ namespace Tapeti.Default
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string QueueName { get; private set; }
|
public string QueueName { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public QueueType QueueType => bindingInfo.QueueInfo.QueueType;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Type Controller => bindingInfo.ControllerType;
|
public Type Controller => bindingInfo.ControllerType;
|
||||||
|
|
||||||
@ -106,7 +109,7 @@ namespace Tapeti.Default
|
|||||||
switch (bindingInfo.BindingTargetMode)
|
switch (bindingInfo.BindingTargetMode)
|
||||||
{
|
{
|
||||||
case BindingTargetMode.Default:
|
case BindingTargetMode.Default:
|
||||||
if (bindingInfo.QueueInfo.Dynamic)
|
if (bindingInfo.QueueInfo.QueueType == QueueType.Dynamic)
|
||||||
QueueName = await target.BindDynamic(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name);
|
QueueName = await target.BindDynamic(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -117,7 +120,7 @@ namespace Tapeti.Default
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case BindingTargetMode.Direct:
|
case BindingTargetMode.Direct:
|
||||||
if (bindingInfo.QueueInfo.Dynamic)
|
if (bindingInfo.QueueInfo.QueueType == QueueType.Dynamic)
|
||||||
QueueName = await target.BindDynamicDirect(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name);
|
QueueName = await target.BindDynamicDirect(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -259,9 +262,9 @@ namespace Tapeti.Default
|
|||||||
public class QueueInfo
|
public class QueueInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the queue is dynamic or durable.
|
/// The type of queue this binding consumes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Dynamic { get; set; }
|
public QueueType QueueType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the durable queue, or optional prefix of the dynamic queue.
|
/// The name of the durable queue, or optional prefix of the dynamic queue.
|
||||||
@ -272,7 +275,7 @@ namespace Tapeti.Default
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if the QueueInfo properties contain a valid combination.
|
/// Determines if the QueueInfo properties contain a valid combination.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsValid => Dynamic|| !string.IsNullOrEmpty(Name);
|
public bool IsValid => QueueType == QueueType.Dynamic || !string.IsNullOrEmpty(Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,10 +130,10 @@ namespace Tapeti
|
|||||||
throw new TopologyConfigurationException($"Cannot combine static and dynamic queue attributes on controller {member.DeclaringType?.Name} method {member.Name}");
|
throw new TopologyConfigurationException($"Cannot combine static and dynamic queue attributes on controller {member.DeclaringType?.Name} method {member.Name}");
|
||||||
|
|
||||||
if (dynamicQueueAttribute != null)
|
if (dynamicQueueAttribute != null)
|
||||||
return new ControllerMethodBinding.QueueInfo { Dynamic = true, Name = dynamicQueueAttribute.Prefix };
|
return new ControllerMethodBinding.QueueInfo { QueueType = QueueType.Dynamic, Name = dynamicQueueAttribute.Prefix };
|
||||||
|
|
||||||
return durableQueueAttribute != null
|
return durableQueueAttribute != null
|
||||||
? new ControllerMethodBinding.QueueInfo { Dynamic = false, Name = durableQueueAttribute.Name }
|
? new ControllerMethodBinding.QueueInfo { QueueType = QueueType.Durable, Name = durableQueueAttribute.Name }
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user