Options Validation
Engine and storage options are range-checked at DI resolution, so a bad configuration throws at startup instead of at first use.
Status: โ Implemented ยท Visibility: Public ยท Level: ๐ฃ Advanced ยท Category: Hosting
๐ฏ What it solves
A misconfigured option โ a zero WAL segment size, a negative checkpoint interval, a database name
the filesystem will reject โ is cheap to catch at startup and expensive to discover later, when the
engine is mid-open and the stack trace points at a subsystem rather than at your Add*() call.
.NET's Options pattern already has the hook for this: an IValidateOptions<T> runs on first
IOptions<T>.Value access and throws OptionsValidationException before the bad value reaches a
service constructor. Typhon wires two real validators into it.
โ๏ธ How it works (in brief)
Two IValidateOptions<T> implementations live in
Hosting/internals/OptionsValidators.cs
and are registered by the Add*() extension that owns each options type:
| Validator | Registered by | Checks |
|---|---|---|
DatabaseEngineOptionsValidator |
AddDatabaseEngine |
Resources is non-null; MaxActiveTransactions, WalRingBufferSizeBytes, CheckpointIntervalMs, CheckpointBarrierTimeoutMs are all > 0; and when Wal is present, its SegmentSize, PreAllocateSegments, StagingBufferSize and GroupCommitIntervalMs |
PagedMMFOptionsValidator<TO> |
AddPagedMMF / AddManagedPagedMMF |
delegates to PagedMMFOptions.Validate(silent, out message) โ DatabaseName, DatabaseDirectory, DatabaseCacheSize well-formedness โ and surfaces its specific rule message |
Failures accumulate rather than short-circuiting, so one startup reports every bad knob instead of
making you fix them one restart at a time. A null Wal is not an error โ the engine derives WAL
defaults when none is supplied, so the validator only checks a WalWriterOptions you actually set.
๐ป Usage
Validation is automatic. There is nothing to call:
services
.AddManagedPagedMMF(o =>
{
o.DatabaseName = "MyGame";
o.DatabaseCacheSize = 4096; // too small โ rejected at DI resolution
})
.AddDatabaseEngine(o =>
{
o.Resources.CheckpointIntervalMs = 0; // non-positive โ rejected too
});
// Throws OptionsValidationException naming the offending rule, before the engine opens a file.
using var provider = services.BuildServiceProvider();
var engine = provider.GetRequiredService<DatabaseEngine>();
PagedMMFOptions.IsValid / Validate(bool silent, out string) remain public, so you can also
check a configuration you built by hand before handing it to DI. That is the same method the
validator calls โ one source of truth for storage-config rules, two entry points.
โ ๏ธ Guarantees & limits
- A validator is only registered when you pass a
configuredelegate to theAdd*()call.AddDatabaseEngine()with no delegate registers nothing to validate, because there is nothing the caller configured โ defaults are valid by construction. - Range-checking is not budgeting. Nothing sums your allocations against a memory ceiling: a
configuration where every individual knob is in range can still ask for more RAM than the machine
has. The
ResourceOptions.TotalMemoryBudgetBytes/Validate()pair that once implied otherwise was removed in #148 โ it governed no allocation. - Validation runs at first
IOptions<T>.Valueaccess, which for these types is the first resolution of the service that consumes them โ not atBuildServiceProvider(). MemoryAllocatorOptionsandResourceRegistryOptionshave no validator today; theirAdd*()extensions configure without registering one.
๐งช Tests
- ResourceOptionsTests โ asserts the shipped
ResourceOptionsdefaults are sensible, plus theExhaustionPolicy/ResourceExhaustedExceptionsurface
๐ Related
- Source:
OptionsValidators.cs(both validators),TyphonBuilderExtensions.cs(the two registration sites),PagedMMFOptions.csโIsValid/Validate - Parent feature: Engine Options Configuration Surface
- Sibling: DI Engine Bootstrap Chain โ the
Add*()calls these validators hang off - Sibling: Resource Budgets & Options โ what the
Resourcesknobs actually control