Rate Limiting in Perl

Here’s the translation of the Go rate limiting example to Perl, formatted in Markdown suitable for Hugo:

Rate limiting is an important mechanism for controlling resource utilization and maintaining quality of service. Perl supports rate limiting through a combination of its threading capabilities and time management functions.

use strict;
use warnings;
use threads;
use Thread::Queue;
use Time::HiRes qw(time sleep);

# 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 queue of the same name.
my $requests = Thread::Queue->new();
for my $i (1..5) {
    $requests->enqueue($i);
}
$requests->end();

# This limiter function will sleep for 200 milliseconds
# between each request. This is the regulator in our
# rate limiting scheme.
sub limiter {
    while (defined(my $req = $requests->dequeue())) {
        sleep(0.2);
        printf("request %d %f\n", $req, time());
    }
}

# By calling the limiter function for each request,
# we limit ourselves to 1 request every 200 milliseconds.
limiter();

# 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 bursty limiter.

my $bursty_requests = Thread::Queue->new();
for my $i (1..5) {
    $bursty_requests->enqueue($i);
}
$bursty_requests->end();

# This bursty_limiter allows bursts of up to 3 events.
sub bursty_limiter {
    my $burst = 3;
    my $last_time = time();
    
    while (defined(my $req = $bursty_requests->dequeue())) {
        my $current_time = time();
        my $time_passed = $current_time - $last_time;
        
        if ($time_passed < 0.2 && $burst > 0) {
            $burst--;
        } elsif ($time_passed < 0.2) {
            sleep(0.2 - $time_passed);
            $burst = 2;
        } else {
            $burst = 3;
        }
        
        $last_time = time();
        printf("request %d %f\n", $req, $last_time);
    }
}

# Now simulate 5 more incoming requests. The first
# 3 of these will benefit from the burst capability
# of bursty_limiter.
bursty_limiter();

Running our program we see the first batch of requests handled once every ~200 milliseconds as desired.

$ perl rate_limiting.pl
request 1 1621234567.123456
request 2 1621234567.323456
request 3 1621234567.523456
request 4 1621234567.723456
request 5 1621234567.923456

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 1621234568.123456
request 2 1621234568.123556
request 3 1621234568.123656
request 4 1621234568.323756
request 5 1621234568.523856

In this Perl implementation, we use the Thread::Queue module to simulate channels, and the Time::HiRes module for high-resolution time functions. The sleep() function is used to implement the rate limiting delay.

The bursty limiter is implemented using a counter ($burst) that allows up to 3 quick requests before enforcing the rate limit. This provides a similar functionality to the buffered channel in the original Go example.

Note that Perl’s threading model is different from Go’s goroutines, so this implementation uses a single-threaded approach for simplicity. In a real-world scenario, you might want to use Perl’s threading capabilities to handle requests concurrently.