Storage & Corruption Exceptions
Typed failures for storage I/O, CRC32C page corruption, and another-process database locks.
Status: โ Implemented ยท Visibility: Public ยท Level: ๐ฃ Advanced ยท Category: Errors
๐ฏ What it solves
Storage-layer failures need very different responses: a generic I/O error might be transient, a CRC32C mismatch on a primary page never is and demands an operator alert, and a database already opened by another process needs to name the culprit so an operator can decide whether to wait or intervene. Without typed exceptions, all three look like the same opaque IOException/InvalidOperationException, forcing the caller to parse message text to tell them apart.
โ๏ธ How it works (in brief)
StorageException is the subsystem-grouping base (TyphonException, IsTransient = false) for I/O and capacity failures โ catch (StorageException) covers the whole storage layer in one clause. CorruptionException adds ComponentName and PageIndex for any data-integrity violation; its subclass PageCorruptionException is the concrete case thrown when a page's CRC32C checksum doesn't match its contents, and carries the ExpectedCrc/ComputedCrc pair. There is no on-load repair path โ Typhon retired Full-Page-Image repair, so a CRC mismatch on a primary page outside crash recovery is unhealable: the exception is the only outcome, never a silently-patched page. DatabaseLockedException is thrown at database open when another process (or another DatabaseEngine instance) already holds the file, and carries OwnerPid, OwnerMachine, and StartedAt so the caller can report or act on who's holding it.
๐ป Usage
try
{
var dbe = sp.GetRequiredService<DatabaseEngine>();
// ... normal ECS/transaction work โ CRC verification runs transparently on page load
}
catch (DatabaseLockedException ex)
{
log.LogError("database held by PID {Pid} on '{Machine}' since {StartedAt:u}",
ex.OwnerPid, ex.OwnerMachine, ex.StartedAt);
}
catch (PageCorruptionException ex)
{
// never transient โ a primary page failed CRC outside recovery, no on-load repair exists
log.LogCritical(ex, "page {Page} CRC mismatch: stored=0x{Expected:X8} computed=0x{Computed:X8}",
ex.PageIndex, ex.ExpectedCrc, ex.ComputedCrc);
}
catch (StorageException ex)
{
// any other storage-layer failure (I/O, capacity)
log.LogError(ex, "storage failure: {ErrorCode}", ex.ErrorCode);
}
โ ๏ธ Guarantees & limits
StorageExceptionand its subclasses are alwaysIsTransient = falseโ none of these failures resolve themselves on retry; corruption and lock contention both need an external action (restore from backup, close the other process).PageCorruptionExceptionfires only for an unhealable mismatch on a primary page outside crash recovery โ derived structures (secondary indexes, occupancy) are silently rebuilt instead of throwing, and a torn primary page caught during crash recovery either heals (if no live chunk references it) or fails the open loudly with a diagnostic bundle, rather than raising this exception mid-session.DatabaseLockedExceptiononly fires for a same-machine lock held by a live PID, or any lock file from a different machine name (which can't be verified remotely and is always treated as live); a stale lock from a dead local process is cleared automatically and never reaches the caller as an exception.CorruptionException'sPageIndexis-1when the corruption isn't tied to a specific page;PageCorruptionExceptionalways has a real page index.StorageExceptioncarries aStorageCapacityExceeded(2004) error code reserved for capacity-exhaustion failures, but no current throw site uses it โ today only the corruption and lock-contention subclasses are live.- Catching
Exceptioninstead ofStorageExceptionstill works for compatibility, but loses the typedErrorCode/ComponentName/OwnerPid-style diagnostics โ prefer the specific orStorageExceptioncatch.
๐งช Tests
- PageCrcVerificationTests โ a CRC32C mismatch on
OnLoadverification throwsPageCorruptionException; contrasting zero-CRC,RecoveryOnly, and root-page paths that correctly skip verification without throwing. - DatabaseFileLockingTests โ a live same-machine lock and an unverifiable cross-machine lock both throw
DatabaseLockedException(OwnerPid/OwnerMachine); a stale local-PID lock is cleared instead of throwing. - TyphonExceptionTests โ
CorruptionExceptionproperty andIsTransient == falseassertions.
๐ Related
- Source:
src/Typhon.Engine/Errors/public/StorageException.cs,CorruptionException.cs,DatabaseLockedException.cs - Related catalog entries: TyphonException Hierarchy & Catalog
- Related feature: Database File Locking & Lifecycle, Page Integrity โ CRC32C, Seqlock Snapshots & A/B Page Pairing