|
@@ -1,63 +1,112 @@
|
|
|
-using qdr.fnd.core.pqueue.Configuration;
|
|
|
|
|
|
|
+using System.Collections.Concurrent;
|
|
|
|
|
+using qdr.fnd.core.pqueue.Configuration;
|
|
|
using qdr.fnd.core.pqueue.Enums;
|
|
using qdr.fnd.core.pqueue.Enums;
|
|
|
using qdr.fnd.core.pqueue.Exceptions;
|
|
using qdr.fnd.core.pqueue.Exceptions;
|
|
|
using qdr.fnd.core.pqueue.Process;
|
|
using qdr.fnd.core.pqueue.Process;
|
|
|
using qdr.fnd.core.pqueue.Storages;
|
|
using qdr.fnd.core.pqueue.Storages;
|
|
|
using Quadarax.Foundation.Core.Data;
|
|
using Quadarax.Foundation.Core.Data;
|
|
|
using Quadarax.Foundation.Core.Logging;
|
|
using Quadarax.Foundation.Core.Logging;
|
|
|
-using Quadarax.Foundation.Core.Object;
|
|
|
|
|
|
|
+using Quadarax.Foundation.Core.Thread;
|
|
|
using Quadarax.Foundation.Core.Value.Generators;
|
|
using Quadarax.Foundation.Core.Value.Generators;
|
|
|
|
|
|
|
|
namespace qdr.fnd.core.pqueue
|
|
namespace qdr.fnd.core.pqueue
|
|
|
{
|
|
{
|
|
|
- public class PsQueue : DisposableObject
|
|
|
|
|
|
|
+ public class PsQueue : LoopWorker
|
|
|
{
|
|
{
|
|
|
|
|
+ #region *** Constants ***
|
|
|
|
|
+ private const string CS_DEF_QUEUE_NAME = "PSQUEUE";
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
#region *** Properties ***
|
|
#region *** Properties ***
|
|
|
- public string Name { get; }
|
|
|
|
|
- public bool IsRunning { get; private set; }
|
|
|
|
|
public PsQueueConfiguration Configuration { get;}
|
|
public PsQueueConfiguration Configuration { get;}
|
|
|
|
|
|
|
|
public event PsQueueStatusChangedEventHandler? ProcessItemStatusChanged;
|
|
public event PsQueueStatusChangedEventHandler? ProcessItemStatusChanged;
|
|
|
#endregion
|
|
#endregion
|
|
|
|
|
|
|
|
#region *** Private Fields ***
|
|
#region *** Private Fields ***
|
|
|
- private ILog _log;
|
|
|
|
|
private TinyHash _hashGenerator;
|
|
private TinyHash _hashGenerator;
|
|
|
- protected IPsQueueStorage? _storage;
|
|
|
|
|
|
|
+ protected IPsQueueStorage _storage;
|
|
|
|
|
+ private List<Tuple<PsStatusEnum,PsStatusEnum[]>> _statusTransitions = new List<Tuple<PsStatusEnum, PsStatusEnum[]>>();
|
|
|
|
|
+
|
|
|
|
|
+ private ConcurrentDictionary<string, IPsProcessProvider> _workingItems = new ConcurrentDictionary<string, IPsProcessProvider>();
|
|
|
#endregion
|
|
#endregion
|
|
|
|
|
|
|
|
#region *** Constructors ***
|
|
#region *** Constructors ***
|
|
|
|
|
|
|
|
- public PsQueue(string queueName, PsQueueConfiguration configuration,IPsQueueStorage storageProvider, ILogger logger)
|
|
|
|
|
|
|
+ public PsQueue(string queueName, PsQueueConfiguration configuration,IPsQueueStorage storageProvider, ILogger logger) : base(queueName,null)
|
|
|
{
|
|
{
|
|
|
_hashGenerator = new TinyHash(5);
|
|
_hashGenerator = new TinyHash(5);
|
|
|
if (string.IsNullOrEmpty(queueName))
|
|
if (string.IsNullOrEmpty(queueName))
|
|
|
- queueName = "PSQUEUE" + _hashGenerator.NewId();
|
|
|
|
|
|
|
+ queueName =CS_DEF_QUEUE_NAME + _hashGenerator.NewId();
|
|
|
|
|
|
|
|
- Name = queueName;
|
|
|
|
|
- IsRunning = false;
|
|
|
|
|
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
|
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
|
|
_storage = storageProvider ?? throw new ArgumentNullException(nameof(storageProvider));
|
|
_storage = storageProvider ?? throw new ArgumentNullException(nameof(storageProvider));
|
|
|
- _log = logger.GetLogger(queueName);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ _statusTransitions.Add(new Tuple<PsStatusEnum, PsStatusEnum[]>(PsStatusEnum.New,new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ PsStatusEnum.Pending
|
|
|
|
|
+ }));
|
|
|
|
|
+ _statusTransitions.Add(new Tuple<PsStatusEnum, PsStatusEnum[]>(PsStatusEnum.Pending,new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ PsStatusEnum.Started,
|
|
|
|
|
+ PsStatusEnum.Expired
|
|
|
|
|
+ }));
|
|
|
|
|
+ _statusTransitions.Add(new Tuple<PsStatusEnum, PsStatusEnum[]>(PsStatusEnum.Started,new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ PsStatusEnum.Pending,
|
|
|
|
|
+ PsStatusEnum.Paused,
|
|
|
|
|
+ PsStatusEnum.Stopped,
|
|
|
|
|
+ PsStatusEnum.DoneOk,
|
|
|
|
|
+ PsStatusEnum.DoneFailed,
|
|
|
|
|
+ PsStatusEnum.Expired
|
|
|
|
|
+ }));
|
|
|
|
|
+ _statusTransitions.Add(new Tuple<PsStatusEnum, PsStatusEnum[]>(PsStatusEnum.Paused,new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ PsStatusEnum.Started,
|
|
|
|
|
+ PsStatusEnum.Stopped,
|
|
|
|
|
+ PsStatusEnum.Expired
|
|
|
|
|
+ }));
|
|
|
|
|
+ _statusTransitions.Add(new Tuple<PsStatusEnum, PsStatusEnum[]>(PsStatusEnum.Stopped,new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ PsStatusEnum.DoneFailed,
|
|
|
|
|
+ PsStatusEnum.Expired
|
|
|
|
|
+ }));
|
|
|
|
|
+ _statusTransitions.Add(new Tuple<PsStatusEnum, PsStatusEnum[]>(PsStatusEnum.DoneOk,new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ PsStatusEnum.Deleted
|
|
|
|
|
+ }));
|
|
|
|
|
+ _statusTransitions.Add(new Tuple<PsStatusEnum, PsStatusEnum[]>(PsStatusEnum.DoneFailed,new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ PsStatusEnum.Deleted
|
|
|
|
|
+ }));
|
|
|
|
|
+ _statusTransitions.Add(new Tuple<PsStatusEnum, PsStatusEnum[]>(PsStatusEnum.Expired,new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ PsStatusEnum.Deleted
|
|
|
|
|
+ }));
|
|
|
|
|
+ _statusTransitions.Add(new Tuple<PsStatusEnum, PsStatusEnum[]>(PsStatusEnum.Deleted,Array.Empty<PsStatusEnum>()));
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
#endregion
|
|
|
|
|
|
|
|
#region *** Public Operations ***
|
|
#region *** Public Operations ***
|
|
|
- public void Start()
|
|
|
|
|
- {
|
|
|
|
|
- if (IsRunning) throw new PSQueueException(PSQueueException.ErrorCodes.PsQueueIsRunning);
|
|
|
|
|
- _log.Log(LogSeverityEnum.Info, $"{Name}:queue started.");
|
|
|
|
|
- }
|
|
|
|
|
- public void Stop()
|
|
|
|
|
- {
|
|
|
|
|
- if (!IsRunning) throw new PSQueueException(PSQueueException.ErrorCodes.PsQueueIsRunning);
|
|
|
|
|
- _log.Log(LogSeverityEnum.Info, $"{Name}:queue stopped.");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
public string EnQueue(IPsQueueItemDescriptor item)
|
|
public string EnQueue(IPsQueueItemDescriptor item)
|
|
|
{
|
|
{
|
|
|
- throw new NotImplementedException();
|
|
|
|
|
|
|
+ if (item is null)
|
|
|
|
|
+ throw new ArgumentNullException(nameof(item));
|
|
|
|
|
+ ValidateItem(item);
|
|
|
|
|
+
|
|
|
|
|
+ var itemId = _storage.CreateItem(item.Owner, item.OwnerRef, item.ProcessProviderClass, item.AtteptsInitial, null);
|
|
|
|
|
+ foreach (var argument in item.Arguments)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (argument.ValueType.FullName == null) throw new InvalidOperationException($"Cannot obtain fullName of type '{argument.ValueType.Name}'");
|
|
|
|
|
+
|
|
|
|
|
+ _storage.CreateItemAttribute(itemId, argument.Name, argument.Value, argument.ValueType.FullName, argument.IsOutput);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Log(LogSeverityEnum.Info,$"{Name} - Item [{itemId}] was enqueued as {item}");
|
|
|
|
|
+ return itemId;
|
|
|
}
|
|
}
|
|
|
public bool Dequeue(string itemId, bool force)
|
|
public bool Dequeue(string itemId, bool force)
|
|
|
{
|
|
{
|
|
@@ -97,13 +146,83 @@ namespace qdr.fnd.core.pqueue
|
|
|
|
|
|
|
|
|
|
|
|
|
#region *** Private Operations ***
|
|
#region *** Private Operations ***
|
|
|
|
|
+
|
|
|
|
|
+ #region Overrides of LoopWorker
|
|
|
|
|
+
|
|
|
|
|
+ /// <inheritdoc />
|
|
|
|
|
+ protected override Task DoIteration(IWorkerContext? context)
|
|
|
|
|
+ {
|
|
|
|
|
+ var workersLeft = Configuration.MaxConcurrentProcesses - _workingItems.Count;
|
|
|
|
|
+ if (workersLeft>0)
|
|
|
|
|
+ {
|
|
|
|
|
+ var itemsToProcess = Query(null, null,
|
|
|
|
|
+ new Tuple<bool, PsStatusEnum>[] { new(false, PsStatusEnum.Pending) })
|
|
|
|
|
+ .Where(x => x.ValidFrom > DateTime.Now && (x.ValidTo == null || x.ValidTo < DateTime.Now)).Take(workersLeft);
|
|
|
|
|
+
|
|
|
|
|
+ foreach(var item in itemsToProcess){
|
|
|
|
|
+ // set to Pending Transient to Started
|
|
|
|
|
+ _storage.UpdateItemStatus(item.Id, null, true);
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return base.DoIteration(context);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ private void ValidateItem(IPsQueueItemDescriptor item)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (item is null)
|
|
|
|
|
+ throw new ArgumentNullException(nameof(item));
|
|
|
|
|
+
|
|
|
|
|
+ if (string.IsNullOrEmpty(item.Owner))
|
|
|
|
|
+ throw new PSQueueValidationException(PSQueueValidationException.ErrorCodes.OwnerRequired);
|
|
|
|
|
+
|
|
|
|
|
+ if (string.IsNullOrEmpty(item.OwnerRef))
|
|
|
|
|
+ throw new PSQueueValidationException(PSQueueValidationException.ErrorCodes.OwnerRefRequired);
|
|
|
|
|
+
|
|
|
|
|
+ var provider = ResolveProvider(item.ProcessProviderClass, item.Arguments);
|
|
|
|
|
+ provider.Validate();
|
|
|
|
|
+
|
|
|
|
|
+ if (item.AtteptsInitial <= 0);
|
|
|
|
|
+ throw new PSQueueValidationException(PSQueueValidationException.ErrorCodes.InitialAttemptMustBePositive);
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void SetItemStatus(IPsQueueItem item, PsStatusEnum? newStatus, bool isTransient, string message)
|
|
|
|
|
+ {
|
|
|
|
|
+ var oldStatus = item.Status;
|
|
|
|
|
+ var oldIsTransient = item.IsStatusTransient;
|
|
|
|
|
+
|
|
|
|
|
+ if (!isTransient && newStatus == null)
|
|
|
|
|
+ throw new PSQueueValidationException(PSQueueValidationException.ErrorCodes.InvalidStatusTransientCombination);
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private IPsProcessProvider ResolveProvider(string providerClass, IEnumerable<ITypedArgument> arguments)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (string.IsNullOrEmpty(providerClass))
|
|
|
|
|
+ throw new PSQueueValidationException(PSQueueValidationException.ErrorCodes.ProcessProviderRequired);
|
|
|
|
|
+
|
|
|
|
|
+ var providerType = Type.GetType(providerClass);
|
|
|
|
|
+ if (providerType == null)
|
|
|
|
|
+ throw new PSQueueValidationException(PSQueueValidationException.ErrorCodes.ProcessProviderCannotResolve, providerClass);
|
|
|
|
|
+
|
|
|
|
|
+ var provider = Activator.CreateInstance(providerType, arguments) as IPsProcessProvider;
|
|
|
|
|
+ if (provider == null)
|
|
|
|
|
+ throw new PSQueueValidationException(PSQueueValidationException.ErrorCodes.ProcessProviderCannotResolve, providerClass);
|
|
|
|
|
+
|
|
|
|
|
+ return provider;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
protected override void OnDisposing()
|
|
protected override void OnDisposing()
|
|
|
{
|
|
{
|
|
|
- if(IsRunning)
|
|
|
|
|
- Stop();
|
|
|
|
|
|
|
+ base.OnDisposing();
|
|
|
|
|
|
|
|
ProcessItemStatusChanged = null;
|
|
ProcessItemStatusChanged = null;
|
|
|
- _storage = null;
|
|
|
|
|
|
|
+ _storage.Dispose();
|
|
|
//TODO: Implement OnDisposing
|
|
//TODO: Implement OnDisposing
|
|
|
}
|
|
}
|
|
|
#endregion
|
|
#endregion
|