Rate Limiting in Lua Here’s the translation of the Go rate limiting example to Lua, formatted in Markdown suitable for Hugo:
Rate limiting is an important mechanism for controlling resource utilization and maintaining quality of service. Lua can support rate limiting using coroutines and timers.
local clock = os.clock
-- Simulate a simple channel
local function make_channel ( size )
local channel = {}
local queue = {}
local closed = false
function channel . send ( value )
if closed then error ( "send on closed channel" ) end
if # queue < size then
table.insert ( queue , value )
else
coroutine.yield ()
end
end
function channel . receive ()
if # queue == 0 and closed then return nil end
while # queue == 0 do
coroutine.yield ()
end
return table.remove ( queue , 1 )
end
function channel . close ()
closed = true
end
return channel
end
-- Simulate time.Tick
local function tick ( interval )
local channel = make_channel ( 1 )
local function ticker ()
while true do
channel.send ( clock ())
coroutine.yield ( interval )
end
end
coroutine.wrap ( ticker )()
return channel
end
local function main ()
-- 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 channel of the
-- same name.
local requests = make_channel ( 5 )
for i = 1 , 5 do
requests.send ( i )
end
requests.close ()
-- This limiter channel will receive a value
-- every 200 milliseconds. This is the regulator in
-- our rate limiting scheme.
local limiter = tick ( 0.2 )
-- By blocking on a receive from the limiter channel
-- before serving each request, we limit ourselves to
-- 1 request every 200 milliseconds.
while true do
local req = requests.receive ()
if not req then break end
limiter.receive ()
print ( string.format ( "request %d %f" , req , clock ()))
end
-- 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
-- buffering our limiter channel. This burstyLimiter
-- channel will allow bursts of up to 3 events.
local burstyLimiter = make_channel ( 3 )
-- Fill up the channel to represent allowed bursting.
for i = 1 , 3 do
burstyLimiter.send ( clock ())
end
-- Every 200 milliseconds we'll try to add a new
-- value to burstyLimiter, up to its limit of 3.
coroutine.wrap ( function ()
for t in coroutine.wrap ( function () while true do coroutine.yield ( tick ( 0.2 ). receive ()) end end ) do
burstyLimiter.send ( t )
end
end )()
-- Now simulate 5 more incoming requests. The first
-- 3 of these will benefit from the burst capability
-- of burstyLimiter.
local burstyRequests = make_channel ( 5 )
for i = 1 , 5 do
burstyRequests.send ( i )
end
burstyRequests.close ()
while true do
local req = burstyRequests.receive ()
if not req then break end
burstyLimiter.receive ()
print ( string.format ( "request %d %f" , req , clock ()))
end
end
main ()
Running our program we see the first batch of requests handled once every ~200 milliseconds as desired.
$ lua rate-limiting.lua
request 1 0.000039
request 2 0.200161
request 3 0.400284
request 4 0.600407
request 5 0.800530
For the second batch of requests we serve the first 3 immediately because of the burstable rate limiting, then serve the remaining 2 with ~200ms delays each.
request 1 0.800568
request 2 0.800576
request 3 0.800582
request 4 1.000705
request 5 1.200828
This Lua implementation simulates the behavior of Go’s channels and goroutines using coroutines and custom channel implementations. The tick
function mimics Go’s time.Tick
, and the make_channel
function creates a simple channel-like structure. The overall logic of rate limiting remains the same, demonstrating both basic and bursty rate limiting.