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 _running = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary _lastMinuteRun = new ConcurrentDictionary(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 GetRunningJobIds() { return new List(_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(); } } }