Table of Contents

Engine Options Configuration Surface

IOptions<T>-based configuration for every engine service, set with a configure delegate on each Add*() call.

Status: โœ… Implemented ยท Visibility: Public ยท Level: ๐Ÿ”ต Core ยท Category: Hosting

๐ŸŽฏ What it solves

Bootstrapping a DatabaseEngine means tuning several independent subsystems โ€” page cache size, WAL directory and durability knobs, lock timeouts, MVCC cleanup cadence, transient storage, and background statistics. Hardcoding these in constructors would force every consumer to know the engine's internal wiring and recompile for any tuning change. The Options Configuration Surface gives every DI registration a single, idiomatic .NET pattern โ€” a configure delegate bound through Microsoft.Extensions.Options โ€” to set these values at startup without touching how the services are constructed or wired together.

โš™๏ธ How it works (in brief)

Each Add* extension (AddDatabaseEngine, AddManagedPagedMMF, AddPagedMemoryMappedFile, AddMemoryAllocator, AddResourceRegistry) calls services.AddOptions<TOptions>() and applies your configure delegate via .Configure(...). The resulting IOptions<TOptions> is resolved inside that service's factory when the container first builds it, so values are effectively frozen at BuildServiceProvider() for singleton registrations. DatabaseEngineOptions is a composite: it groups per-subsystem option types (Resources, Timeouts, DeferredCleanup, Wal, Transient, Statistics) instead of one flat bag of properties, so you only touch the knobs you need.

๐Ÿ’ป Usage

Most apps set options through the one-line AddTyphon surface โ€” its Configure* delegates target the very option types this page documents:

services.AddTyphon(o => o
    .DatabaseFile(@"C:\Data\MyGame\game.typhon")
    .PageCacheSize(512UL * 1024 * 1024)                            // 512 MiB page cache (default is 256 MiB)
    .ConfigureEngine(e =>
    {
        e.Resources.MaxActiveTransactions = 2000;
        e.Wal.UseFUA       = false;                                // GroupCommit workload, no per-write FUA
        e.Statistics       = null;                                 // disable background stats worker
    }));

var engine = services.BuildServiceProvider().GetRequiredService<DatabaseEngine>();

AddTyphon folds those Configure* delegates into the per-Add* configuration surface below โ€” reach for it directly when you need finer control over service lifetimes, or want to configure or substitute a single subsystem. configure is optional on every Add* call; omit it to run on defaults.

var services = new ServiceCollection();
services.AddLogging(b => b.AddFilter((_, level) => level >= LogLevel.Warning));

services
    .AddResourceRegistry()
    .AddMemoryAllocator()
    .AddEpochManager()
    .AddHighResolutionSharedTimer()
    .AddDeadlineWatchdog()
    .AddManagedPagedMMF(opts =>
    {
        opts.DatabaseName      = "MyGame";
        opts.DatabaseDirectory = @"C:\Data\MyGame";
        opts.DatabaseCacheSize = 65536UL * 8192;      // 512 MiB page cache
    })
    .AddDatabaseEngine(engineOpts =>
    {
        engineOpts.Resources.MaxActiveTransactions = 2000;
        engineOpts.Wal.UseFUA       = false;          // GroupCommit workload, no per-write FUA
        engineOpts.Statistics       = null;           // disable background stats worker
    });

var provider = services.BuildServiceProvider();
var engine   = provider.GetRequiredService<DatabaseEngine>();
Options type Registered by Key knobs
DatabaseEngineOptions AddDatabaseEngine Resources, Timeouts, DeferredCleanup, Wal, Transient, Statistics
PagedMMFOptions / ManagedPagedMMFOptions AddPagedMemoryMappedFile / AddManagedPagedMMF DatabaseName, DatabaseDirectory, DatabaseCacheSize
MemoryAllocatorOptions AddMemoryAllocator Name (diagnostics label only)
ResourceRegistryOptions AddResourceRegistry Name (diagnostics label only)

โš ๏ธ Guarantees & limits

  • configure is optional on every Add* call โ€” omit it to run on defaults (256 MiB page cache; WAL in ./wal; UseFUA = true; etc.). The page cache has an 8 MiB minimum floor; running near it risks PageCacheBackpressureTimeout, so a below-recommended size logs a startup warning.
  • Options are read once, effectively at BuildServiceProvider() for singletons โ€” mutating an already-resolved IOptions<T>.Value afterward has no effect on a running engine.
  • Calling the same Add*() more than once accumulates Configure callbacks (every registered delegate runs, in order) rather than replacing the previous one โ€” avoid double-registration.
  • Scoped/Transient variants (AddScopedDatabaseEngine, AddTransientManagedPagedMemoryMappedFile, etc.) accept the same configure delegate, but mixing lifetimes across the dependency chain risks a captive-dependency failure under BuildServiceProvider(validateScopes: true) โ€” the canonical setup is all-singleton.
  • MemoryAllocatorOptions and ResourceRegistryOptions are configurable but, in practice, only expose a diagnostics Name today โ€” there is nothing else to tune on them.
  • See Options Validation Hooks for what configuration mistakes are โ€” and are not โ€” caught before they reach the engine.

๐Ÿงช Tests

  • ResourceOptionsTests โ€” DatabaseEngineOptions.Resources composite property and its defaults (DatabaseEngineOptions_HasResourceOptionsProperty / _ResourceOptions_HasDefaults)
  • TransactionChainTests โ€” TransactionChainMaxActiveTests fixture configures AddScopedDatabaseEngine(options => options.Resources.MaxActiveTransactions = ...) and proves the configure delegate value actually reaches and is enforced by the running engine