Supervised quiescence is defensible only if the acceptance requirement is changed from “prove every native release succeeded” to “prove all authorized owners have terminated, prevent new owners, and verify defined storage postconditions”; it is not equivalent to complete observation of every handle, lock, mapping, or I/O release.
Recommended containment and quiescence protocol
- Put every SQLite-capable process in one non-breakaway Job Object.
- Create the job before launching workers.
- Set
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. - Do not set
JOB_OBJECT_LIMIT_BREAKAWAY_OKorJOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK. - Launch workers so they are associated with the job before they can initialize SQLite. Creating them suspended, assigning them with
AssignProcessToJobObject, and only then resuming them closes the launch/assignment race. - Treat assignment failure as initialization failure and terminate the still-suspended process.
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEterminates associated processes when the final job handle closes ^2^. - Prevent unintended handle propagation.
- Create database and control handles as non-inheritable.
- Use
PROC_THREAD_ATTRIBUTE_HANDLE_LISTwhen a child requires a small, explicit handle set. - Do not use unrestricted
bInheritHandles=TRUE. - Audit any use of
DuplicateHandle, because duplicated handles can refer to the same file object from another process.
- Close the logical access gate before shutdown.
- Atomically change supervisor state to
QUIESCING. - Reject new registry, journal, and ledger operations before connection creation.
- Drain admitted operations and stop creating workers.
- Require every owner, including partially initialized owners, to register before it can obtain a database path or connection.
- Keep one supervisor outside the disposable job; otherwise closing the last job handle cannot be controlled reliably.
- Atomically change supervisor state to
- Add an OS-level fence if protection from non-cooperating local processes is required.
- An application mutex or Python lock fences only cooperating code.
- A restrictive
CreateFileshare mode can block conflicting future opens, but cannot revoke handles already open. Share settings remain effective until each open handle closes ^5^. - Therefore, acquire any exclusive sentinel only after existing owners have drained, and cover the database plus applicable journal/WAL/SHM files. Whether this works with SQLite must be tested against the exact access/share flags used by your VFS; the supplied documentation does not establish those flags for SQLite 3.50.4.
- Perform graceful SQLite shutdown, then enforce a deadline.
- End active statements and transactions.
- Close every SQLite connection.
- In WAL mode, the last clean
sqlite3_close()can checkpoint and remove-waland-shm; abnormal termination can leave those artifacts ^6^. - If workers miss the deadline, terminate the job and classify the run as forced quiescence rather than clean disposal.
- Verify zero live owners before reopening storage.
- Query job state/accounting and retain process handles until every process has terminated.
- An I/O-completion-port
JOB_OBJECT_MSG_ACTIVE_PROCESS_ZEROis useful, but ordinary job completion messages are not universally guaranteed; corroborate the notification by querying job state and waiting on the tracked process handles ^1^ ^7^. - Only after that boundary should a designated verifier reopen the database and evaluate the formally specified SQLite/storage postconditions.
What Windows can and cannot verify
| Resource | Supported evidence | Limitation |
|---|---|---|
| File handles | Restart Manager can register filenames and use RmGetList to identify applications and services using them ^8^. |
Useful corroboration, not a complete kernel-wide negative proof. It covers registered resources and identified applications/services. |
| Byte-range locks | A nonblocking LockFileEx probe can test a precisely known range. Process termination or closing the file releases outstanding locks ^9^. |
Every relevant SQLite lock range and mode must be known. Release after process termination can be delayed by system resources. Locks also do not prevent access through mapped views. |
| Mapped views | Owners can explicitly call UnmapViewOfFile; complete mapping closure requires all views to be unmapped and mapping handles closed ^10^. |
QueryWorkingSetEx answers questions about specified virtual addresses, not “does any process map this file?” ^11^. No cited public interface supplies complete system-wide negative proof by pathname. |
| Pending I/O | An owner can track each OVERLAPPED request and wait with GetOverlappedResult; CancelIoEx can request cancellation for a handle ^12^. |
Cancellation is process-local, does not wait, and races with normal completion. Every request’s final completion status must still be collected. Job I/O accounting is cumulative, not a list of outstanding requests. |
| Durable writes | For application-owned mapped writes, use FlushViewOfFile followed by FlushFileBuffers; FlushViewOfFile alone does not flush metadata or guarantee physical completion ^13^. |
A supervisor cannot retroactively perform this sequence without the required mapping base address and writable file handle. It also does not prove SQLite’s logical transaction state. |
Conditions that prevent equivalence
Quiescence cannot equal per-operation disposal proof if any of these remain possible:
- A database-capable process can escape the job, pre-exist outside it, or be launched through an uncontained path.
- A handle can be inherited or duplicated into an untracked process.
- Another account, service, or process can open the database after the logical fence closes.
- The acceptance requirement includes proof that each
CloseHandle, unmap, unlock, cancellation, or flush completed successfully—not merely that no authorized owner remains. - Lock ranges, mapping addresses, or individual asynchronous requests are unknown.
- Forced termination is treated as equivalent to clean SQLite shutdown.
- Storage durability requires evidence through the physical device/cache stack beyond the documented flush contract.
Accordingly, the formal claim should be narrowly stated: containment plus zero-owner verification and explicit storage postconditions establish a controlled quiescent state, but do not reconstruct or prove every hidden native release outcome.
References
- Job Objects
- JOBOBJECT_BASIC_LIMIT_INFORMATION structure (winnt.h)
- Inheritance
- File Handles
- CreateFileW function (fileapi.h)
- WAL-mode File Format - SQLite
- JOBOBJECT_ASSOCIATE_COMPLETION_PORT structure (winnt.h)
- Using Restart Manager with a Primary Installer
- LockFileEx function (fileapi.h)
- Closing a File Mapping Object
- QueryWorkingSetEx function (psapi.h)
- CancelIoEx function (ioapiset.h)
- FlushViewOfFile function (memoryapi.h)