TaskItem.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. using System.Text;
  2. using System.Threading;
  3. using Quadarax.Foundation.Core.Business.Processor.Enums;
  4. using Quadarax.Foundation.Core.Business.Processor.Exceptons;
  5. using Quadarax.Foundation.Core.Business.Processor.Queue;
  6. using Quadarax.Foundation.Core.Logging;
  7. using Quadarax.Foundation.Core.Object;
  8. using Quadarax.Foundation.Core.Value.Extensions;
  9. namespace Quadarax.Foundation.Core.Business.Processor.Task
  10. {
  11. internal class TaskItem : ITaskItem
  12. {
  13. #region *** Properties ***
  14. /// <summary>
  15. /// <inheritdoc cref="ITaskItem.Id"/>
  16. /// </summary>
  17. public long Id { get; internal set; }
  18. /// <summary>
  19. /// <inheritdoc cref="ITaskItem.QueueType"/>
  20. /// </summary>
  21. public ProcessQueueTypeEnum QueueType { get;private set; }
  22. /// <summary>
  23. /// <inheritdoc cref="ITaskItem.Created"/>
  24. /// </summary>
  25. public DateTime Created { get; private set;}
  26. /// <summary>
  27. /// <inheritdoc cref="ITaskItem.Changed"/>
  28. /// </summary>
  29. public DateTime? Changed { get; private set;}
  30. /// <summary>
  31. /// <inheritdoc cref="ITaskItem.AffinityToken"/>
  32. /// </summary>
  33. public IAffinityToken? AffinityToken { get; private set;}
  34. /// <summary>
  35. /// <inheritdoc cref="ITaskItem.Reference"/>
  36. /// </summary>
  37. public string Reference { get; private set;}
  38. /// <summary>
  39. /// <inheritdoc cref="ITaskItem.Status"/>
  40. /// </summary>
  41. public ProcessStatusEnum Status { get; private set; }
  42. /// <summary>
  43. /// <inheritdoc cref="ITaskItem.ProviderClass"/>
  44. /// </summary>
  45. public string ProviderClass { get; private set;}
  46. /// <summary>
  47. /// <inheritdoc cref="ITaskItem.Priority"/>
  48. /// </summary>
  49. public byte Priority { get; private set;}
  50. /// <summary>
  51. /// <inheritdoc cref="ITaskItem.ProcessingValidFrom"/>
  52. /// </summary>
  53. public DateTime ProcessingValidFrom { get;private set; }
  54. /// <summary>
  55. /// <inheritdoc cref="ITaskItem.ProcessingValidTo"/>
  56. /// </summary>
  57. public DateTime? ProcessingValidTo { get; private set;}
  58. /// <summary>
  59. /// <inheritdoc cref="ITaskItem.AttemptsLeft"/>
  60. /// </summary>
  61. public int AttemptsLeft { get; private set;}
  62. /// <summary>
  63. /// <inheritdoc cref="ITaskItem.InitialAttepts"/>
  64. /// </summary>
  65. public int InitialAttempts { get; private set; }
  66. /// <summary>
  67. /// <inheritdoc cref="ITaskItem.AttemptTimeout"/>
  68. /// </summary>
  69. public TimeSpan? AttemptTimeout { get; private set;}
  70. /// <summary>
  71. /// <inheritdoc cref="ITaskItem.StatStarted"/>
  72. /// </summary>
  73. public DateTime StatStarted { get; private set;}
  74. /// <summary>
  75. /// <inheritdoc cref="ITaskItem.StatFinished"/>
  76. /// </summary>
  77. public DateTime StatFinished { get; private set;}
  78. /// <summary>
  79. /// <inheritdoc cref="ITaskItem.StatLastAttemptDuration"/>
  80. /// </summary>
  81. public TimeSpan StatLastAttemptDuration { get; private set;}
  82. /// <summary>
  83. /// <inheritdoc cref="ITaskItem.StatusLog"/>
  84. /// </summary>
  85. public ITaskStatusLogItem[] StatusLog => _statusLog.ToArray();
  86. public TypedArgument[] Arguments => _arguments.ToArray();
  87. #endregion
  88. #region *** Private Fields ***
  89. private readonly List<ITaskStatusLogItem> _statusLog = new();
  90. private readonly List<TypedArgument> _arguments = new();
  91. private ILog _log;
  92. private ProcessQueue _parent;
  93. private Thread _thread;
  94. private CancellationTokenSource _cancellationTokenSource;
  95. #endregion
  96. #region *** Constructors ***
  97. private TaskItem(ProcessQueue parent, long id, ProcessStatusEnum initialStatus, string reference, string source, TypedArgument[] initialArguments, byte priority, int attepts, TimeSpan? attemptTimeout, DateTime validFrom, DateTime? validTo, ITaskStatusLogItem[]? initialStatusLog):
  98. this(parent, reference, source, initialArguments, priority, attepts, attemptTimeout, validFrom, validTo, initialStatusLog)
  99. {
  100. Id = id;
  101. Status = initialStatus;
  102. }
  103. public TaskItem(ProcessQueue parent, string reference, string providerClass, TypedArgument[] initialArguments, byte priority, int attepts, TimeSpan? attemptTimeout, DateTime validFrom, DateTime? validTo, ITaskStatusLogItem[]? initialStatusLog)
  104. {
  105. AffinityToken = null;
  106. _parent = parent ?? throw new ArgumentNullException(nameof(parent));
  107. _log = new ConsoleLogger().GetLogger(GetType());
  108. Priority = priority;
  109. Reference = reference;
  110. ProviderClass = providerClass;
  111. _arguments.Setup(initialArguments);
  112. AttemptTimeout = attemptTimeout;
  113. AttemptsLeft = attepts;
  114. InitialAttempts = attepts;
  115. ProcessingValidFrom = validFrom;
  116. ProcessingValidTo = validTo;
  117. Created = DateTime.Now;
  118. if (initialStatusLog != null)
  119. _statusLog.Setup(initialStatusLog);
  120. Status = ProcessStatusEnum.Pending;
  121. Id = long.MinValue;
  122. }
  123. #endregion
  124. #region *** Public Operations ***
  125. /// <summary>
  126. /// Starts task and sets <see cref="Status"/> to <see cref="ProcessStatusEnum.Attached"/> then <see cref="ProcessStatusEnum.Started"/> or
  127. /// <see cref="ProcessStatusEnum.ProcessedSucc"/> or <see cref="ProcessStatusEnum.ProcessedFail"/> (target status is depends on input arguments)..
  128. /// <list type="bullet">
  129. /// <item><description>Allowed state: <see cref="ProcessStatusEnum.Pending"/> or <see cref="ProcessStatusEnum.Paused"/></description></item>
  130. /// <item><description>Set state to: <see cref="ProcessStatusEnum.Fa"/> or <see cref="ProcessStatusEnum.ProcessedSucc"/> or <see cref="ProcessStatusEnum.ProcessedFail"/></description></item>
  131. /// </list>
  132. /// <exception cref="InvalidTaskStateException">When task state is invalid for operation.</exception>
  133. /// </summary>
  134. /// <param name="reason">Reason message to stop.</param>
  135. /// <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>
  136. /// <param name="willFail">If true, then <see cref="Status"/> will be set to <see cref="ProcessStatusEnum.ProcessedFail"/> otherwise to <see cref="ProcessStatusEnum.ProcessedSucc"/>.</param>
  137. public void Start(IAffinityToken token)
  138. {
  139. if (_cancellationTokenSource != null) throw new InvalidOperationException("Inconsistent task state. Thread must not be initialized before task start.");
  140. Reload();
  141. CheckTaskState(ProcessStatusEnum.Pending, ProcessStatusEnum.Paused);
  142. if (AttemptsLeft == 0)
  143. {
  144. SetStatus(ProcessStatusEnum.ExpiredAttemts, "(System) Attempts expired");
  145. _parent.Provider.Set(this);
  146. return;
  147. }
  148. AffinityToken = token;
  149. SetStatus(ProcessStatusEnum.Attached, $"Attached to worker {AffinityToken}");
  150. _parent.Provider.Set(this);
  151. _cancellationTokenSource = new CancellationTokenSource();
  152. if (AttemptTimeout.HasValue)
  153. _cancellationTokenSource.CancelAfter(AttemptTimeout.Value);
  154. ThreadPool.QueueUserWorkItem(Execute, _cancellationTokenSource);
  155. }
  156. public void Postpone(TimeSpan postponeInterval, string reason)
  157. {
  158. throw new NotImplementedException();
  159. }
  160. /// <summary>
  161. /// <inheritdoc cref="ITaskItem.Stop"/>
  162. /// </summary>
  163. public void Stop(string reason, bool willPause = true, bool willFail = false)
  164. {
  165. if (_cancellationTokenSource == null) throw new InvalidOperationException("Inconsistent task state. Thread must be initialized before task stop.");
  166. Reload();
  167. CheckTaskState(ProcessStatusEnum.Started);
  168. if (AttemptsLeft == 0)
  169. {
  170. SetStatus(ProcessStatusEnum.ExpiredAttemts, "(System) Attempts expired");
  171. _parent.Provider.Set(this);
  172. return;
  173. }
  174. _cancellationTokenSource.Cancel();
  175. _cancellationTokenSource = null;
  176. SetStatus(willPause ? ProcessStatusEnum.Paused : willFail ? ProcessStatusEnum.ProcessedFail : ProcessStatusEnum.ProcessedSucc, reason);
  177. StatFinished = DateTime.Now;
  178. _parent.Provider.Set(this);
  179. }
  180. public void Reset(string reason, TimeSpan postponeValid)
  181. {
  182. throw new NotImplementedException();
  183. }
  184. public void Delete(string reason, bool immediate = false)
  185. {
  186. throw new NotImplementedException();
  187. }
  188. public bool Euqals(ITaskItemData data)
  189. {
  190. return ((ITaskItemData)this).Equals(data);
  191. }
  192. public override string ToString()
  193. {
  194. var sb = new StringBuilder();
  195. sb.Append("Id=");
  196. sb.Append(Id == long.MinValue ? "NEW" : Id.ToString()).Append(";");
  197. sb.Append("Ref=").Append(Reference).Append(";");
  198. sb.Append("Sta=").Append(Status).Append(";");
  199. sb.Append("Cre=").Append(Created.ToString("G")).Append(";");
  200. return sb.ToString();
  201. }
  202. #endregion
  203. #region *** Internal Operations ***
  204. internal void SetStatus(ProcessStatusEnum status, string reason)
  205. {
  206. if (status == Status) return;
  207. _statusLog.Add(new TaskStatusLogItem(Status, reason));
  208. Status = status;
  209. Changed = DateTime.Now;
  210. }
  211. internal void Reload()
  212. {
  213. if (Id == long.MinValue) throw new InvalidOperationException("Task is not saved yet.");
  214. var data = _parent.GetTask(this);
  215. CopyFrom(data);
  216. }
  217. internal void CopyFrom(ITaskItemData data)
  218. {
  219. Id = data.Id;
  220. Status = data.Status;
  221. Changed = data.Changed;
  222. Reference = data.Reference;
  223. ProviderClass = data.ProviderClass;
  224. Priority = data.Priority;
  225. ProcessingValidFrom = data.ProcessingValidFrom;
  226. ProcessingValidTo = data.ProcessingValidTo;
  227. Created = data.Created;
  228. Changed = data.Changed;
  229. StatStarted = data.StatStarted;
  230. StatFinished = data.StatFinished;
  231. StatLastAttemptDuration = data.StatLastAttemptDuration;
  232. AttemptsLeft = data.AttemptsLeft;
  233. InitialAttempts = data.InitialAttempts;
  234. AttemptTimeout = data.AttemptTimeout;
  235. _statusLog.Clear();
  236. _statusLog.AddRange(data.StatusLog);
  237. _arguments.Clear();
  238. _arguments.AddRange(data.Arguments);
  239. }
  240. bool IEquatable<ITaskItemData>.Equals(ITaskItemData? other)
  241. {
  242. if (other == null)
  243. return false;
  244. var result = Id == other.Id &&
  245. Status == other.Status &&
  246. Changed == other.Changed &&
  247. Reference == other.Reference &&
  248. ProviderClass == other.ProviderClass &&
  249. Priority == other.Priority &&
  250. ProcessingValidFrom == other.ProcessingValidFrom &&
  251. ProcessingValidTo == other.ProcessingValidTo &&
  252. Created == other.Created &&
  253. Changed == other.Changed &&
  254. StatStarted == other.StatStarted &&
  255. StatFinished == other.StatFinished &&
  256. StatLastAttemptDuration == other.StatLastAttemptDuration &&
  257. InitialAttempts == other.InitialAttempts &&
  258. AttemptsLeft == other.AttemptsLeft &&
  259. AttemptTimeout == other.AttemptTimeout &&
  260. _statusLog.SequenceEqual(other.StatusLog) &&
  261. _arguments.SequenceEqual(other.Arguments);
  262. return result;
  263. }
  264. #endregion
  265. #region *** Private Operations ***
  266. private void CheckValidity()
  267. {
  268. var now = DateTime.Now;
  269. if (!(ProcessingValidFrom <= now && now < ProcessingValidTo.GetValueOrDefault(DateTime.MaxValue)))
  270. throw new TaskValidityExpiredException(this);
  271. }
  272. private void CheckTaskState(params ProcessStatusEnum[] allowedStates)
  273. {
  274. if (allowedStates == null || allowedStates.Length == 0) throw new ArgumentNullException(nameof(allowedStates));
  275. CheckValidity();
  276. if (!allowedStates.Contains(Status))
  277. throw new InvalidTaskStateException(this, allowedStates);
  278. }
  279. private void Execute(object? state)
  280. {
  281. if (state==null) throw new ArgumentNullException(nameof(state));
  282. var token = (CancellationTokenSource)state;
  283. SetStatus(ProcessStatusEnum.Started, $"(System) Task started with attempt #{(InitialAttempts - AttemptsLeft) + 1}");
  284. StatStarted = DateTime.Now;
  285. _parent.Provider.Set(this);
  286. }
  287. #endregion
  288. }
  289. }