|
|
@@ -1,5 +1,7 @@
|
|
|
using System.Text;
|
|
|
+using System.Threading;
|
|
|
using Quadarax.Foundation.Core.Business.Processor.Enums;
|
|
|
+using Quadarax.Foundation.Core.Business.Processor.Exceptons;
|
|
|
using Quadarax.Foundation.Core.Business.Processor.Queue;
|
|
|
using Quadarax.Foundation.Core.Logging;
|
|
|
using Quadarax.Foundation.Core.Object;
|
|
|
@@ -59,6 +61,11 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
|
|
|
/// <inheritdoc cref="ITaskItem.AttemptsLeft"/>
|
|
|
/// </summary>
|
|
|
public int AttemptsLeft { get; private set;}
|
|
|
+ /// <summary>
|
|
|
+ /// <inheritdoc cref="ITaskItem.InitialAttepts"/>
|
|
|
+ /// </summary>
|
|
|
+ public int InitialAttempts { get; private set; }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// <inheritdoc cref="ITaskItem.AttemptTimeout"/>
|
|
|
/// </summary>
|
|
|
@@ -87,7 +94,9 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
|
|
|
private readonly List<ITaskStatusLogItem> _statusLog = new();
|
|
|
private readonly List<TypedArgument> _arguments = new();
|
|
|
private ILog _log;
|
|
|
- private ProcessQueue _parent;
|
|
|
+ private ProcessQueue _parent;
|
|
|
+ private Thread _thread;
|
|
|
+ private CancellationTokenSource _cancellationTokenSource;
|
|
|
#endregion
|
|
|
|
|
|
#region *** Constructors ***
|
|
|
@@ -108,6 +117,7 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
|
|
|
_arguments.Setup(initialArguments);
|
|
|
AttemptTimeout = attemptTimeout;
|
|
|
AttemptsLeft = attepts;
|
|
|
+ InitialAttempts = attepts;
|
|
|
ProcessingValidFrom = validFrom;
|
|
|
ProcessingValidTo = validTo;
|
|
|
Created = DateTime.Now;
|
|
|
@@ -120,14 +130,70 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
|
|
|
|
|
|
|
|
|
#region *** Public Operations ***
|
|
|
+ /// <summary>
|
|
|
+ /// Starts task and sets <see cref="Status"/> to <see cref="ProcessStatusEnum.Attached"/> then <see cref="ProcessStatusEnum.Started"/> or
|
|
|
+ /// <see cref="ProcessStatusEnum.ProcessedSucc"/> or <see cref="ProcessStatusEnum.ProcessedFail"/> (target status is depends on input arguments)..
|
|
|
+ /// <list type="bullet">
|
|
|
+ /// <item><description>Allowed state: <see cref="ProcessStatusEnum.Pending"/> or <see cref="ProcessStatusEnum.Paused"/></description></item>
|
|
|
+ /// <item><description>Set state to: <see cref="ProcessStatusEnum.Fa"/> or <see cref="ProcessStatusEnum.ProcessedSucc"/> or <see cref="ProcessStatusEnum.ProcessedFail"/></description></item>
|
|
|
+ /// </list>
|
|
|
+ /// <exception cref="InvalidTaskStateException">When task state is invalid for operation.</exception>
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="reason">Reason message to stop.</param>
|
|
|
+ /// <param name="willPause">If true, then <see cref="Status"/> will be set to <see cref="ProcessStatusEnum.Paused"/> otherwise to <see cref="ProcessStatusEnum.ProcessedSucc"/> or <see cref="ProcessStatusEnum.ProcessedFail"/>.</param>
|
|
|
+ /// <param name="willFail">If true, then <see cref="Status"/> will be set to <see cref="ProcessStatusEnum.ProcessedFail"/> otherwise to <see cref="ProcessStatusEnum.ProcessedSucc"/>.</param>
|
|
|
+ public void Start(IAffinityToken token)
|
|
|
+ {
|
|
|
+ if (_cancellationTokenSource != null) throw new InvalidOperationException("Inconsistent task state. Thread must not be initialized before task start.");
|
|
|
+
|
|
|
+ Reload();
|
|
|
+ CheckTaskState(ProcessStatusEnum.Pending, ProcessStatusEnum.Paused);
|
|
|
+ if (AttemptsLeft == 0)
|
|
|
+ {
|
|
|
+ SetStatus(ProcessStatusEnum.ExpiredAttemts, "(System) Attempts expired");
|
|
|
+ _parent.Provider.Set(this);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ AffinityToken = token;
|
|
|
+ SetStatus(ProcessStatusEnum.Attached, $"Attached to worker {AffinityToken}");
|
|
|
+ _parent.Provider.Set(this);
|
|
|
+
|
|
|
+ _cancellationTokenSource = new CancellationTokenSource();
|
|
|
+ if (AttemptTimeout.HasValue)
|
|
|
+ _cancellationTokenSource.CancelAfter(AttemptTimeout.Value);
|
|
|
+
|
|
|
+ ThreadPool.QueueUserWorkItem(Execute, _cancellationTokenSource);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
public void Postpone(TimeSpan postponeInterval, string reason)
|
|
|
{
|
|
|
throw new NotImplementedException();
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// <inheritdoc cref="ITaskItem.Stop"/>
|
|
|
+ /// </summary>
|
|
|
public void Stop(string reason, bool willPause = true, bool willFail = false)
|
|
|
{
|
|
|
- throw new NotImplementedException();
|
|
|
+ if (_cancellationTokenSource == null) throw new InvalidOperationException("Inconsistent task state. Thread must be initialized before task stop.");
|
|
|
+
|
|
|
+ Reload();
|
|
|
+ CheckTaskState(ProcessStatusEnum.Started);
|
|
|
+ if (AttemptsLeft == 0)
|
|
|
+ {
|
|
|
+ SetStatus(ProcessStatusEnum.ExpiredAttemts, "(System) Attempts expired");
|
|
|
+ _parent.Provider.Set(this);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ _cancellationTokenSource.Cancel();
|
|
|
+ _cancellationTokenSource = null;
|
|
|
+ SetStatus(willPause ? ProcessStatusEnum.Paused : willFail ? ProcessStatusEnum.ProcessedFail : ProcessStatusEnum.ProcessedSucc, reason);
|
|
|
+ StatFinished = DateTime.Now;
|
|
|
+ _parent.Provider.Set(this);
|
|
|
+
|
|
|
}
|
|
|
|
|
|
public void Reset(string reason, TimeSpan postponeValid)
|
|
|
@@ -158,6 +224,22 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
|
|
|
|
|
|
#region *** Internal Operations ***
|
|
|
|
|
|
+ internal void SetStatus(ProcessStatusEnum status, string reason)
|
|
|
+ {
|
|
|
+ if (status == Status) return;
|
|
|
+
|
|
|
+ _statusLog.Add(new TaskStatusLogItem(Status, reason));
|
|
|
+ Status = status;
|
|
|
+ Changed = DateTime.Now;
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void Reload()
|
|
|
+ {
|
|
|
+ if (Id == long.MinValue) throw new InvalidOperationException("Task is not saved yet.");
|
|
|
+ var data = _parent.GetTask(this);
|
|
|
+ CopyFrom(data);
|
|
|
+ }
|
|
|
+
|
|
|
internal void CopyFrom(ITaskItemData data)
|
|
|
{
|
|
|
Id = data.Id;
|
|
|
@@ -174,6 +256,7 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
|
|
|
StatFinished = data.StatFinished;
|
|
|
StatLastAttemptDuration = data.StatLastAttemptDuration;
|
|
|
AttemptsLeft = data.AttemptsLeft;
|
|
|
+ InitialAttempts = data.InitialAttempts;
|
|
|
AttemptTimeout = data.AttemptTimeout;
|
|
|
_statusLog.Clear();
|
|
|
_statusLog.AddRange(data.StatusLog);
|
|
|
@@ -199,6 +282,7 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
|
|
|
StatStarted == other.StatStarted &&
|
|
|
StatFinished == other.StatFinished &&
|
|
|
StatLastAttemptDuration == other.StatLastAttemptDuration &&
|
|
|
+ InitialAttempts == other.InitialAttempts &&
|
|
|
AttemptsLeft == other.AttemptsLeft &&
|
|
|
AttemptTimeout == other.AttemptTimeout &&
|
|
|
_statusLog.SequenceEqual(other.StatusLog) &&
|
|
|
@@ -206,5 +290,38 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
|
|
|
return result;
|
|
|
}
|
|
|
#endregion
|
|
|
+
|
|
|
+ #region *** Private Operations ***
|
|
|
+
|
|
|
+ private void CheckValidity()
|
|
|
+ {
|
|
|
+ var now = DateTime.Now;
|
|
|
+ if (!(ProcessingValidFrom <= now && now < ProcessingValidTo.GetValueOrDefault(DateTime.MaxValue)))
|
|
|
+ throw new TaskValidityExpiredException(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CheckTaskState(params ProcessStatusEnum[] allowedStates)
|
|
|
+ {
|
|
|
+ if (allowedStates == null || allowedStates.Length == 0) throw new ArgumentNullException(nameof(allowedStates));
|
|
|
+
|
|
|
+ CheckValidity();
|
|
|
+
|
|
|
+ if (!allowedStates.Contains(Status))
|
|
|
+ throw new InvalidTaskStateException(this, allowedStates);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Execute(object? state)
|
|
|
+ {
|
|
|
+ if (state==null) throw new ArgumentNullException(nameof(state));
|
|
|
+ var token = (CancellationTokenSource)state;
|
|
|
+
|
|
|
+ SetStatus(ProcessStatusEnum.Started, $"(System) Task started with attempt #{(InitialAttempts - AttemptsLeft) + 1}");
|
|
|
+ StatStarted = DateTime.Now;
|
|
|
+ _parent.Provider.Set(this);
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
}
|
|
|
}
|