# Rate Limit Policy API **Unit**: `Murphy.Policy.RateLimit` The Rate Limit policy controls the rate of operations to prevent system overload using a token bucket algorithm. ## Overview Rate limiting prevents: - API throttling (exceeding third-party rate limits) - Resource exhaustion (too many concurrent operations) - System overload (protecting your own services) - Uncontrolled bursts of activity ## Table of Contents - [ERateLimitRejectedException Class](#eratelimitrejectedexception-class) - [IRateLimitPolicy Interface](#iratelimitpolicy-interface) - [TRateLimitPolicy Class](#tratelimitpolicy-class) - [TRateLimitBuilder Class](#tratelimitbuilder-class) - [Usage Examples](#usage-examples) --- ## ERateLimitRejectedException Class Exception thrown when the rate limit has been exceeded. ### Declaration ```pascal type ERateLimitRejectedException = class(Exception) end; ``` **Description**: Simple exception class indicating that the rate limit was exceeded. **Usage**: ```pascal try RateLimitPolicy.Execute(procedure begin MakeAPICall; end); except on E: ERateLimitRejectedException do begin WriteLn('Rate limit exceeded - too many calls'); // Wait and retry, or handle appropriately end; end; ``` --- ## IRateLimitPolicy Interface The interface for the Rate Limit policy pattern. ### Declaration ```pascal type IRateLimitPolicy = interface(IPolicy) ['{B4AFFB0E-C958-4D89-A2BC-79659915581E}'] function Allow(ACalls: Integer): IRateLimitPolicy; procedure Execute(AProc: TProc); function Within(ADuration: TTimeSpan): IRateLimitPolicy; end; ``` ### Methods #### Allow ```pascal function Allow(ACalls: Integer): IRateLimitPolicy; ``` **Description**: Configures the maximum number of calls allowed within the time window. **Parameters**: - `ACalls: Integer` - Maximum number of calls allowed **Returns**: `IRateLimitPolicy` - Self for method chaining **Default**: `20` calls **Example**: ```pascal // Allow 100 calls Policy := TRateLimitBuilder .Handle(Exception) // Exception type doesn't affect rate limiting .Allow(100) .Within(TTimeSpan.FromMinutes(1)) .Build; ``` #### Execute ```pascal procedure Execute(AProc: TProc); ``` **Description**: Executes the provided procedure if rate limit allows, otherwise throws `ERateLimitRejectedException`. **Parameters**: - `AProc: TProc` - Anonymous procedure to execute **Behavior**: 1. Checks if token bucket has expired (time window passed) 2. If expired: Refills bucket with tokens 3. If tokens available: Consumes one token and executes `AProc` 4. If no tokens available: Throws `ERateLimitRejectedException` **Example**: ```pascal try RateLimitPolicy.Execute( procedure begin WriteLn('Making API call...'); CallAPI; end); except on E: ERateLimitRejectedException do WriteLn('Rate limit exceeded!'); end; ``` #### Within ```pascal function Within(ADuration: TTimeSpan): IRateLimitPolicy; ``` **Description**: Configures the time window for the rate limit. **Parameters**: - `ADuration: TTimeSpan` - Time window duration **Returns**: `IRateLimitPolicy` - Self for method chaining **Default**: `1 second` **Example**: ```pascal // 100 calls per minute Policy := TRateLimitBuilder .Handle(Exception) .Allow(100) .Within(TTimeSpan.FromMinutes(1)) .Build; // 10 calls per second Policy := TRateLimitBuilder .Handle(Exception) .Allow(10) .Within(TTimeSpan.FromSeconds(1)) .Build; // 1000 calls per hour Policy := TRateLimitBuilder .Handle(Exception) .Allow(1000) .Within(TTimeSpan.FromHours(1)) .Build; ``` --- ## TRateLimitPolicy Class Concrete implementation of the Rate Limit policy using token bucket algorithm. ### Declaration ```pascal type TRateLimitPolicy = class(TPolicy, IRateLimitPolicy) private FAllowedCalls: Integer; FBucketIds: TStack; FBucketTime: TDateTime; FWithinDuration: TTimeSpan; protected function IsBucketExpired: Boolean; public constructor Create(AExceptionTypes: TArray); override; destructor Destroy; override; function Allow(ACalls: Integer): IRateLimitPolicy; procedure Execute(AProc: TProc); function Within(ADuration: TTimeSpan): IRateLimitPolicy; end; ``` ### Constructor ```pascal constructor Create(AExceptionTypes: TArray); override; ``` **Defaults**: - `FAllowedCalls`: `20` - `FWithinDuration`: `1 second` - `FBucketTime`: `MinDateTime` (will trigger immediate bucket fill) - `FBucketIds`: Empty stack ### Protected Methods #### IsBucketExpired ```pascal function IsBucketExpired: Boolean; ``` **Description**: Checks if the current token bucket time window has expired. **Returns**: `Boolean` - `True` - Time window has expired, bucket should be refilled - `False` - Still within current time window **Implementation** (Murphy.Policy.RateLimit.pas:105): ```pascal Result := (FBucketTime + FWithinDuration) < Now; ``` ### Token Bucket Algorithm The implementation uses a simple token bucket: 1. **Bucket Creation**: When expired or first use, fill bucket with `FAllowedCalls` tokens (GUIDs) 2. **Token Consumption**: Each `Execute` call pops one token from the bucket 3. **Rejection**: If bucket is empty, throw `ERateLimitRejectedException` 4. **Refill**: When time window expires, clear and refill bucket **Characteristics**: - Allows bursts (can use all tokens immediately) - Simple and predictable - No gradual token replenishment (refills completely when window expires) --- ## TRateLimitBuilder Class Builder class for creating rate limit policies. ### Declaration ```pascal type TRateLimitBuilder = class sealed(TPolicyBuilder) public class function Handle(AExceptionTypes: TArray): IRateLimitPolicy; override; end; ``` ### Class Methods #### Handle ```pascal class function Handle(AExceptionTypes: TArray): IRateLimitPolicy; override; ``` **Description**: Creates a rate limit policy. **Note**: Exception type parameter is not used by rate limiting logic (inherited from base), but provided for consistency. **Usage**: ```pascal Policy := TRateLimitBuilder .Handle(Exception) // Can use any exception type .Allow(50) .Within(TTimeSpan.FromMinutes(1)) .Build; ``` --- ## Usage Examples ### Example 1: Basic Rate Limiting ```pascal var Policy: IRateLimitPolicy; I: Integer; begin // Allow 5 calls per 10 seconds Policy := TRateLimitBuilder .Handle(Exception) .Allow(5) .Within(TTimeSpan.FromSeconds(10)) .Build; for I := 1 to 10 do begin try Policy.Execute( procedure begin WriteLn(Format('Call %d at %s', [I, TimeToStr(Now)])); MakeAPICall; end); except on E: ERateLimitRejectedException do begin WriteLn(Format('Call %d rejected - rate limit exceeded', [I])); Sleep(2000); // Wait before retrying end; end; end; end; ``` ### Example 2: API Client with Rate Limiting ```pascal type TAPIClient = class private FRateLimitPolicy: IRateLimitPolicy; public constructor Create; function Get(const AURL: string): string; end; constructor TAPIClient.Create; begin // GitHub API: 60 requests per hour for unauthenticated requests FRateLimitPolicy := TRateLimitBuilder .Handle(Exception) .Allow(60) .Within(TTimeSpan.FromHours(1)) .Build; end; function TAPIClient.Get(const AURL: string): string; begin try FRateLimitPolicy.Execute( procedure begin Result := HTTP.Get(AURL); end); except on E: ERateLimitRejectedException do begin raise Exception.Create('API rate limit exceeded. Please wait before making more requests.'); end; end; end; ``` ### Example 3: Rate Limiting with Retry on Rejection ```pascal var Policy: IRateLimitPolicy; MaxRetries: Integer; RetryCount: Integer; begin Policy := TRateLimitBuilder .Handle(Exception) .Allow(10) .Within(TTimeSpan.FromSeconds(1)) .Build; MaxRetries := 5; RetryCount := 0; while RetryCount < MaxRetries do begin try Policy.Execute( procedure begin ProcessItem; end); Break; // Success - exit retry loop except on E: ERateLimitRejectedException do begin Inc(RetryCount); WriteLn(Format('Rate limited. Retry %d/%d...', [RetryCount, MaxRetries])); Sleep(200); // Wait 200ms before retry end; end; end; if RetryCount >= MaxRetries then raise Exception.Create('Failed after maximum retries due to rate limiting'); end; ``` ### Example 4: Multiple Rate Limits (Different Time Windows) ```pascal var PerSecondPolicy: IRateLimitPolicy; PerMinutePolicy: IRateLimitPolicy; begin // Twitter-style: 15 calls per 15 minutes AND max 1 per second PerSecondPolicy := TRateLimitBuilder .Handle(Exception) .Allow(1) .Within(TTimeSpan.FromSeconds(1)) .Build; PerMinutePolicy := TRateLimitBuilder .Handle(Exception) .Allow(15) .Within(TTimeSpan.FromMinutes(15)) .Build; // Apply both limits try PerSecondPolicy.Execute( procedure begin PerMinutePolicy.Execute( procedure begin MakeAPICall; end); end); except on E: ERateLimitRejectedException do WriteLn('Rate limit exceeded'); end; end; ``` ### Example 5: Batch Processing with Rate Limiting ```pascal procedure ProcessItemsWithRateLimit(Items: TList); var Policy: IRateLimitPolicy; Item: string; ProcessedCount: Integer; begin // Process max 100 items per minute Policy := TRateLimitBuilder .Handle(Exception) .Allow(100) .Within(TTimeSpan.FromMinutes(1)) .Build; ProcessedCount := 0; for Item in Items do begin try Policy.Execute( procedure begin WriteLn(Format('Processing: %s', [Item])); ProcessItem(Item); Inc(ProcessedCount); end); except on E: ERateLimitRejectedException do begin WriteLn(Format('Rate limit reached after %d items. Waiting...', [ProcessedCount])); Sleep(60000); // Wait 1 minute for bucket to reset // Retry this item Policy.Execute(procedure begin ProcessItem(Item); end); end; end; end; WriteLn(Format('Processed %d items', [ProcessedCount])); end; ``` --- ## Important Notes ### Token Bucket Behavior The current implementation: - **Refills completely** when time window expires (not gradually) - **Allows bursts** - all tokens can be consumed immediately - **Resets on expiration** - doesn't carry over unused tokens Example: ```pascal // Allow 10 calls per minute Policy := TRateLimitBuilder.Handle(Exception).Allow(10).Within(TTimeSpan.FromMinutes(1)).Build; // You can make 10 calls immediately (burst) for I := 1 to 10 do Policy.Execute(procedure begin CallAPI; end); // All succeed // 11th call fails Policy.Execute(procedure begin CallAPI; end); // Throws ERateLimitRejectedException // After 1 minute, bucket refills and you can make 10 more calls ``` ### Thread Safety **Current Status**: Rate limit policy is **not thread-safe**. **Recommendations**: - Use separate policy instances per thread - Or protect shared instance with synchronization primitives (TMonitor, TCriticalSection) ### Combining with Other Patterns Rate limiting works well with: - **Retry**: Retry after delay when rate limit exceeded - **Circuit Breaker**: Prevent overwhelming rate-limited services - **Fallback**: Provide cached data when rate limit prevents fresh data fetch --- ## See Also - [Rate Limit Pattern Guide](../Patterns/RateLimit.md) - Comprehensive usage guide - [Combining Patterns](../Patterns/Combining-Patterns.md) - Using rate limit with other policies - [Best Practices](../Advanced/Best-Practices.md) - Production recommendations - [Base Policy API](Base-Policy.md) - Inherited functionality --- [← Fallback Policy](Fallback-Policy.md) | [API Reference Index](README.md) | [Timeout Policy →](Timeout-Policy.md)