From b5a3379f1d00d6d317c27fd972b4a37809dfee96 Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sun, 26 Apr 2020 11:03:41 -0700 Subject: [PATCH 1/2] Fix issue where multiple backups could occur after pause. Previously, the value of the LastRun field in the Schedule table was updated to be (approximately) the time that the job was added to the worker's list of tasks. However, if the backup was missed (via pause or system suspend), this value would be incorrect. If the next scheduled backup was also missed, then after the backup was finally run, the computed next start time would be based on the (incorrect) last run time of the first missed backup. This would then immediately trigger an unnecessary second backup. Now, we add a handler to the StartingWork event to set a more accurate value for the LastRun field in the Schedule table. This fixes issue #2249. --- Duplicati/Server/Scheduler.cs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/Duplicati/Server/Scheduler.cs b/Duplicati/Server/Scheduler.cs index 5ec0a28f1..a41c64205 100644 --- a/Duplicati/Server/Scheduler.cs +++ b/Duplicati/Server/Scheduler.cs @@ -81,6 +81,7 @@ namespace Duplicati.Server m_thread = new Thread(new ThreadStart(Runner)); m_worker = worker; m_worker.CompletedWork += OnCompleted; + m_worker.StartingWork += OnStartingWork; m_schedule = new KeyValuePair[0]; m_terminate = false; m_event = new AutoResetEvent(false); @@ -211,7 +212,20 @@ namespace Duplicati.Server } } - + + private void OnStartingWork(WorkerThread worker, Runner.IRunnerData task) + { + lock(m_lock) + { + if (m_updateTasks.TryGetValue(task, out Tuple scheduleInfo)) + { + // Item2 is the scheduled start time (Time in the Schedule table). + // Item3 is the actual start time (LastRun in the Schedule table). + m_updateTasks[task] = Tuple.Create(scheduleInfo.Item1, scheduleInfo.Item2, DateTime.UtcNow); + } + } + } + /// /// The actual scheduling procedure /// @@ -308,10 +322,15 @@ namespace Duplicati.Server Server.Runner.IRunnerData lastJob = jobsToRun.LastOrDefault(); if (lastJob != null) - lock(m_lock) + { + lock (m_lock) + { + // The actual last run time will be updated when the StartingWork event is raised. m_updateTasks[lastJob] = new Tuple(sc, start, DateTime.UtcNow); - - foreach(var job in jobsToRun) + } + } + + foreach (var job in jobsToRun) m_worker.AddTask(job); if (start < DateTime.UtcNow) From 1036b64002643a6bd618c31a7a5f446a941c52db Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Sun, 26 Apr 2020 13:56:06 -0700 Subject: [PATCH 2/2] Avoid indexing Dictionary by null key. --- Duplicati/Server/Scheduler.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Duplicati/Server/Scheduler.cs b/Duplicati/Server/Scheduler.cs index a41c64205..c496761fd 100644 --- a/Duplicati/Server/Scheduler.cs +++ b/Duplicati/Server/Scheduler.cs @@ -215,6 +215,11 @@ namespace Duplicati.Server private void OnStartingWork(WorkerThread worker, Runner.IRunnerData task) { + if (task is null) + { + return; + } + lock(m_lock) { if (m_updateTasks.TryGetValue(task, out Tuple scheduleInfo))