Skip to content

Instantly share code, notes, and snippets.

@george-polevoy
Created April 5, 2023 15:37
Show Gist options
  • Save george-polevoy/56ed54b8586fc4ed0c233f4636f84693 to your computer and use it in GitHub Desktop.
Save george-polevoy/56ed54b8586fc4ed0c233f4636f84693 to your computer and use it in GitHub Desktop.
Simple C# channel-based token bucket limiter

Timings

0.001096, 0.101212, 0.2020205, 0.3030929, 0.4042182, 0.5052511, 0.6066207, 0.7077272, 0.8092904, 0.9107451
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading.Channels;
namespace SimpleRequestLimiting;
public static class TokenBucketLimiter
{
private enum Void { }
public static Func<CancellationToken, Task> CreateBucketLimiter(double rate, int maxBucketSize,
CancellationToken refillCancellationToken)
{
var chan = Channel.CreateBounded<Void>(maxBucketSize);
_ = RefillAsync(chan.Writer, rate, refillCancellationToken);
return async cancellationToken => { await chan.Reader.ReadAsync(cancellationToken); };
}
private static async Task RefillAsync(ChannelWriter<Void> w, double rate, CancellationToken cancellationToken)
{
var delaySeconds = 1.0 / rate;
while (true)
{
await w.WriteAsync(new Void(), cancellationToken);
await Task.Delay(TimeSpan.FromSeconds(delaySeconds), cancellationToken);
}
}
}
public class Tests
{
[Test]
public async Task RequestRateIsLimited()
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
var cancellationToken = cts.Token;
var limiter = TokenBucketLimiter.CreateBucketLimiter(10, 2, cancellationToken);
var timings = new ConcurrentQueue<double>();
var sw = Stopwatch.StartNew();
var workers = Enumerable.Range(0, 100).Select(async _ =>
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
await limiter(cancellationToken);
timings.Enqueue(sw.Elapsed.TotalSeconds);
}
catch (OperationCanceledException)
{
}
}
}).ToList();
await Task.WhenAll(workers);
TestContext.WriteLine(string.Join(", ", timings));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment