Changes
This commit is contained in:
parent
976cd03623
commit
60288a173a
|
@ -2,37 +2,47 @@
|
||||||
|
|
||||||
namespace JobQueue;
|
namespace JobQueue;
|
||||||
|
|
||||||
public abstract class Job : IComparable
|
public abstract class Job<T> : IComparable where T : notnull
|
||||||
{
|
{
|
||||||
|
protected JobQueue<T> Queue { get; init; }
|
||||||
public ProgressToken ProgressToken { get; private set; }
|
public ProgressToken ProgressToken { get; private set; }
|
||||||
public TimeSpan Interval { get; private set; }
|
public TimeSpan Interval { get; private set; }
|
||||||
public DateTime LastStarted { get; private set; }
|
public DateTime LastStarted { get; private set; }
|
||||||
public string JobId { get; init; }
|
public string JobId { get; init; }
|
||||||
|
public string? ParentJobId { get; init; }
|
||||||
|
private List<Job<T>> childJobs = new();
|
||||||
public TimeSpan MaximumTimeBetweenUpdates { get; init; }
|
public TimeSpan MaximumTimeBetweenUpdates { get; init; }
|
||||||
private readonly ILogger? _logger;
|
private readonly ILogger? _logger;
|
||||||
|
|
||||||
public Job(string? jobId = null, ILogger? logger = null) : this (TimeSpan.Zero, jobId, logger)
|
protected Job(JobQueue<T> queue, string? jobId = null, ILogger? logger = null) : this (queue, TimeSpan.Zero, jobId, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public Job(TimeSpan interval, string? jobId = null, ILogger? logger = null) : this(interval, TimeSpan.FromMinutes(3), 1, jobId, logger)
|
protected Job(JobQueue<T> queue, TimeSpan interval, string? jobId = null, ILogger? logger = null) : this(queue, interval, TimeSpan.FromMinutes(3), 1, jobId, null, logger)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Job(TimeSpan interval, TimeSpan maximumTimeBetweenUpdates, string? jobId = null, ILogger? logger = null) : this(interval, maximumTimeBetweenUpdates, 1, jobId, logger)
|
protected Job(JobQueue<T> queue, TimeSpan interval, TimeSpan maximumTimeBetweenUpdates, string? jobId = null, ILogger? logger = null) : this(queue, interval, maximumTimeBetweenUpdates, 1, jobId, null, logger)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Job(JobQueue<T> queue, TimeSpan interval, TimeSpan maximumTimeBetweenUpdates, string? jobId = null, string? parentJobId = null, ILogger? logger = null) : this(queue, interval, maximumTimeBetweenUpdates, 1, jobId, parentJobId, logger)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Job(TimeSpan interval, TimeSpan maximumTimeBetweenUpdates, int steps, string? jobId = null, ILogger? logger = null)
|
protected Job(JobQueue<T> queue, TimeSpan interval, TimeSpan maximumTimeBetweenUpdates, int steps, string? jobId = null, string? parentJobId = null, ILogger? logger = null)
|
||||||
{
|
{
|
||||||
|
this.Queue = queue;
|
||||||
this._logger = logger;
|
this._logger = logger;
|
||||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
||||||
this.ProgressToken = new ProgressToken(steps);
|
this.ProgressToken = new ProgressToken(steps);
|
||||||
this.Interval = interval;
|
this.Interval = interval;
|
||||||
this.MaximumTimeBetweenUpdates = maximumTimeBetweenUpdates;
|
this.MaximumTimeBetweenUpdates = maximumTimeBetweenUpdates;
|
||||||
|
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
this.JobId = jobId ?? new string(Enumerable.Repeat(chars, 16).Select(s => s[Random.Shared.Next(s.Length)]).ToArray());
|
this.JobId = jobId ?? new string(Enumerable.Repeat(chars, 16).Select(s => s[Random.Shared.Next(s.Length)]).ToArray());
|
||||||
|
this.ParentJobId = parentJobId;
|
||||||
this._logger?.LogDebug($"Created Job {jobId}");
|
this._logger?.LogDebug($"Created Job {jobId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +53,8 @@ public abstract class Job : IComparable
|
||||||
this.LastStarted = DateTime.UtcNow;
|
this.LastStarted = DateTime.UtcNow;
|
||||||
this.ProgressToken.Start();
|
this.ProgressToken.Start();
|
||||||
this._logger?.LogDebug($"Started Job {JobId}");
|
this._logger?.LogDebug($"Started Job {JobId}");
|
||||||
this.Execute(ProgressToken.CancellationTokenSource.Token);
|
Task t = new (() => Execute(ProgressToken.CancellationTokenSource.Token));
|
||||||
|
t.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cancel()
|
public void Cancel()
|
||||||
|
@ -68,6 +79,11 @@ public abstract class Job : IComparable
|
||||||
return this.ProgressToken.LastUpdate.Add(MaximumTimeBetweenUpdates) > DateTime.UtcNow;
|
return this.ProgressToken.LastUpdate.Add(MaximumTimeBetweenUpdates) > DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Job<T>> GetChildJobs()
|
||||||
|
{
|
||||||
|
return this.childJobs;
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"Job: {JobId} Last Started: {LastStarted:dd/MM-HH:mm:ss.fff} Interval: {Interval:d-hh\\:mm\\:ss} {ProgressToken}";
|
return $"Job: {JobId} Last Started: {LastStarted:dd/MM-HH:mm:ss.fff} Interval: {Interval:d-hh\\:mm\\:ss} {ProgressToken}";
|
||||||
|
@ -75,13 +91,13 @@ public abstract class Job : IComparable
|
||||||
|
|
||||||
public int CompareTo(object? obj)
|
public int CompareTo(object? obj)
|
||||||
{
|
{
|
||||||
if (obj is not Job other)
|
if (obj is not Job<T> other)
|
||||||
throw new ArgumentException("Type can not be compared", nameof(obj));
|
throw new ArgumentException("Type can not be compared", nameof(obj));
|
||||||
return TimeToNextExecution().CompareTo(other.TimeToNextExecution());
|
return TimeToNextExecution().CompareTo(other.TimeToNextExecution());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
return obj is Job other && other.JobId.Equals(this.JobId);
|
return obj is Job<T> other && other.JobId.Equals(this.JobId);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,8 +4,8 @@ namespace JobQueue;
|
||||||
|
|
||||||
public class JobQueue<T> : IDisposable where T : notnull
|
public class JobQueue<T> : IDisposable where T : notnull
|
||||||
{
|
{
|
||||||
private readonly Dictionary<T, HashSet<Job>> _queues = new();
|
private readonly Dictionary<T, HashSet<Job<T>>> _queues = new();
|
||||||
private Dictionary<T, HashSet<Job>> FailedJobs { get; init; } = new();
|
private Dictionary<T, HashSet<Job<T>>> AllFailedJobs { get; init; } = new();
|
||||||
private bool _running = true;
|
private bool _running = true;
|
||||||
private readonly ILogger? _logger;
|
private readonly ILogger? _logger;
|
||||||
|
|
||||||
|
@ -21,35 +21,38 @@ public class JobQueue<T> : IDisposable where T : notnull
|
||||||
{
|
{
|
||||||
foreach (T type in _queues.Where(q => !RunningJobsFor(q.Key).Any()).Select(kv => kv.Key))
|
foreach (T type in _queues.Where(q => !RunningJobsFor(q.Key).Any()).Select(kv => kv.Key))
|
||||||
{
|
{
|
||||||
Job? startJob = WaitingJobsFor(type).Min();
|
Job<T>? startJob = WaitingJobsFor(type).Min();
|
||||||
this._logger?.LogInformation($"Starting job: {startJob}");
|
this._logger?.LogInformation($"Starting job: {startJob}");
|
||||||
startJob?.Start();
|
startJob?.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Job job in _queues.Values.SelectMany(job => job).Where(job =>
|
foreach (Job<T> job in _queues.Values.SelectMany(job => job).Where(job =>
|
||||||
job.ProgressToken.State is ProgressState.Finished or ProgressState.Cancelled))
|
job.ProgressToken.State is ProgressState.Finished or ProgressState.Cancelled))
|
||||||
{
|
{
|
||||||
this._logger?.LogInformation($"Job finished: {job}");
|
this._logger?.LogInformation($"Job finished: {job}");
|
||||||
job.Reset();
|
if(job.Interval.TotalMilliseconds != 0)
|
||||||
|
job.Reset();
|
||||||
|
else
|
||||||
|
RemoveJob(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (T type in _queues.Keys)
|
foreach (T type in _queues.Keys)
|
||||||
{
|
{
|
||||||
HashSet<Job> failed = FailedJobsFor(type).ToHashSet();
|
HashSet<Job<T>> failed = _queues[type].Where(job => job.ProgressToken.State is ProgressState.Failed).ToHashSet();
|
||||||
if (!failed.Any())
|
if (!failed.Any())
|
||||||
break;
|
break;
|
||||||
this._logger?.LogInformation($"Failed Jobs:\n\t{string.Join("\n\t", failed)}");
|
this._logger?.LogInformation($"Failed Jobs:\n\t{string.Join("\n\t", failed)}");
|
||||||
FailedJobs[type] = FailedJobs[type].Concat(failed).ToHashSet();
|
AllFailedJobs[type] = AllFailedJobs[type].Concat(failed).ToHashSet();
|
||||||
_queues[type].RemoveWhere(job => failed.Contains(job));
|
_queues[type].RemoveWhere(job => failed.Contains(job));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (T type in _queues.Keys)
|
foreach (T type in _queues.Keys)
|
||||||
{
|
{
|
||||||
HashSet<Job> overtimeJobs = RunningJobsFor(type).Where(job => job.OverTimelimit()).ToHashSet();
|
HashSet<Job<T>> overtimeJobs = RunningJobsFor(type).Where(job => job.OverTimelimit()).ToHashSet();
|
||||||
if (!overtimeJobs.Any())
|
if (!overtimeJobs.Any())
|
||||||
break;
|
break;
|
||||||
this._logger?.LogInformation($"Jobs over TimeLimit:\n\t{string.Join("\n\t", overtimeJobs)}");
|
this._logger?.LogInformation($"Jobs over TimeLimit:\n\t{string.Join("\n\t", overtimeJobs)}");
|
||||||
foreach (Job jobOvertime in overtimeJobs)
|
foreach (Job<T> jobOvertime in overtimeJobs)
|
||||||
jobOvertime.Cancel();
|
jobOvertime.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,10 +63,10 @@ public class JobQueue<T> : IDisposable where T : notnull
|
||||||
public void AddQueue(T type)
|
public void AddQueue(T type)
|
||||||
{
|
{
|
||||||
this._queues.Add(type, new());
|
this._queues.Add(type, new());
|
||||||
this.FailedJobs.Add(type, new());
|
this.AllFailedJobs.Add(type, new());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddJob(T obj, Job job)
|
public void AddJob(T obj, Job<T> job)
|
||||||
{
|
{
|
||||||
if (!_queues.ContainsKey(obj))
|
if (!_queues.ContainsKey(obj))
|
||||||
throw new ArgumentException($"{obj.ToString()} does not have a queue.", nameof(obj));
|
throw new ArgumentException($"{obj.ToString()} does not have a queue.", nameof(obj));
|
||||||
|
@ -71,24 +74,79 @@ public class JobQueue<T> : IDisposable where T : notnull
|
||||||
_queues[obj].Add(job);
|
_queues[obj].Add(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Job> JobsMatching(T obj, ProgressState state)
|
public void RemoveJob(Job<T> job)
|
||||||
|
{
|
||||||
|
foreach (HashSet<Job<T>> queue in this._queues.Values)
|
||||||
|
{
|
||||||
|
if (queue.Contains(job))
|
||||||
|
{
|
||||||
|
queue.Remove(job);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryJobWithId(string jobId, out Job<T>? job)
|
||||||
|
{
|
||||||
|
job = JobWithId(jobId);
|
||||||
|
return job is not null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Job<T>? JobWithId(string jobId)
|
||||||
|
{
|
||||||
|
return _queues.Values.SelectMany(queue => queue).FirstOrDefault(job => job.JobId.Equals(jobId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Job<T>> JobsWithParent(string parentId)
|
||||||
|
{
|
||||||
|
return _queues.Values.SelectMany(queue => queue).Where(job => job.ParentJobId?.Equals(parentId) ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Job<T>> JobsWithParent(Job<T> parentJob)
|
||||||
|
{
|
||||||
|
return JobsWithParent(parentJob.JobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Job<T>> JobsMatchingState(ProgressState state)
|
||||||
|
{
|
||||||
|
return _queues.Values.SelectMany(queue => queue).Where(job => job.ProgressToken.State == state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Job<T>> RunningJobs() => JobsMatchingState(ProgressState.Running);
|
||||||
|
|
||||||
|
public IEnumerable<Job<T>> WaitingJobs() => JobsMatchingState(ProgressState.Waiting);
|
||||||
|
|
||||||
|
public IEnumerable<Job<T>> FailedJobs() => AllFailedJobs.Values.SelectMany(queue => queue).Select(job => job);
|
||||||
|
|
||||||
|
public IEnumerable<Job<T>> CancelledJobs() => JobsMatchingState(ProgressState.Cancelled);
|
||||||
|
|
||||||
|
public IEnumerable<Job<T>> FinishedJobs() => JobsMatchingState(ProgressState.Finished);
|
||||||
|
|
||||||
|
public IEnumerable<Job<T>> AllJobs() => _queues.Values.SelectMany(queue => queue).Select(job => job);
|
||||||
|
|
||||||
|
public IEnumerable<Job<T>> JobsMatchingStateFor(T obj, ProgressState state)
|
||||||
{
|
{
|
||||||
if (!_queues.ContainsKey(obj))
|
if (!_queues.ContainsKey(obj))
|
||||||
throw new ArgumentException($"{obj.ToString()} does not have a queue.", nameof(obj));
|
throw new ArgumentException($"{obj.ToString()} does not have a queue.", nameof(obj));
|
||||||
return _queues[obj].Where(job => job.ProgressToken.State == state);
|
return _queues[obj].Where(job => job.ProgressToken.State == state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Job> RunningJobsFor(T obj) => JobsMatching(obj, ProgressState.Running);
|
public IEnumerable<Job<T>> RunningJobsFor(T obj) => JobsMatchingStateFor(obj, ProgressState.Running);
|
||||||
|
|
||||||
public IEnumerable<Job> WaitingJobsFor(T obj) => JobsMatching(obj, ProgressState.Waiting);
|
public IEnumerable<Job<T>> WaitingJobsFor(T obj) => JobsMatchingStateFor(obj, ProgressState.Waiting);
|
||||||
|
|
||||||
public IEnumerable<Job> FailedJobsFor(T obj) => JobsMatching(obj, ProgressState.Failed);
|
public IEnumerable<Job<T>> FailedJobsFor(T obj)
|
||||||
|
{
|
||||||
|
if(!AllFailedJobs.ContainsKey(obj))
|
||||||
|
throw new ArgumentException($"{obj.ToString()} does not have a queue.", nameof(obj));
|
||||||
|
return AllFailedJobs[obj];
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<Job> CancelledJobsFor(T obj) => JobsMatching(obj, ProgressState.Cancelled);
|
public IEnumerable<Job<T>> CancelledJobsFor(T obj) => JobsMatchingStateFor(obj, ProgressState.Cancelled);
|
||||||
|
|
||||||
public IEnumerable<Job> FinishedJobsFor(T obj) => JobsMatching(obj, ProgressState.Finished);
|
public IEnumerable<Job<T>> FinishedJobsFor(T obj) => JobsMatchingStateFor(obj, ProgressState.Finished);
|
||||||
|
|
||||||
public IEnumerable<Job> AllJobsFor(T obj)
|
public IEnumerable<Job<T>> AllJobsFor(T obj)
|
||||||
{
|
{
|
||||||
if (!_queues.ContainsKey(obj))
|
if (!_queues.ContainsKey(obj))
|
||||||
throw new ArgumentException($"{obj.ToString()} does not have a queue.", nameof(obj));
|
throw new ArgumentException($"{obj.ToString()} does not have a queue.", nameof(obj));
|
||||||
|
@ -99,7 +157,7 @@ public class JobQueue<T> : IDisposable where T : notnull
|
||||||
{
|
{
|
||||||
this._logger?.LogInformation("Shutting down JobQueue.");
|
this._logger?.LogInformation("Shutting down JobQueue.");
|
||||||
_running = false;
|
_running = false;
|
||||||
foreach(Job job in _queues.Values.SelectMany(list => list))
|
foreach(Job<T> job in _queues.Values.SelectMany(list => list))
|
||||||
job.Cancel();
|
job.Cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,9 +7,9 @@ public struct ProgressToken
|
||||||
public ProgressState State { get; private set; }
|
public ProgressState State { get; private set; }
|
||||||
public DateTime LastUpdate { get; private set; }
|
public DateTime LastUpdate { get; private set; }
|
||||||
public float Progress { get; private set; }
|
public float Progress { get; private set; }
|
||||||
public int? Steps { get; init; }
|
public int? Steps { get; private set; }
|
||||||
public int? FinishedSteps { get; private set; }
|
public int? FinishedSteps { get; private set; }
|
||||||
internal CancellationTokenSource CancellationTokenSource { get; } = new();
|
public CancellationTokenSource CancellationTokenSource { get; } = new();
|
||||||
private readonly ILogger? _logger;
|
private readonly ILogger? _logger;
|
||||||
|
|
||||||
public ProgressToken(int? steps = null, ILogger? logger = null)
|
public ProgressToken(int? steps = null, ILogger? logger = null)
|
||||||
|
@ -64,6 +64,12 @@ public struct ProgressToken
|
||||||
return new ProgressToken(Steps, _logger);
|
return new ProgressToken(Steps, _logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetSteps(int steps)
|
||||||
|
{
|
||||||
|
this.Steps = steps;
|
||||||
|
UpdateProgress((float)FinishedSteps / (float)Steps);
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (Steps is null || FinishedSteps is null)
|
if (Steps is null || FinishedSteps is null)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user