Skip to content

0.15.0

Choose a tag to compare

@domagojmedo domagojmedo released this 18 May 09:46
1ca4c69

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 plain BackgroundService semantics.
  • ServiceScope.Singleton — exactly one instance across the cluster, coordinated via a database lease in background_service_lease. TTL 30 s, renewed every 3 s on the existing Heartbeat task. 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.