From bc968cbaf00927cd8ec4adbcea626ec08d4944e6 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Wed, 3 Jun 2026 12:45:57 +0200 Subject: [PATCH] Fix remote file locking on Windows This fixes a locked database issue on Windows. The issue was caused by the backup process having an open database, and then not passing that connection to the lock handler. The lock handler would then attempt to open a new connection to the same database, which would fail. This PR fixes the issue by passing an instance of the database to the lock handler. Also addressed a logic issue where the dbpath was checked, even if an existing database connection was found. Also fixed the lock results not setting the EndTime property, causing the reported lelapsed time to be off. --- .../Library/Main/Database/LocalLockDatabase.cs | 17 +++++++++++++++++ .../Library/Main/Operation/BackupHandler.cs | 5 +++-- .../Main/Operation/ReadLockInfoHandler.cs | 7 ++++--- .../Library/Main/Operation/SetLocksHandler.cs | 9 +++++---- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Duplicati/Library/Main/Database/LocalLockDatabase.cs b/Duplicati/Library/Main/Database/LocalLockDatabase.cs index dd5e946fa..c720c48e6 100644 --- a/Duplicati/Library/Main/Database/LocalLockDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalLockDatabase.cs @@ -54,6 +54,23 @@ namespace Duplicati.Library.Main.Database return dbnew; } + /// + /// Creates a new instance of the class. + /// + /// The database to create the new instance on. + /// A cancellation token to cancel the operation. + /// The instance + public static async Task CreateAsync(LocalDatabase database, CancellationToken token) + { + var db = (LocalLockDatabase)await LocalDatabase.CreateLocalDatabaseAsync( + database, + new LocalLockDatabase(), + token + ).ConfigureAwait(false); + db.ShouldCloseConnection = false; + return db; + } + /// /// Updates the lock expiration time for a remote volume. /// diff --git a/Duplicati/Library/Main/Operation/BackupHandler.cs b/Duplicati/Library/Main/Operation/BackupHandler.cs index db6f40bfe..5a3f2287e 100644 --- a/Duplicati/Library/Main/Operation/BackupHandler.cs +++ b/Duplicati/Library/Main/Operation/BackupHandler.cs @@ -604,9 +604,10 @@ namespace Duplicati.Library.Main.Operation return; } - m_result.LockResults = new SetLockResults(m_result); + using var lockDb = await LocalLockDatabase.CreateAsync(m_database, m_taskReader.StopToken); + m_result.LockResults ??= new SetLockResults(); await new SetLocksHandler(m_options, (SetLockResults)m_result.LockResults) - .RunAsync(backendManager, versionTimestamps) + .RunAsync(backendManager, lockDb, versionTimestamps) .ConfigureAwait(false); } diff --git a/Duplicati/Library/Main/Operation/ReadLockInfoHandler.cs b/Duplicati/Library/Main/Operation/ReadLockInfoHandler.cs index 08be0eec0..04124d535 100644 --- a/Duplicati/Library/Main/Operation/ReadLockInfoHandler.cs +++ b/Duplicati/Library/Main/Operation/ReadLockInfoHandler.cs @@ -73,14 +73,14 @@ namespace Duplicati.Library.Main.Operation if (!backendManager.SupportsObjectLocking) throw new UserInformationException("Backend does not support object locking", "BackendDoesNotSupportLocking"); - if (!File.Exists(m_options.Dbpath)) + var ownsDatabase = databaseOverride is null; + if (ownsDatabase && !File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseFileDoesNotExist"); m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.ReadLockInfo_Running); - var ownsDatabase = databaseOverride is null; await using var db = ownsDatabase - ? await LocalLockDatabase.CreateAsync(m_options.Dbpath, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false) + ? await LocalLockDatabase.CreateAsync(m_options.Dbpath!, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false) : null; var database = databaseOverride ?? db!; @@ -118,6 +118,7 @@ namespace Duplicati.Library.Main.Operation m_result.VolumesRead = readCount; m_result.VolumesUpdated = updatedCount; + m_result.EndTime = DateTime.UtcNow; if (updatedCount > 0 || errorCount > 0) Log.WriteInformationMessage(LOGTAG, "ReadLockInfoComplete", "Read lock info complete: {0} updated, {1} errors", updatedCount, errorCount); diff --git a/Duplicati/Library/Main/Operation/SetLocksHandler.cs b/Duplicati/Library/Main/Operation/SetLocksHandler.cs index f684ee9c6..7d85f9d7f 100644 --- a/Duplicati/Library/Main/Operation/SetLocksHandler.cs +++ b/Duplicati/Library/Main/Operation/SetLocksHandler.cs @@ -50,7 +50,7 @@ namespace Duplicati.Library.Main.Operation public Task RunAsync(IBackendManager backendManager, IEnumerable? versionTimestamps = null) => RunAsync(backendManager, null, versionTimestamps); - internal async Task RunAsync(IBackendManager backendManager, Database.LocalLockDatabase? databaseOverride, IEnumerable? versionTimestamps = null) + public async Task RunAsync(IBackendManager backendManager, Database.LocalLockDatabase? databaseOverride, IEnumerable? versionTimestamps = null) { if (m_options.RemoteFileLockDuration is null) throw new UserInformationException("No lock duration specified", "MissingLockDuration"); @@ -61,16 +61,16 @@ namespace Duplicati.Library.Main.Operation if (!backendManager.SupportsObjectLocking) throw new UserInformationException("Backend does not support object locking", "BackendDoesNotSupportLocking"); - if (!File.Exists(m_options.Dbpath)) + var ownsDatabase = databaseOverride is null; + if (ownsDatabase && !File.Exists(m_options.Dbpath)) throw new Exception(string.Format("Database file does not exist: {0}", m_options.Dbpath)); var effectiveVersionTimestamps = versionTimestamps ?? m_versionTimestamps; m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_Lock); - var ownsDatabase = databaseOverride is null; await using var db = ownsDatabase - ? await LocalLockDatabase.CreateAsync(m_options.Dbpath, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false) + ? await LocalLockDatabase.CreateAsync(m_options.Dbpath!, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false) : null; var database = databaseOverride ?? db!; @@ -123,6 +123,7 @@ namespace Duplicati.Library.Main.Operation m_result.VolumesRead = readCount; m_result.VolumesUpdated = updatedCount; + m_result.EndTime = DateTime.UtcNow; } private async Task> ResolveFilesetIdsAsync(Database.LocalListDatabase db, IEnumerable? suppliedVersions)