/// <summary> /// <see cref="TimerCallback"/> to be called every tick. Checks validity of the tick, performs workload and schedues the next tick. /// </summary> /// <param name="state"></param> private static void OnTick(object state) { Logger.LogTick(); DateTime now = DateTime.Now; // Recalculate next run dates if the last tick was more than 90 seconds ago or if it is in the future. // Such clock skew implies manual time change, huge NTP leap, machine awakened from hibernation/suspension or some other potentially unsafe state. if (now - _lastTick > _skewLimit || now < _lastTick) { Logger.LogTickSkew(); foreach (Task task in Config.Tasks.Values) { task.RefreshNextRunDate(); } LogCleaner.Start(); LogCleaner.RefreshNextRunDate(); } else { DoTick(now); } // The next tick time has to be readjusted often to maintain the precision of the scheduled tasks. // System.Threading.Timer is ridiculously imprecise. The best case scenario skews the timer by 1 ms per minute (1 minute per ~100 days). // An alternative would be to create a custom timer implementaion based on high-precision multimedia timers, but // System.Threading.Timer is simple to use and guaranteed to be supported pretty much everywhere, so it's good enough. SetNextTick(now); }
/// <summary> /// Logs the values and tasks present in <see cref="Config"/>. /// </summary> public static void LogConfig() { if (Writer != null) { Log(string.Format("[Config] Got log retention period {0} days", Config.LogRetention)); LogCleaner.RefreshNextRunDate(); // Produces log cleaner log line foreach (Task task in Config.Tasks.Values) { task.RefreshNextRunDate(); // Produces task detail log line } } }
/// <summary> /// <see cref="ServiceBase.Run"/> handler. Opens <see cref="Logger"/> (if configured), loads <see cref="Config"/> and starts the <see cref="Timer"/>. /// </summary> /// <param name="args">Command line arguments.</param> protected override void OnStart(string[] args) { if (args.Length == 1 && args[0] == "/log") { Logger.Open(Path.Combine(Config.LogRoot, "service.log")); } Logger.LogServiceStarted(); Config.Load(); LogCleaner.Start(); IpcServer.Start(); SetNextTick(DateTime.Now); }
/// <summary> /// Performs workload during normal tick. Runs <see cref="Task"/> or <see cref="LogCleaner"/> if they are scheduled to be run in the current tick. /// </summary> /// <param name="now"></param> private static void DoTick(DateTime now) { foreach (Task task in Config.Tasks.Values) { if (task.NextRunDate <= now) { task.Start(); task.RefreshNextRunDate(); } } if (LogCleaner.NextRunDate <= now) { LogCleaner.Start(); LogCleaner.RefreshNextRunDate(); } }
/// <summary> /// Reloads service configuration. /// </summary> public void ReloadConfig() { Logger.LogIpcReloadMessage(); Config.Load(); LogCleaner.Start(); }