ThrottleChannel.Redis
1.0.0
dotnet add package ThrottleChannel.Redis --version 1.0.0
NuGet\Install-Package ThrottleChannel.Redis -Version 1.0.0
<PackageReference Include="ThrottleChannel.Redis" Version="1.0.0" />
<PackageVersion Include="ThrottleChannel.Redis" Version="1.0.0" />
<PackageReference Include="ThrottleChannel.Redis" />
paket add ThrottleChannel.Redis --version 1.0.0
#r "nuget: ThrottleChannel.Redis, 1.0.0"
#:package ThrottleChannel.Redis@1.0.0
#addin nuget:?package=ThrottleChannel.Redis&version=1.0.0
#tool nuget:?package=ThrottleChannel.Redis&version=1.0.0
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
Stopwatchscheduling guarantees ≤ configured RPS without timer drift. - Immediate cancellation: canceled waiters free capacity instantly and never clog the queue.
- Deterministic shutdown:
DisposeAsyncdrains the channel, flags waiters, and surfacesOperationCanceledException. - Observability baked in: queue depth, cancellations, and wait durations exposed through
RateGateMetricsSnapshot. - Redis adapter:
ThrottleChannel.Redisships a fixed-window distributed permit acquirer using standardConnectionMultiplexerpatterns.
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
Meterinstance (or configureFixedRpsGateFactorywith one) to enable automatic publishing throughRateGateMetricsPublisher. Metrics are emitted on theThrottleChannel.RateGatemeter.The
tests/ThrottleChannel.ServiceDefaultsproject wires OpenTelemetry exporters. WhenOTEL_EXPORTER_OTLP_ENDPOINTis set (for example,http://localhost:4317exposed 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.csprojThe orchestrator provisions Redis, boots the integration worker, and links the OpenTelemetry pipeline to the Aspire dashboard.
Cancellation semantics
WaitAsyncthrowsOperationCanceledExceptionwhen 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 taggingv*.*.*(requiresNUGET_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 | Versions 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. |
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.9)
- StackExchange.Redis (>= 2.9.25)
- ThrottleChannel (>= 1.0.0)
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 |