Skip to main content

Auto-Freeze

Dispatch automatically optimizes internal caches on application startup using FrozenDictionary. This provides lock-free, high-performance lookups in production without any configuration.

Before You Start

  • .NET 8.0+ (or .NET 9/10 for latest features)
  • Install the required package:
    dotnet add package Excalibur.Dispatch
  • Auto-freeze is enabled by default — no additional setup required

How It Works

When your application starts, Dispatch listens for IHostApplicationLifetime.ApplicationStarted and freezes all internal caches:

// Automatic - happens on startup (default behavior)
var host = builder.Build();
await host.RunAsync();

// Caches freeze automatically when ApplicationStarted fires
// Zero configuration required!

What Gets Frozen

CachePurposeBenefit
Handler invocationCompiled handler delegatesLock-free lookups
Handler registryManual handler registrationsNo synchronization overhead
Handler activationHandler context setupFaster activation
Result factoryMessage result creationOptimized result creation
Middleware evaluationMiddleware applicability metadataFaster middleware filtering

Performance Impact

MetricBefore FreezeAfter FreezeImprovement
Handler lookup~50 ns~5 ns10x faster
Memory overheadSynchronization locksNoneReduced GC pressure
CPU overheadLock contention possibleLock-freeBetter scalability

Configuration

Auto-freeze is enabled by default. No configuration needed:

builder.Services.AddDispatch();

Opt-out for Development

If you need to register handlers at runtime (rare), disable auto-freeze:

builder.Services.Configure<DispatchOptions>(options =>
{
options.Performance.AutoFreezeOnStart = false;
});
Runtime Registration

Disabling auto-freeze means caches remain mutable, using ConcurrentDictionary with synchronization overhead. Only disable if you have a specific need for runtime handler registration.

Hot Reload Detection

Auto-freeze is automatically disabled when hot reload is detected:

  • dotnet watch sets DOTNET_WATCH=1
  • Edit & Continue sets DOTNET_MODIFIABLE_ASSEMBLIES=debug

This ensures handler discovery works correctly during development without any configuration.

Manual Cache Control

For advanced scenarios, use IDispatchCacheManager directly:

public class WarmupService : IHostedService
{
private readonly IDispatchCacheManager _cacheManager;

public WarmupService(IDispatchCacheManager cacheManager)
{
_cacheManager = cacheManager;
}

public Task StartAsync(CancellationToken cancellationToken)
{
// Optionally trigger freeze after custom warmup
_cacheManager.FreezeAll();
return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
}

Cache Status Diagnostics

Check the freeze status programmatically:

public class DiagnosticsController : ControllerBase
{
private readonly IDispatchCacheManager _cacheManager;

public DiagnosticsController(IDispatchCacheManager cacheManager)
{
_cacheManager = cacheManager;
}

[HttpGet("cache-status")]
public IActionResult GetCacheStatus()
{
var status = _cacheManager.GetStatus();

return Ok(new
{
AllFrozen = status.AllFrozen,
FrozenAt = status.FrozenAt,
HandlerInvoker = status.HandlerInvokerFrozen,
HandlerRegistry = status.HandlerRegistryFrozen,
HandlerActivator = status.HandlerActivatorFrozen,
ResultFactory = status.ResultFactoryFrozen,
MiddlewareEvaluator = status.MiddlewareEvaluatorFrozen
});
}
}

CacheFreezeStatus Properties

PropertyTypeDescription
AllFrozenboolTrue if all caches are frozen
FrozenAtDateTimeOffset?Timestamp when freeze occurred
HandlerInvokerFrozenboolHandler invocation cache status
HandlerRegistryFrozenboolHandler registry cache status
HandlerActivatorFrozenboolHandler activation cache status
ResultFactoryFrozenboolResult factory cache status
MiddlewareEvaluatorFrozenboolMiddleware evaluator cache status

Health Check Integration

Add a health check to monitor cache status:

public class CacheHealthCheck : IHealthCheck
{
private readonly IDispatchCacheManager _cacheManager;

public CacheHealthCheck(IDispatchCacheManager cacheManager)
{
_cacheManager = cacheManager;
}

public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken)
{
var status = _cacheManager.GetStatus();

if (status.AllFrozen)
{
return Task.FromResult(HealthCheckResult.Healthy(
$"All caches frozen at {status.FrozenAt}"));
}

// Degraded is OK for development mode
return Task.FromResult(HealthCheckResult.Degraded(
"Caches not frozen (development mode or late registration)"));
}
}

// Register the health check
builder.Services.AddHealthChecks()
.AddCheck<CacheHealthCheck>("dispatch-caches");

Freeze Timing

The freeze occurs at ApplicationStarted, not ApplicationStarting. This ensures:

  1. DI container is fully built
  2. All handlers have been registered
  3. Application is ready to serve requests
Host.Build() -> ConfigureServices -> ApplicationStarting -> ApplicationStarted -> FreezeAll()
^
Caches freeze here

Troubleshooting

Caches Not Freezing

Symptom: GetStatus().AllFrozen returns false in production

Causes:

  1. AutoFreezeOnStart disabled in configuration
  2. Hot reload environment variables set
  3. Application not using generic host

Solution:

// Verify configuration
builder.Services.Configure<PerformanceOptions>(perf =>
{
perf.AutoFreezeOnStart = true; // Explicit enable
});

// Or freeze manually after startup
app.Lifetime.ApplicationStarted.Register(() =>
{
var cacheManager = app.Services.GetRequiredService<IDispatchCacheManager>();
cacheManager.FreezeAll();
});

Performance Degradation

Symptom: Handler lookups slower than expected

Diagnosis:

var status = cacheManager.GetStatus();
if (!status.AllFrozen)
{
_logger.LogWarning("Caches not frozen - using ConcurrentDictionary fallback");
}

Solution: Ensure caches are frozen before handling production traffic.

Best Practices

  1. Let it happen automatically - The default configuration is optimal for most applications

  2. Don't disable without reason - Only disable auto-freeze if you have a specific need for runtime handler registration

  3. Monitor in production - Add a health check to verify caches are frozen

  4. Warmup before freeze - If you have lazy-loaded handlers, ensure they're registered before ApplicationStarted

See Also

Next Steps