ThrottleChannel.Redis 1.0.0

dotnet add package ThrottleChannel.Redis --version 1.0.0
                    
NuGet\Install-Package ThrottleChannel.Redis -Version 1.0.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="ThrottleChannel.Redis" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ThrottleChannel.Redis" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="ThrottleChannel.Redis" />
                    
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 ThrottleChannel.Redis --version 1.0.0
                    
#r "nuget: ThrottleChannel.Redis, 1.0.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 ThrottleChannel.Redis@1.0.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=ThrottleChannel.Redis&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=ThrottleChannel.Redis&version=1.0.0
                    
Install as a Cake Tool

ThrottleChannel

TL;DR

Channel-driven rate gate with capacity tokens, zero-loss cancellation, deterministic shutdown, and optional Redis-backed distributed coordination.

Features

  • Channel-based backpressure: unbounded data channel with separate capacity counter prevents accidental drops.
  • Precise pacing: monotonic Stopwatch scheduling guarantees ≤ configured RPS without timer drift.
  • Immediate cancellation: canceled waiters free capacity instantly and never clog the queue.
  • Deterministic shutdown: DisposeAsync drains the channel, flags waiters, and surfaces OperationCanceledException.
  • Observability baked in: queue depth, cancellations, and wait durations exposed through RateGateMetricsSnapshot.
  • Redis adapter: ThrottleChannel.Redis ships a fixed-window distributed permit acquirer using standard ConnectionMultiplexer patterns.

Installation

dotnet add package ThrottleChannel
# optional distributed integration
dotnet add package ThrottleChannel.Redis

Target frameworks: net8.0, netstandard2.1.

Quick Start

using System.Diagnostics.Metrics;
using ThrottleChannel.RateGate;

using var meter = new Meter("ThrottleChannel.RateGate", "1.0.0");
await using var gate = new FixedRpsGate(
    rps: 1,
    capacity: 1024,
    meter: meter);

foreach (var request in requests)
{
    await gate.WaitAsync(request.CancellationToken);
    await ProcessAsync(request);
}

var metrics = gate.Metrics;
Console.WriteLine($"Granted: {metrics.TotalGranted}, cancelled: {metrics.TotalCancelled}");

ASP.NET Core with Redis coordination

using Microsoft.Extensions.DependencyInjection;
using ThrottleChannel.Distributed;
using ThrottleChannel.RateGate;
using ThrottleChannel.Redis;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddThrottleChannelRedis(
    configuration: builder.Configuration.GetConnectionString("redis")!,
    configure: options =>
    {
        options.Key = "throttle:orders";
        options.PermitsPerWindow = 1;
    });

builder.Services.AddSingleton<FixedRpsGate>(sp =>
{
    var acquirer = sp.GetRequiredService<IDistributedPermitAcquirer>();
    return new FixedRpsGate(rps: 1, capacity: 1024, distributedAcquirer: acquirer);
});
builder.Services.AddSingleton<IRateGate>(sp => sp.GetRequiredService<FixedRpsGate>());

var app = builder.Build();

app.MapPost("/checkout", async (IRateGate gate, CancellationToken ct) =>
{
    await gate.WaitAsync(ct);
    return Results.Ok();
});

app.Run();

FixedRpsGate is registered as a singleton, so the DI container disposes it gracefully on shutdown.

Observability & Aspire

  • Pass a Meter instance (or configure FixedRpsGateFactory with one) to enable automatic publishing through RateGateMetricsPublisher. Metrics are emitted on the ThrottleChannel.RateGate meter.

  • The tests/ThrottleChannel.ServiceDefaults project wires OpenTelemetry exporters. When OTEL_EXPORTER_OTLP_ENDPOINT is set (for example, http://localhost:4317 exposed by the Aspire dashboard), metrics and traces flow to that endpoint.

  • Run the local Aspire orchestrator to see live telemetry and distributed coordination:

    dotnet run --project tests/ThrottleChannel.AppHost/ThrottleChannel.AppHost.csproj
    

    The orchestrator provisions Redis, boots the integration worker, and links the OpenTelemetry pipeline to the Aspire dashboard.

Cancellation semantics

  • WaitAsync throws OperationCanceledException when the caller supplies a canceled token.
  • Tokens canceled while queued release capacity immediately and complete with OperationCanceledException.
  • Shutdown (DisposeAsync) cancels every pending waiter, keeping channel and semaphore in a consistent state.

Why Channel + capacity tokens

Concern Queue + lock Channel + capacity
Backpressure Full queue drops items or blocks producers SemaphoreSlim gates new writers without touching the channel
Cancellation Requires manual removal and lock convoy Token callback flips the TCS and releases capacity instantly
Throughput pacing Timer drift accumulates Monotonic Stopwatch timestamps keep ≤ configured RPS
Shutdown Requires walk over queue under lock Reader naturally drains remaining work with deterministic cancel

Benchmarks

Scenario: 1k permit acquisitions per run, 1 or 10 rps configuration, averaged over 5 iterations. Baseline is legacy linked-list queue + locks (simulated in benchmarks project).

RPS Legacy gate (mean µs) Channel gate (mean µs) Δ
1 (collect after running dotnet run -c Release in benchmarks) (collect after running benchmarks) (pending)
10 (collect after running dotnet run -c Release in benchmarks) (collect after running benchmarks) (pending)

Run locally:

cd benchmarks/ThrottleChannel.Benchmarks
dotnet run -c Release

Benchmarks require restoring NuGet packages. In restricted CI environments provide an offline cache or enable network access.

Redis-backed tests

Redis integration suites rely on Docker via Testcontainers; the tests spin up a disposable Redis container and skip only if the local Docker daemon is unavailable. Make sure Docker Desktop/Colima/Podman is running before executing:

dotnet test ThrottleChannel.sln

CI/CD

  • ci.yml — restore, build, test (with coverage), pack artifacts on every push/PR.
  • release.yml — publish signed packages to NuGet when tagging v*.*.* (requires NUGET_API_KEY).

Repository layout

  • src/ThrottleChannel — core channel-based gate.
  • src/ThrottleChannel.Redis — Redis permit acquirer + DI helpers.
  • tests/ThrottleChannel.Tests — xUnit coverage for cancellation, capacity, pacing, shutdown.
  • tests/ThrottleChannel.TestSupport — Redis Testcontainers helpers and conditional attributes.
  • tests/ThrottleChannel.ServiceDefaults — shared resilience + OpenTelemetry defaults for Aspire-integrated services.
  • benchmarks/ThrottleChannel.Benchmarks — BenchmarkDotNet comparisons.
  • samples/BasicConsole — console demo with multiple producers under 1 RPS gate.
  • tests/ThrottleChannel.AppHost — Aspire orchestrator for integration scenarios.
  • tests/ThrottleChannel.IntegrationWorker — background load generator used in Aspire orchestration.

Roadmap

See AGENTS.md for the full backlog: token bucket, per-key limits, sliding window, concurrency caps, telemetry hooks, NativeAOT.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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
1.0.0 175 10/12/2025