Skip to content

Instantly share code, notes, and snippets.

@edamtoft
Last active February 20, 2019 15:52
Show Gist options
  • Save edamtoft/9a72cc535156b9955e00233083b27750 to your computer and use it in GitHub Desktop.
Save edamtoft/9a72cc535156b9955e00233083b27750 to your computer and use it in GitHub Desktop.
Rate Limiting for X-Requests-per-Y-Timespan APIs
public interface IRateLimiter
{
Task<T> RunRateLimited<T>(Func<Task<T>> func);
}
public sealed class SemaphoreRateLimiter : IRateLimiter, IDisposable
{
private readonly SemaphoreSlim _semaphore;
private readonly TimeSpan _span;
public SemaphoreRateLimiter(int count, TimeSpan timespan)
{
_semaphore = new SemaphoreSlim(count);
_span = timespan;
}
public async Task<T> RunRateLimited<T>(Func<Task<T>> func)
{
// Conceptually, this has a semaphore with a lock with the configured
// number of "slots". Once a lock is aquired, that "slot" enters a
// refactory period for the allotted period of time, allowing no more than
// the confiured number of requests within a moving time window.
await AquireLock();
ScheduleRelese();
return await func();
}
Task AquireLock() => _semaphore.WaitAsync();
async void ScheduleRelese()
{
// Normally async voids functions are a bad pattern because you cannot wait for the work to complete
// and any expections basically dissapear into the ether, but in this case, we are basically using it
// as a lazy timer and deliberately firing this off with no expectation of waiting for completion.
await Task.Delay(_span);
_semaphore.Release();
}
public void Dispose() => _semaphore.Dispose();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment