Rate Limiting in Ada

with Ada.Text_IO;
with Ada.Calendar;
with Ada.Containers.Unbounded_Synchronized_Queues;
with Ada.Real_Time;

procedure Rate_Limiting is
   use Ada.Text_IO;
   use Ada.Calendar;
   use Ada.Real_Time;

   package Integer_Queues is new Ada.Containers.Unbounded_Synchronized_Queues
     (Element_Type => Integer);

   Requests : Integer_Queues.Queue;
   Bursty_Requests : Integer_Queues.Queue;

   procedure Process_Requests (Q : in out Integer_Queues.Queue; Limiter : in Duration) is
      Next_Time : Time := Clock;
   begin
      while not Q.Is_Empty loop
         delay until Next_Time;
         declare
            Req : Integer;
         begin
            Q.Dequeue (Req);
            Put_Line ("request " & Integer'Image (Req) & " " & Image (Clock));
         end;
         Next_Time := Next_Time + To_Duration (Milliseconds (Limiter));
      end loop;
   end Process_Requests;

   task Bursty_Limiter is
      entry Request;
   end Bursty_Limiter;

   task body Bursty_Limiter is
      Burst_Size : constant := 3;
      Tokens : Natural := Burst_Size;
      Next_Token : Time := Clock;
   begin
      loop
         select
            when Tokens > 0 =>
               accept Request;
               Tokens := Tokens - 1;
         or
            delay until Next_Token;
            Tokens := Natural'Min (Tokens + 1, Burst_Size);
            Next_Token := Next_Token + To_Duration (Milliseconds (200));
         end select;
      end loop;
   end Bursty_Limiter;

begin
   -- First, we'll look at basic rate limiting
   for I in 1 .. 5 loop
      Requests.Enqueue (I);
   end loop;

   Process_Requests (Requests, 200.0);

   -- Now, we'll look at bursty rate limiting
   for I in 1 .. 5 loop
      Bursty_Requests.Enqueue (I);
   end loop;

   while not Bursty_Requests.Is_Empty loop
      Bursty_Limiter.Request;
      declare
         Req : Integer;
      begin
         Bursty_Requests.Dequeue (Req);
         Put_Line ("request " & Integer'Image (Req) & " " & Image (Clock));
      end;
   end loop;
end Rate_Limiting;

This Ada program demonstrates rate limiting, which is an important mechanism for controlling resource utilization and maintaining quality of service. Ada supports rate limiting through tasks, protected objects, and timing events.

First, we look at basic rate limiting. We create a queue of requests and process them at a rate of one request every 200 milliseconds.

Then, we implement a bursty rate limiter. This allows short bursts of requests while preserving the overall rate limit. The Bursty_Limiter task allows bursts of up to 3 events, refilling one token every 200 milliseconds.

To run the program, save it as rate_limiting.adb and use the following commands:

$ gnatmake rate_limiting.adb
$ ./rate_limiting

Running the program, you’ll see the first batch of requests handled once every ~200 milliseconds as desired. For the second batch of requests, you’ll see the first 3 served immediately because of the burstable rate limiting, then the remaining 2 with ~200ms delays each.

This example demonstrates how Ada can be used to implement rate limiting schemes, providing control over resource usage in concurrent systems.