docs(db): correct why VACUUM fails against an open reader

The comment said PerformMaintenance inherits a five-second timeout before
throwing. It does inherit that timeout, but it is not what happens here: a VACUUM
on a connection with a live reader fails instantly with 'cannot VACUUM - SQL
statements in progress'.

That is SQLITE_ERROR, not SQLITE_BUSY. Busy handling only covers contention
between different connections, so no timeout applies and no retry helps. The
practical difference matters for the error message the UI will show: the DELETE
has already committed when it fires, so the rows are gone and only the compaction
is missing -- 'deleted but not compacted', not 'failed'.

And PerformMaintenance batches VACUUM, REINDEX and ANALYZE in one statement, so
a failing VACUUM takes the other two with it.
This commit is contained in:
2026-08-18 20:37:40 +02:00
parent 6a3bbe2357
commit 22de2de234
+11 -6
View File
@@ -14,12 +14,17 @@ internal enum DbOperation
// frame. Generalises the retention-sweep lock, which already did exactly this
// for a single case.
//
// The reason it has to cover all of them together, not one each: an export
// leaves a reader open on the primary connection deliberately outside _readLock,
// because the enumerator is consumed lazily by its caller. If a VACUUM starts
// while that reader lives, it meets an active reader on a connection Microsoft
// documents as not thread-safe, and PerformMaintenance sets no command timeout
// so it inherits five seconds before throwing.
// The reason it has to cover all of them together, not one each: an export can
// leave a reader open on the primary connection outside _readLock, because
// StreamForExport hands back an enumerator its caller consumes lazily. A VACUUM
// starting while that reader lives fails immediately with "cannot VACUUM - SQL
// statements in progress".
//
// That is SQLITE_ERROR, not SQLITE_BUSY, so no timeout applies and no retry
// helps -- busy handling only covers contention between different connections.
// And it fails after the DELETE has committed, so the rows are gone and the file
// is not compacted. PerformMaintenance runs VACUUM, REINDEX and ANALYZE as one
// batch, so the latter two never run either.
//
// Pure state machine, no ImGui and no database, so the build suite can pin the
// transitions without standing up either.