Durability (WAL / BulkLoad / Commit) Exceptions
Typed, fail-fast failures from the WAL writer, the commit pipeline's durability wait, and BulkLoad session lifecycle.
Status: โ Implemented ยท Visibility: Public ยท Level: ๐ฃ Advanced ยท Category: Errors
๐ฏ What it solves
WAL and commit failures are not interchangeable: a disk write that fails outright must stop the engine from accepting further durable commits, a WAL claim that's larger than the whole ring buffer can never succeed no matter how many times it's retried, and an Immediate-mode commit whose post-publish fsync wait times out has already committed โ it is not a failure to retry. Treating all of these as one generic I/O exception would force callers to parse messages to tell "restart the engine" apart from "this commit is fine, just unconfirmed." BulkLoad sessions add their own lifecycle failures (concurrent session, closed session, stuck checkpoint) that don't map to any standard transaction exception either. This feature gives each distinct outcome its own typed, catchable exception.
โ๏ธ How it works (in brief)
All seven types inherit DurabilityException : TyphonException, so catch (DurabilityException) is the subsystem-wide umbrella. WalWriteException wraps a fatal WAL writer I/O failure (ADR-020's dedicated writer thread) โ not transient; the engine stops accepting durable commits and needs a restart. WalClaimTooLargeException and WalSegmentException are buffer-sizing and segment-file errors, also not transient โ they're configuration problems, not contention. CommitDurabilityUncertainException is the AP-02 point-of-no-return signal: by the time it's thrown, Append and Publish already succeeded and the transaction is committed and visible โ only the durability confirmation (the post-publish FUA wait) didn't land in time. The three BulkLoadSession exceptions cover its own exclusivity gate, post-close calls, and the CompleteBulkLoad checkpoint barrier (BulkLoad Write Path covers the session mechanics; this entry covers only its exceptions).
๐ป Usage
using var uow = db.CreateUnitOfWork(DurabilityMode.Immediate);
using var tx = uow.CreateTransaction();
UpdateAccountBalance(tx, accountId, newBalance);
try
{
tx.Commit();
}
catch (CommitDurabilityUncertainException ex)
{
// Committed and visible โ APPEND + PUBLISH already ran. Do NOT retry the transaction.
// Poll ex.HighLsn against DurabilityLog.DurableLsn to learn when it becomes durable.
LogDurabilityUncertain(ex.HighLsn);
}
catch (WalWriteException)
{
// Fatal WAL I/O โ the engine will reject further durable commits. Escalate to restart.
InitiateEngineRestart();
}
try
{
using var bulk = db.BeginBulkLoad();
// ... bulk.Spawn / bulk.Update ...
bulk.CompleteBulkLoad();
}
catch (BulkLoadCheckpointTimeoutException ex)
{
// Session is still open โ caller may retry CompleteBulkLoad() or Dispose() to discard.
}
| Type | Error code | IsTransient |
Notable properties |
|---|---|---|---|
WalWriteException |
WalWriteFailure (7003) |
false |
inner exception only |
WalClaimTooLargeException |
WalClaimTooLarge (7002) |
false |
RequestedBytes, BufferCapacity |
WalSegmentException |
WalSegmentError (7004) |
false |
SegmentPath |
CommitDurabilityUncertainException |
CommitDurabilityUncertain (7008) |
false |
HighLsn |
BulkSessionAlreadyActiveException |
BulkSessionAlreadyActive (7005) |
false |
ActiveBulkSessionId |
BulkSessionClosedException |
BulkSessionClosed (7006) |
false |
BulkSessionId |
BulkLoadCheckpointTimeoutException |
BulkLoadCheckpointTimeout (7007) |
false |
BulkSessionId, Timeout |
โ ๏ธ Guarantees & limits
WalWriteExceptionis unconditionally fatal โ there is no retry path; it signals the dedicated WAL writer thread itself failed (ADR-020), and the engine stops accepting durable commits until restarted.CommitDurabilityUncertainExceptionmeans "committed, durability unconfirmed," never a rollback โIsTransientisfalsespecifically to stop callers from auto-retrying a transaction that already happened.WalClaimTooLargeException/WalSegmentExceptionare not transient: a larger buffer or a fixed segment path is needed before the same operation can succeed.- The three
BulkLoadSessionexceptions are scoped to that session's own state machine โBulkSessionAlreadyActiveExceptionfrom a secondBeginBulkLoad,BulkSessionClosedExceptionfrom any call afterCompleteBulkLoad/Dispose,BulkLoadCheckpointTimeoutExceptiononly fromCompleteBulkLoad(the session stays open afterward โ retry or dispose). - All seven inherit
DurabilityException, socatch (DurabilityException)catches the whole family uniformly for logging/alerting, while specific catches still get typed properties (HighLsn,SegmentPath, etc.). - None of these are part of the
TyphonTimeoutExceptionfamily, even thoughBulkLoadCheckpointTimeoutExceptionis deadline-driven โ it stays underDurabilityExceptionbecause the BulkLoad session, unlike a lock wait, remains alive and resumable after the timeout.
๐งช Tests
- WalCommitBufferTests โ an over-capacity claim throws
WalClaimTooLargeException; a ring-buffer claim that outlives its deadline throwsWalBackPressureTimeoutException. - BulkLoadApiSurfaceTests โ a second concurrent
BeginBulkLoadthrowsBulkSessionAlreadyActiveException(ActiveBulkSessionId); any call afterDispose/CompleteBulkLoadthrowsBulkSessionClosedException(CompleteBulkLoad,Destroy).
๐ Related
- Related catalog entries: TyphonException Hierarchy & Catalog, Commit Pipeline, BulkLoad Write Path, Bulk Load Session