LevelUp.Bifrost.Scheduling.Testing 0.5.0

dotnet add package LevelUp.Bifrost.Scheduling.Testing --version 0.5.0
                    
NuGet\Install-Package LevelUp.Bifrost.Scheduling.Testing -Version 0.5.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LevelUp.Bifrost.Scheduling.Testing" Version="0.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LevelUp.Bifrost.Scheduling.Testing" Version="0.5.0" />
                    
Directory.Packages.props
<PackageReference Include="LevelUp.Bifrost.Scheduling.Testing" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add LevelUp.Bifrost.Scheduling.Testing --version 0.5.0
                    
#r "nuget: LevelUp.Bifrost.Scheduling.Testing, 0.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package LevelUp.Bifrost.Scheduling.Testing@0.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=LevelUp.Bifrost.Scheduling.Testing&version=0.5.0
                    
Install as a Cake Addin
#tool nuget:?package=LevelUp.Bifrost.Scheduling.Testing&version=0.5.0
                    
Install as a Cake Tool

Bifrost

Build License .NET

A work-orchestration library for .NET 10.

Bifrost runs background work (such as emails, webhooks, and sandboxed jobs) on a pool of workers behind a bounded channel. You give it a work type and a handler; it owns admission, dispatch, retries, dead-lettering, autoscaling, and observability around them. It's built on System.Threading.Channels, trim- and AOT-compatible, offers a priority dispatch backed by a state-of-the-art, scalable concurrent priority queue, and is split into focused packages so you depend only on the parts you use.

How it works

Define a work type and a handler, register them, enqueue:

public record EmailJob(string To, string Subject, string Body);

public class EmailHandler(IEmailService email) : IWorkHandler<EmailJob>
{
    public ValueTask HandleAsync(EmailJob job, CancellationToken ct) =>
        email.SendAsync(job.To, job.Subject, job.Body, ct);
}

services.AddWorkOrchestrator<EmailJob>(o =>
{
    o.Capacity = 128;
    o.WorkerCount = 2;
})
.WithHandler<EmailHandler>();

Enqueue from anywhere you've injected IWorkOrchestrator<EmailJob>:

var result = await orchestrator.EnqueueAsync(new EmailJob(to, "Welcome", body));
if (!result.IsAccepted)
{
    // result.Reason is CapacityExceeded, WatermarkExceeded, or Shutdown.
    // A full queue is a return value, not an exception you have to catch.
}

What you compose on

Past the core queue, every capability is an opt-in decorator you add to the orchestrator.

  • Autoscaling. Workers scale on queue utilization between a floor and a ceiling, with high/low watermarks and a cooldown.
  • Resilience. Polly retry, timeout, and circuit breaker around each handler call.
  • Dead-letter queue. Work that exhausts its retries, or gets rejected at admission, lands in a DLQ instead of vanishing.
  • Health checks. ASP.NET Core health check integration.
  • OpenTelemetry. Queue-wait and rejection metrics, tagged by work class.
  • Event stream. Subscribe to enqueue, complete, and dead-letter events.
  • Priority dispatch. Class-aware ordering when interactive and batch work share a queue. Off by default; see below.
services.AddWorkOrchestrator<EmailJob>(/* ... */)
    .WithHandler<EmailHandler>()
    .WithAutoscaling(s => { s.MinWorkers = 1; s.MaxWorkers = 16; s.HighWatermark = 0.8; s.LowWatermark = 0.3; })
    .WithResilience(r => { r.RetryCount = 3; r.UseExponentialBackoff = true; })
    .WithHealthChecks()
    .WithOpenTelemetry();

Packages

Package What it is
LevelUp.Bifrost.Core Abstractions, zero dependencies
LevelUp.Bifrost The orchestrator and its decorators
LevelUp.Bifrost.Concurrency Concurrent priority queues (MultiQueue + locking), no Bifrost dependencies
LevelUp.Bifrost.HealthChecks ASP.NET Core health checks
LevelUp.Bifrost.OpenTelemetry Metrics
LevelUp.Bifrost.Resilience Polly integration
dotnet add package LevelUp.Bifrost

Priority dispatch

By default everything runs through one strict-FIFO queue. With some workloads, it is useful to reorder queue elements. Priority dispatch provides this functionality with class-aware ordering.

Measure. Tag work with a WorkClass (Interactive, Default, Batch) and enable OpenTelemetry. The bifrost.orchestrator.queue_wait histogram shows what each class actually waits.

services.AddWorkOrchestrator<SandboxJob>(/* ... */)
    .WithClassifier(job => job.UserInitiated ? WorkClass.Interactive : WorkClass.Batch)
    .WithOpenTelemetry();

Enable once a threshold you picked in advance gets crossed (say, interactive p95 queue-wait over 500 ms while batch work is co-resident with latency-sensitive work):

services.AddWorkOrchestrator<SandboxJob>(/* ... */).UsePriorityDispatch();

Priority dispatch orders the queue by a virtual-time key, so interactive work jumps ahead by at most a bounded window (default 30s), and anything that has waited longer than the window outranks fresh arrivals. That window is the starvation bound. Under pressure, admission sheds the lowest class first: Batch at 0.90× capacity, Default at 0.95, Interactive to full.

Two bindings ship, and Auto (the default) picks one at construction from hardware processor count and capacity:

Binding Ordering Built for
Locking Exact min-key under a global lock Few workers, longer work items, low contention
MultiQueue Relaxed two-choice, bounded rank error Many workers hammering numerous work items

When to stay on FIFO

  • You have no measured head-of-line problem. FIFO keeps producer-wait backpressure and exact ordering; don't trade those away for a problem you haven't seen.
  • Your producers can't handle rejection. Priority dispatch rejects at admission instead of blocking. If that doesn't work for you, even with DLQ routing, stay on FIFO.
  • You need strict ordering. Priority dispatch reorders by design, and MultiQueue relaxes ordering further within the priority order.
  • Your work doesn't run through IWorkOrchestrator. Bifrost earns its keep as a pipeline — resilience, DLQ, autoscaling, and observability around the queue. For standalone job scheduling, NCronJob or TickerQ fit better; the scheduling design doc covers the comparison.

The interface

public interface IWorkOrchestrator<TWork> : IAsyncDisposable
{
    // Admission outcomes are values, not exceptions. A rejected enqueue returns
    // EnqueueResult.Rejected(reason); only a canceled ct throws.
    ValueTask<EnqueueResult> EnqueueAsync(TWork work, WorkClass workClass = WorkClass.Default, CancellationToken ct = default);
    bool TryEnqueue(TWork work, WorkClass workClass = WorkClass.Default);

    void Run(TWork work, WorkClass workClass = WorkClass.Default);    // throws when full
    bool TryRun(TWork work, WorkClass workClass = WorkClass.Default); // false when full

    int PendingCount { get; }
    int ActiveWorkers { get; }
    int Capacity { get; }

    Task StopAsync(CancellationToken ct = default);
    Task DrainAsync(CancellationToken ct = default);
}

Handlers implement one method:

public interface IWorkHandler<TWork>
{
    ValueTask HandleAsync(TWork work, CancellationToken ct);
}

Build

git clone https://github.com/lvlup-sw/bifrost.git
cd bifrost
dotnet build src/Bifrost.sln -c Release
dotnet test --solution src/Bifrost.sln

Tests run on TUnit over the Microsoft Testing Platform, so they take --solution or --project rather than a bare dotnet test. Design notes live in docs/design and docs/designs.

License

Apache 2.0. See LICENSE.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.5.0 40 6/16/2026