Rate Limiting in Idris
Rate limiting is an important mechanism for controlling resource utilization and maintaining quality of service. Idris supports rate limiting through its powerful type system and effect handling capabilities.
import Data.IORef
import System.Concurrency
import System.Clock
-- First we'll look at basic rate limiting. Suppose
-- we want to limit our handling of incoming requests.
-- We'll serve these requests off a list of the same name.
main : IO ()
main = do
requests <- newIORef [1, 2, 3, 4, 5]
-- This limiter function will introduce a delay
-- every 200 milliseconds. This is the regulator in
-- our rate limiting scheme.
let limiter = do
threadDelay 200000 -- 200 milliseconds
pure ()
-- By calling the limiter function before serving each request,
-- we limit ourselves to 1 request every 200 milliseconds.
let processRequests = do
reqs <- readIORef requests
case reqs of
[] -> pure ()
(req::rs) => do
limiter
putStrLn $ "request " ++ show req ++ " " ++ show !time
writeIORef requests rs
processRequests
-- Process the requests
processRequests
-- We may want to allow short bursts of requests in
-- our rate limiting scheme while preserving the
-- overall rate limit. We can accomplish this by
-- using a semaphore. This burstyLimiter will allow
-- bursts of up to 3 events.
burstyLimiter <- newSemaphore 3
-- Fill up the semaphore to represent allowed bursting.
replicateM_ 3 $ signal burstyLimiter
-- Every 200 milliseconds we'll try to add a new
-- value to burstyLimiter, up to its limit of 3.
fork $ forever $ do
threadDelay 200000
signal burstyLimiter
-- Now simulate 5 more incoming requests. The first
-- 3 of these will benefit from the burst capability
-- of burstyLimiter.
burstyRequests <- newIORef [1, 2, 3, 4, 5]
let processBurstyRequests = do
reqs <- readIORef burstyRequests
case reqs of
[] -> pure ()
(req::rs) => do
wait burstyLimiter
putStrLn $ "request " ++ show req ++ " " ++ show !time
writeIORef burstyRequests rs
processBurstyRequests
-- Process the bursty requests
processBurstyRequests
Running our program, we would see the first batch of requests handled once every ~200 milliseconds as desired.
For the second batch of requests, we would serve the first 3 immediately because of the burstable rate limiting, then serve the remaining 2 with ~200ms delays each.
Note that Idris doesn’t have built-in channels like Go, so we’ve used IORef
for shared state and semaphores for synchronization. The fork
function is used to start a new thread, similar to Go’s goroutines. The threadDelay
function is used to introduce delays, measured in microseconds.
Also, Idris doesn’t have a built-in way to get the current time with microsecond precision, so we’ve used a placeholder time
function. In a real implementation, you’d need to use a library or FFI to get the current time with high precision.
This example demonstrates how Idris’s powerful type system and effect handling can be used to implement rate limiting, even though the specific mechanisms differ from Go’s channels and tickers.