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.
This commit is contained in:
Kenneth Skovhede
2026-06-03 12:45:57 +02:00
parent 691a882f53
commit bc968cbaf0
4 changed files with 29 additions and 9 deletions
@@ -54,6 +54,23 @@ namespace Duplicati.Library.Main.Database
return dbnew;
}
/// <summary>
/// Creates a new instance of the <see cref="LocalLockDatabase"/> class.
/// </summary>
/// <param name="database">The database to create the new instance on.</param>
/// <param name="token">A cancellation token to cancel the operation.</param>
/// <returns>The <see cref="LocalLockDatabase"/> instance</returns>
public static async Task<LocalLockDatabase> CreateAsync(LocalDatabase database, CancellationToken token)
{
var db = (LocalLockDatabase)await LocalDatabase.CreateLocalDatabaseAsync(
database,
new LocalLockDatabase(),
token
).ConfigureAwait(false);
db.ShouldCloseConnection = false;
return db;
}
/// <summary>
/// Updates the lock expiration time for a remote volume.
/// </summary>
@@ -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);
}
@@ -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);
@@ -50,7 +50,7 @@ namespace Duplicati.Library.Main.Operation
public Task RunAsync(IBackendManager backendManager, IEnumerable<DateTime>? versionTimestamps = null)
=> RunAsync(backendManager, null, versionTimestamps);
internal async Task RunAsync(IBackendManager backendManager, Database.LocalLockDatabase? databaseOverride, IEnumerable<DateTime>? versionTimestamps = null)
public async Task RunAsync(IBackendManager backendManager, Database.LocalLockDatabase? databaseOverride, IEnumerable<DateTime>? 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<List<long>> ResolveFilesetIdsAsync(Database.LocalListDatabase db, IEnumerable<DateTime>? suppliedVersions)