107 lines
3.5 KiB
C#
107 lines
3.5 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace FileInterface.Scheduler
|
|
{
|
|
public sealed class CronScheduler : IDisposable
|
|
{
|
|
private readonly CronJobRepository _repository;
|
|
private readonly CronJobExecutor _executor;
|
|
private readonly JobLog _log;
|
|
private readonly ConcurrentDictionary<string, byte> _running = new ConcurrentDictionary<string, byte>(StringComparer.OrdinalIgnoreCase);
|
|
private readonly ConcurrentDictionary<string, string> _lastMinuteRun = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
private Timer _timer;
|
|
private int _checking;
|
|
|
|
public CronScheduler(CronJobRepository repository, CronJobExecutor executor, JobLog log)
|
|
{
|
|
_repository = repository;
|
|
_executor = executor;
|
|
_log = log;
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
if (_timer != null) return;
|
|
_timer = new Timer(CheckDueJobs, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
|
|
}
|
|
|
|
public bool RunNow(string jobId)
|
|
{
|
|
CronJobConfig job = _repository.GetById(jobId);
|
|
if (job == null) return false;
|
|
QueueExecution(job, "manual");
|
|
return true;
|
|
}
|
|
|
|
public IList<string> GetRunningJobIds()
|
|
{
|
|
return new List<string>(_running.Keys);
|
|
}
|
|
|
|
private void CheckDueJobs(object state)
|
|
{
|
|
if (Interlocked.Exchange(ref _checking, 1) == 1) return;
|
|
try
|
|
{
|
|
DateTime now = DateTime.Now;
|
|
string minuteStamp = now.ToString("yyyyMMddHHmm");
|
|
foreach (CronJobConfig job in _repository.GetAll())
|
|
{
|
|
if (!job.Enabled) continue;
|
|
try
|
|
{
|
|
CronExpression cron = CronExpression.Parse(job.Cron);
|
|
if (!cron.IsMatch(now)) continue;
|
|
|
|
string previous;
|
|
if (_lastMinuteRun.TryGetValue(job.Id, out previous) && previous == minuteStamp) continue;
|
|
_lastMinuteRun[job.Id] = minuteStamp;
|
|
QueueExecution(job, "cron " + job.Cron);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_log.Error(job, "Invalid cron/configuration: " + ex.Message);
|
|
}
|
|
}
|
|
}
|
|
finally { Interlocked.Exchange(ref _checking, 0); }
|
|
}
|
|
|
|
private void QueueExecution(CronJobConfig job, string reason)
|
|
{
|
|
byte ignored;
|
|
if (!_running.TryAdd(job.Id, 0))
|
|
{
|
|
_log.Info(job, "Skipped " + reason + " start because this job is still running.");
|
|
return;
|
|
}
|
|
|
|
Task.Run(() =>
|
|
{
|
|
try
|
|
{
|
|
_log.Info(job, "Job started (" + reason + ").");
|
|
_executor.Execute(job);
|
|
_log.Info(job, "Job finished.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_log.Error(job, "Job crashed: " + ex);
|
|
}
|
|
finally { _running.TryRemove(job.Id, out ignored); }
|
|
});
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Timer timer = _timer;
|
|
_timer = null;
|
|
if (timer != null) timer.Dispose();
|
|
}
|
|
}
|
|
}
|