VOOZH about

URL: https://deepwiki.com/hypervel/components/6.4-rate-limiting-and-distributed-locks

⇱ Rate Limiting and Distributed Locks | hypervel/components | DeepWiki


Loading...
Last indexed: 7 March 2026 (96fbab)
Menu

Rate Limiting and Distributed Locks

This document covers the rate limiting and distributed locking capabilities provided by the cache system. Rate limiting allows throttling operations by tracking attempts over time windows, while distributed locks enable coordinating exclusive access to resources across multiple coroutines or processes.

For general cache store architecture and drivers, see Cache Manager and Drivers. For cache store implementations, see Swoole Store and Memory Management and Stack Store and Multi-Tier Caching.


Rate Limiting Architecture

The rate limiting system uses cache stores to track access attempts and enforce configurable throttle limits. The RateLimiter class provides a high-level API for defining named limiters, tracking attempts, and determining when limits are exceeded.

Core Components


Sources: src/cache/src/RateLimiter.php1-187

RateLimiter Class Structure

The RateLimiter class accepts a cache factory in its constructor and maintains a registry of named limiter configurations:

PropertyTypePurpose
$cacheCache (Factory)Cache store implementation for persistence
$limitersarrayRegistry of named limiter closures

Sources: src/cache/src/RateLimiter.php11-30


Named Limiter Registration

The rate limiter supports registering named configurations that can be resolved and applied dynamically. This pattern allows centralized limiter definitions that can be referenced by name throughout the application.

Registration and Resolution


The for() method registers a named limiter by storing a closure that returns limiter configuration:

RateLimiter::for(string $name, Closure $callback): static

The limiter() method retrieves a registered limiter by name:

RateLimiter::limiter(string $name): ?Closure

Sources: src/cache/src/RateLimiter.php33-49


Attempt Tracking and Throttling

The rate limiter uses two cache keys per limiter to track state:

  1. Counter Key ({key}): Stores the number of attempts
  2. Timer Key ({key}:timer): Stores the Unix timestamp when the limit expires

Attempt Flow


Sources: src/cache/src/RateLimiter.php51-112

Core Methods

MethodPurposeReturn Type
attempt(key, maxAttempts, callback, decaySeconds)Execute callback if not rate limitedmixed
tooManyAttempts(key, maxAttempts)Check if key has exceeded limitbool
hit(key, decaySeconds)Increment attempt counterint
attempts(key)Get current attempt countmixed
remaining(key, maxAttempts)Get remaining attemptsint
resetAttempts(key)Clear attempt countermixed
clear(key)Clear both counter and timervoid
availableIn(key)Seconds until key is accessibleint

Sources: src/cache/src/RateLimiter.php54-178

Hit Counter Implementation

The hit() method increments the attempt counter with special handling to prevent memory leaks in race conditions:

  1. Add timer key with expiration timestamp
  2. Add counter key with initial value 0
  3. Increment counter key
  4. If add() failed but increment returns 1, force reset to 1

This ensures the counter doesn't persist beyond its TTL even if the initial add() fails.

Sources: src/cache/src/RateLimiter.php86-112


Key Sanitization

Rate limiter keys are sanitized to remove unicode characters that could cause storage issues:

RateLimiter::cleanRateLimiterKey(string $key): string

The sanitization uses HTML entity encoding to convert unicode characters to ASCII equivalents:


This ensures keys like "jôhn" become "john" for consistent storage across different cache backends.

Sources: src/cache/src/RateLimiter.php180-186 tests/Cache/CacheRateLimiterTest.php150-174


Distributed Lock System

The cache system provides distributed locks for coordinating exclusive access to resources across coroutines and processes. Locks use cache stores for persistence and support ownership verification.

Lock Class Hierarchy


Sources: src/cache/src/CacheLock.php1-75 src/cache/src/FileLock.php1-18

CacheLock Implementation

The CacheLock class uses any cache store implementing the Store interface for lock persistence:

PropertyTypeDescription
$storeStoreCache store for lock storage
$namestringLock identifier key
$secondsintLock TTL in seconds
$ownerstring|nullUnique identifier for lock owner

Sources: src/cache/src/CacheLock.php9-24

Lock Acquisition


The acquire() method attempts to obtain the lock using a two-phase strategy:

  1. Primary Strategy: Use atomic add() if available (prevents race conditions)
  2. Fallback Strategy: Check-then-set using get() followed by put()/forever()

Sources: src/cache/src/CacheLock.php26-46

Lock Release and Ownership


The release() method only releases locks owned by the current process:

CacheLock::release(): bool
  • Checks ownership via isOwnedByCurrentProcess()
  • Returns false if not owned by current process
  • Calls store.forget(name) if owned

The forceRelease() method bypasses ownership checks:

CacheLock::forceRelease(): void
  • Unconditionally removes lock from store
  • Used for cleanup or emergency lock breaking

Sources: src/cache/src/CacheLock.php48-74

Ownership Verification

Lock ownership is determined by comparing the stored owner value with the current lock instance's $owner property:

protected function getCurrentOwner(): string

Returns the owner identifier stored in the cache for this lock name.

Sources: src/cache/src/CacheLock.php69-74


FileLock Specialization

The FileLock class extends CacheLock specifically for file-based cache stores, ensuring atomic lock acquisition:


The FileLock::acquire() method always uses the atomic add() operation, bypassing the fallback strategy:


This ensures file locks are always atomically acquired, preventing race conditions in file-based locking scenarios.

Sources: src/cache/src/FileLock.php7-17


Integration with Cache Stores

Both rate limiters and locks integrate with the cache system through the cache factory and store interfaces.

Store Requirements

FeatureRequired Store Methods
Rate Limitingget(), add(), put(), increment(), forget(), has()
Distributed Locksadd(), get(), put(), forever(), forget()

Lock Provider Interface

Stores can implement the LockProvider interface to expose lock creation capabilities. The CacheManager includes LockProvider in its mixin annotations, indicating repositories provide lock access.

Sources: src/cache/src/CacheManager.php20-24

Store-Specific Lock Implementations

Different cache stores may use specialized lock implementations:

Store TypeLock ConfigurationFile Reference
File StoreUses FileLock, configurable lock directorysrc/cache/src/CacheManager.php212-219
Redis StoreUses CacheLock, configurable lock connectionsrc/cache/src/CacheManager.php233-244
Database StoreUses CacheLock, dedicated cache_locks tablesrc/cache/src/CacheManager.php285-300

Usage Patterns

Rate Limiting Pattern


Distributed Lock Pattern


Combined Pattern: Rate-Limited Lock Acquisition


Sources: src/cache/src/RateLimiter.php1-187 src/cache/src/CacheLock.php1-75


Testing Considerations

Rate Limiter Test Coverage

The rate limiter test suite covers:

  • Lockout detection when limits exceeded
  • Attempt counter increment
  • Memory leak prevention in race conditions
  • Retry counting
  • Key clearing
  • Callback execution behavior
  • Unicode key sanitization

Sources: tests/Cache/CacheRateLimiterTest.php1-175

Lock Test Scenarios

Lock implementations should be tested for:

  • Successful acquisition when lock is available
  • Failed acquisition when lock is held
  • Ownership verification during release
  • Force release regardless of ownership
  • TTL expiration and automatic release
  • Atomic acquisition behavior (especially for FileLock)