0.15.0
One new opt-in addon — WarpBackgroundService — dashboard-visible analog of .NET's BackgroundService that runs outside the worker pool. No breaking changes; no public API removals.
New: WarpBackgroundService addon
Opt-in via opt.AddBackgroundService<T>(). Lets you register a long-lived background loop that Warp manages — restart-with-backoff, optional cluster-singleton coordination via lease, captured ILogger<T> output in the dashboard. Migration from plain BackgroundService is one line — replace the base class.
public sealed class KafkaDrainService : WarpBackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<KafkaDrainService> _logger;
public KafkaDrainService(IServiceScopeFactory scopeFactory, ILogger<KafkaDrainService> logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
}
public override ServiceScope Scope => ServiceScope.Singleton;
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
using var scope = _scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// ... drain a batch, commit offsets, etc.
await Task.Delay(TimeSpan.FromSeconds(5), ct);
}
}
}
services.AddWarpWorker<AppDbContext>(opt =>
{
opt.UsePostgreSql();
opt.AddBackgroundService<KafkaDrainService>();
});Scope:
ServiceScope.PerServer(default) — every server with the service compiled in runs an independent instance. Matches plainBackgroundServicesemantics.ServiceScope.Singleton— exactly one instance across the cluster, coordinated via a database lease inbackground_service_lease. TTL 30 s, renewed every 3 s on the existingHeartbeattask. Failover ≤ 30 s on hard-kill; ~0 s on graceful shutdown (the holder deletes the lease row before exit).
Lifecycle is always-on. Faults — including a graceful return without cancellation — trigger exponential restart-backoff (1 → 2 → 4 → 8 → 16 → 30s, capped). A successful run of ≥ 5 minutes resets the counter.
Log capture out of the box. Auto-wired ILoggerProvider per running instance — ILogger<TService> calls flow to both your normal log stack and a dashboard-visible background_service_log table. Guardrails: level filter (MinLogLevel, default Information, override per-service), 100 entries/sec rate cap with a 10 s drop window, 4 KB message truncation, 1 000-row / 7-day retention (configurable globally and per-service). Lifecycle events (Started, LeaseAcquired, LeaseLost, Faulted, Restarting, Stopped, ConfigurationMismatch) share the same table with Source = Lifecycle.
Configuration-mismatch detection. Rolling deploys that change Scope across versions are detected and refuse to run user code until the deploy converges — split-brain across the transition window is impossible.
Dashboard. New /warp/services nav (gated via the existing /api/addons discovery flag). List page aggregates per service name; detail page shows per-instance tabs with status / heartbeat / last-error, a lease panel with TTL countdown for singletons, and a filterable log tail.
Telemetry. Four OTel counters — warp.background_services.started, .faulted (tagged service_name + exception_type), .restarts, .lease_lost.
Schema change
Opt-in users get four new tables plus two log indexes (the second one is for the cross-server dashboard log tail — easy to miss):
CREATE TABLE warp.background_service_definition (...);
CREATE TABLE warp.background_service_instance (...);
CREATE TABLE warp.background_service_lease (...);
CREATE TABLE warp.background_service_log (...);
CREATE INDEX ix_background_service_log_per_instance
ON warp.background_service_log (server_id, service_name, id);
CREATE INDEX ix_background_service_log_by_service
ON warp.background_service_log (service_name, id DESC);EF Core auto-migration produces these from the model when opt.AddBackgroundService<T>() triggers the entity configurators. Deployments that don't call AddBackgroundService see no schema change — the entities are absent from the model and the cleanup tasks guard with Model.FindEntityType(...) != null.
Stats
- ~10 k lines source + tests across 128 files
- 1 944 tests (PostgreSQL + SQL Server + NoDb) — zero skipped, zero failures
See website docs — Background Services for the full feature reference and screenshots.