2017-02-05 22:22:34 +00:00
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using System;
|
2017-01-31 11:01:08 +00:00
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2017-10-17 08:34:07 +00:00
|
|
|
|
using Tapeti.Flow.FlowHelpers;
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
2017-02-05 22:22:34 +00:00
|
|
|
|
namespace Tapeti.Flow.Default
|
2017-01-31 11:01:08 +00:00
|
|
|
|
{
|
|
|
|
|
public class FlowStore : IFlowStore
|
|
|
|
|
{
|
2017-10-17 08:34:07 +00:00
|
|
|
|
private readonly ConcurrentDictionary<Guid, FlowState> FlowStates = new ConcurrentDictionary<Guid, FlowState>();
|
|
|
|
|
private readonly ConcurrentDictionary<Guid, Guid> ContinuationLookup = new ConcurrentDictionary<Guid, Guid>();
|
|
|
|
|
private readonly LockCollection<Guid> Locks = new LockCollection<Guid>(EqualityComparer<Guid>.Default);
|
2017-02-05 22:22:34 +00:00
|
|
|
|
|
2017-08-14 11:58:01 +00:00
|
|
|
|
private readonly IFlowRepository repository;
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
2017-10-17 08:34:07 +00:00
|
|
|
|
private volatile bool InUse = false;
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
2017-08-14 11:58:01 +00:00
|
|
|
|
public FlowStore(IFlowRepository repository)
|
2017-01-31 11:01:08 +00:00
|
|
|
|
{
|
|
|
|
|
this.repository = repository;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task Load()
|
|
|
|
|
{
|
2017-10-17 08:34:07 +00:00
|
|
|
|
if (InUse)
|
|
|
|
|
throw new InvalidOperationException("Can only load the saved state once.");
|
|
|
|
|
|
|
|
|
|
InUse = true;
|
|
|
|
|
|
2017-02-05 22:22:34 +00:00
|
|
|
|
FlowStates.Clear();
|
|
|
|
|
ContinuationLookup.Clear();
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
2017-08-14 11:58:01 +00:00
|
|
|
|
foreach (var flowStateRecord in await repository.GetStates<FlowState>())
|
2017-01-31 11:01:08 +00:00
|
|
|
|
{
|
2017-07-27 13:55:37 +00:00
|
|
|
|
FlowStates.TryAdd(flowStateRecord.Key, flowStateRecord.Value);
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
2017-07-27 13:55:37 +00:00
|
|
|
|
foreach (var continuation in flowStateRecord.Value.Continuations)
|
|
|
|
|
ContinuationLookup.GetOrAdd(continuation.Key, flowStateRecord.Key);
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-02-05 22:22:34 +00:00
|
|
|
|
public Task<Guid?> FindFlowID(Guid continuationID)
|
2017-01-31 11:01:08 +00:00
|
|
|
|
{
|
|
|
|
|
Guid result;
|
2017-02-05 22:22:34 +00:00
|
|
|
|
return Task.FromResult(ContinuationLookup.TryGetValue(continuationID, out result) ? result : (Guid?)null);
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-02-05 22:22:34 +00:00
|
|
|
|
public async Task<IFlowStateLock> LockFlowState(Guid flowID)
|
2017-01-31 11:01:08 +00:00
|
|
|
|
{
|
2017-10-17 08:34:07 +00:00
|
|
|
|
InUse = true;
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
2017-10-17 08:34:07 +00:00
|
|
|
|
var flowStatelock = new FlowStateLock(this, flowID, await Locks.GetLock(flowID));
|
|
|
|
|
return flowStatelock;
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class FlowStateLock : IFlowStateLock
|
|
|
|
|
{
|
|
|
|
|
private readonly FlowStore owner;
|
|
|
|
|
private readonly Guid flowID;
|
2017-10-17 08:34:07 +00:00
|
|
|
|
private volatile IDisposable flowLock;
|
|
|
|
|
private FlowState flowState;
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
|
|
|
|
|
2017-10-17 08:34:07 +00:00
|
|
|
|
public FlowStateLock(FlowStore owner, Guid flowID, IDisposable flowLock)
|
2017-01-31 11:01:08 +00:00
|
|
|
|
{
|
|
|
|
|
this.owner = owner;
|
|
|
|
|
this.flowID = flowID;
|
2017-10-17 08:34:07 +00:00
|
|
|
|
this.flowLock = flowLock;
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
2017-10-17 08:34:07 +00:00
|
|
|
|
owner.FlowStates.TryGetValue(flowID, out flowState);
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
2017-10-17 08:34:07 +00:00
|
|
|
|
var l = flowLock;
|
|
|
|
|
flowLock = null;
|
|
|
|
|
l?.Dispose();
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-05 22:22:34 +00:00
|
|
|
|
public Guid FlowID => flowID;
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
|
|
|
|
public Task<FlowState> GetFlowState()
|
|
|
|
|
{
|
2017-10-17 08:34:07 +00:00
|
|
|
|
if (flowLock == null)
|
|
|
|
|
throw new ObjectDisposedException("FlowStateLock");
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
2017-10-17 08:34:07 +00:00
|
|
|
|
return Task.FromResult(flowState?.Clone());
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task StoreFlowState(FlowState newFlowState)
|
|
|
|
|
{
|
2017-10-17 08:34:07 +00:00
|
|
|
|
if (flowLock == null)
|
|
|
|
|
throw new ObjectDisposedException("FlowStateLock");
|
|
|
|
|
|
|
|
|
|
// Ensure no one has a direct reference to the protected state in the dictionary
|
|
|
|
|
newFlowState = newFlowState.Clone();
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
2017-10-17 08:34:07 +00:00
|
|
|
|
// Update the lookup dictionary for the ContinuationIDs
|
|
|
|
|
if (flowState != null)
|
|
|
|
|
{
|
2017-02-05 22:22:34 +00:00
|
|
|
|
foreach (var removedContinuation in flowState.Continuations.Keys.Where(k => !newFlowState.Continuations.ContainsKey(k)))
|
2017-01-31 11:01:08 +00:00
|
|
|
|
{
|
|
|
|
|
Guid removedValue;
|
2017-10-17 08:34:07 +00:00
|
|
|
|
owner.ContinuationLookup.TryRemove(removedContinuation, out removedValue);
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
2017-10-17 08:34:07 +00:00
|
|
|
|
}
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
2017-10-17 08:34:07 +00:00
|
|
|
|
foreach (var addedContinuation in newFlowState.Continuations.Where(c => flowState == null || !flowState.Continuations.ContainsKey(c.Key)))
|
|
|
|
|
{
|
|
|
|
|
owner.ContinuationLookup.TryAdd(addedContinuation.Key, flowID);
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
2017-02-05 22:22:34 +00:00
|
|
|
|
|
2017-10-17 08:34:07 +00:00
|
|
|
|
var isNew = flowState == null;
|
|
|
|
|
flowState = newFlowState;
|
|
|
|
|
owner.FlowStates[flowID] = newFlowState;
|
|
|
|
|
|
|
|
|
|
// Storing the flowstate in the underlying repository
|
2017-01-31 11:01:08 +00:00
|
|
|
|
if (isNew)
|
|
|
|
|
{
|
|
|
|
|
var now = DateTime.UtcNow;
|
2017-07-27 13:55:37 +00:00
|
|
|
|
await owner.repository.CreateState(flowID, flowState, now);
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-07-27 13:55:37 +00:00
|
|
|
|
await owner.repository.UpdateState(flowID, flowState);
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task DeleteFlowState()
|
|
|
|
|
{
|
2017-10-17 08:34:07 +00:00
|
|
|
|
if (flowLock == null)
|
|
|
|
|
throw new ObjectDisposedException("FlowStateLock");
|
2017-01-31 11:01:08 +00:00
|
|
|
|
|
2017-10-17 08:34:07 +00:00
|
|
|
|
if (flowState != null)
|
|
|
|
|
{
|
2017-01-31 11:01:08 +00:00
|
|
|
|
foreach (var removedContinuation in flowState.Continuations.Keys)
|
|
|
|
|
{
|
|
|
|
|
Guid removedValue;
|
2017-10-17 08:34:07 +00:00
|
|
|
|
owner.ContinuationLookup.TryRemove(removedContinuation, out removedValue);
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
2017-02-05 22:22:34 +00:00
|
|
|
|
|
2017-01-31 11:01:08 +00:00
|
|
|
|
FlowState removedFlow;
|
2017-10-17 08:34:07 +00:00
|
|
|
|
owner.FlowStates.TryRemove(flowID, out removedFlow);
|
2017-02-05 22:22:34 +00:00
|
|
|
|
|
2017-10-17 08:34:07 +00:00
|
|
|
|
if (flowState != null)
|
|
|
|
|
{
|
|
|
|
|
flowState = null;
|
|
|
|
|
await owner.repository.DeleteState(flowID);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
2017-02-05 22:22:34 +00:00
|
|
|
|
}
|
2017-01-31 11:01:08 +00:00
|
|
|
|
}
|
|
|
|
|
}
|