Skip to content

Commit 33d846a

Browse files
committed
Fix repair of corrupt BlobSizes.sql and mount tolerance
The repair job for BlobSizes.sql was unable to delete the corrupt database file on Windows because SQLite connection pooling kept the file handle open after the integrity check in HasIssue(). Two fixes: 1. SqliteDatabase.HasIssue: Use Pooling=False for integrity check connections so file handles are released immediately on dispose, allowing repair to delete the corrupt file. 2. BlobSizes.Initialize: Tolerate corrupt databases by catching SQLITE_CORRUPT and SQLITE_NOTADB errors, deleting the corrupt file (and WAL/SHM sidecars), and recreating a fresh database. This provides defense-in-depth since BlobSizes is a cache. Also remove SkipInCI from RepairFixesCorruptBlobSizesDatabase and add an assertion that repair actually cleans up the corrupt folder. Assisted-by: Claude Opus 4.6 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
1 parent 340a5ca commit 33d846a

4 files changed

Lines changed: 73 additions & 31 deletions

File tree

GVFS/GVFS.Common/Database/SqliteDatabase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public static bool HasIssue(string databasePath, PhysicalFileSystem filesystem,
2121

2222
try
2323
{
24-
string sqliteConnectionString = CreateConnectionString(databasePath);
24+
string sqliteConnectionString = $"data source={databasePath};Pooling=False";
2525
using (SqliteConnection integrityConnection = new SqliteConnection(sqliteConnectionString))
2626
{
2727
integrityConnection.Open();
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace GVFS.Common.Database
2+
{
3+
/// <summary>
4+
/// SQLite result codes used for error classification.
5+
/// See https://www.sqlite.org/rescode.html
6+
/// </summary>
7+
public static class SqliteErrorCodes
8+
{
9+
/// <summary>SQLITE_CORRUPT (11) — database disk image is malformed</summary>
10+
public const int Corrupt = 11;
11+
12+
/// <summary>SQLITE_NOTADB (26) — file is not a database</summary>
13+
public const int NotADatabase = 26;
14+
}
15+
}

GVFS/GVFS.FunctionalTests/Tests/MultiEnlistmentTests/SharedCacheTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ public void SecondCloneDoesNotDownloadAdditionalObjects()
6060
}
6161

6262
[TestCase]
63-
[SkipInCI("Product bug: repair does not fully restore corrupt BlobSizes.sql — mount crashes after repair")]
6463
public void RepairFixesCorruptBlobSizesDatabase()
6564
{
6665
GVFSFunctionalTestEnlistment enlistment = this.CloneAndMountEnlistment();
@@ -74,9 +73,12 @@ public void RepairFixesCorruptBlobSizesDatabase()
7473
blobSizesDbPath.ShouldBeAFile(this.fileSystem);
7574
this.fileSystem.WriteAllText(blobSizesDbPath, "0000");
7675

77-
// GVFS now tolerates corrupt blob sizes DB on mount (recreates
78-
// in-memory), but repair should still fix the underlying file.
76+
// Repair should detect and fix the corrupt database
7977
enlistment.Repair(confirm: true);
78+
79+
// Verify repair actually cleaned up the corrupt file
80+
blobSizesRoot.ShouldNotExistOnDisk(this.fileSystem);
81+
8082
enlistment.MountGVFS();
8183
}
8284

GVFS/GVFS.Virtualization/BlobSize/BlobSizes.cs

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,54 @@ public virtual void Initialize()
5454
string folderPath = Path.GetDirectoryName(this.databasePath);
5555
this.fileSystem.CreateDirectory(folderPath);
5656

57+
try
58+
{
59+
this.InitializeDatabase();
60+
}
61+
catch (SqliteException ex) when (ex.SqliteErrorCode == SqliteErrorCodes.Corrupt || ex.SqliteErrorCode == SqliteErrorCodes.NotADatabase)
62+
{
63+
EventMetadata metadata = this.CreateEventMetadata(ex);
64+
metadata.Add("SqliteErrorCode", ex.SqliteErrorCode);
65+
this.tracer.RelatedWarning(metadata, $"{nameof(BlobSizes)}.{nameof(this.Initialize)}: database corrupt, deleting and recreating");
66+
67+
SqliteConnection.ClearAllPools();
68+
this.DeleteDatabaseFiles();
69+
this.InitializeDatabase();
70+
}
71+
72+
this.flushDataThread = new Thread(this.FlushDbThreadMain);
73+
this.flushDataThread.IsBackground = true;
74+
this.flushDataThread.Start();
75+
}
76+
77+
public virtual void Shutdown()
78+
{
79+
this.isStopping = true;
80+
this.wakeUpFlushThread.Set();
81+
this.flushDataThread.Join();
82+
}
83+
84+
public virtual void AddSize(Sha1Id sha, long size)
85+
{
86+
this.queuedSizes.Enqueue(new BlobSize(sha, size));
87+
}
88+
89+
public virtual void Flush()
90+
{
91+
this.wakeUpFlushThread.Set();
92+
}
93+
94+
public void Dispose()
95+
{
96+
if (this.wakeUpFlushThread != null)
97+
{
98+
this.wakeUpFlushThread.Dispose();
99+
this.wakeUpFlushThread = null;
100+
}
101+
}
102+
103+
private void InitializeDatabase()
104+
{
57105
using (SqliteConnection connection = new SqliteConnection(this.sqliteConnectionString))
58106
{
59107
connection.Open();
@@ -125,36 +173,13 @@ public virtual void Initialize()
125173
createTableCommand.ExecuteNonQuery();
126174
}
127175
}
128-
129-
this.flushDataThread = new Thread(this.FlushDbThreadMain);
130-
this.flushDataThread.IsBackground = true;
131-
this.flushDataThread.Start();
132176
}
133177

134-
public virtual void Shutdown()
178+
private void DeleteDatabaseFiles()
135179
{
136-
this.isStopping = true;
137-
this.wakeUpFlushThread.Set();
138-
this.flushDataThread.Join();
139-
}
140-
141-
public virtual void AddSize(Sha1Id sha, long size)
142-
{
143-
this.queuedSizes.Enqueue(new BlobSize(sha, size));
144-
}
145-
146-
public virtual void Flush()
147-
{
148-
this.wakeUpFlushThread.Set();
149-
}
150-
151-
public void Dispose()
152-
{
153-
if (this.wakeUpFlushThread != null)
154-
{
155-
this.wakeUpFlushThread.Dispose();
156-
this.wakeUpFlushThread = null;
157-
}
180+
this.fileSystem.TryDeleteFile(this.databasePath);
181+
this.fileSystem.TryDeleteFile(this.databasePath + "-wal");
182+
this.fileSystem.TryDeleteFile(this.databasePath + "-shm");
158183
}
159184

160185
private void FlushDbThreadMain()

0 commit comments

Comments
 (0)