Add a first-party rate limiter package - #480
Conversation
Extract the 64-stripe lock coordinator into Core so Cache, Reverb, and the rate limiter share one pre-fork-safe primitive. Cover deterministic key striping, ascending multi-lock acquisition, shared-stripe deduplication, bounded contention, rollback, and reverse-order release.
Replace Cache’s package-local Atomic lock implementation with the shared Core primitive while preserving row and all-lock behavior. Move generic contention, timeout, and rollback coverage to Core and retain Cache’s real cross-process store concurrency coverage.
Refresh each manager’s cached configuration repository when tests swap the application container. Cover both MultipleInstanceManager and MailManager so subsequent resolutions read the replacement application without rebuilding existing instances.
Fail before executing callbacks when Parallel::wait() is called without an active coroutine. Prevent Coroutine::join() from terminating callers without a useful diagnostic and prove misuse produces no callback side effects.
Introduce the dedicated hypervel/rate-limiter component with immutable fixed-window, GCRA leaky-bucket, unlimited, and exponential-backoff policies. Add atomic Redis Lua, Swoole numeric-table, database, and worker-array stores; typed decisions; named store management; pruning and table commands; default config and provider wiring; integration workflows; corruption checks; and concurrency coverage. Use native store operations instead of generic cache serialization and return complete one-call admission decisions.
Add Testbench’s worker-array rate-limiter default and standard rate_limits migration. Update migration inventory, refresh and rollback coverage, and default-config assertions so tests can opt into database limiting without forcing ordinary framework tests through SQLite.
Rewrite route throttling around atomic typed decisions and one backend-neutral middleware path. Preserve Laravel-facing helpers, headers, named limiters, stacked policies, response predicates, and custom responses while removing Redis-specific middleware state and switches. Keep decision data request-local and select Redis as an ordinary named store.
Move RateLimited and ThrottlesExceptions onto named rate-limiter stores with serializable store selectors. Preserve release timing, dontRelease, exception predicates, reporting callbacks, and Laravel interoperability while deleting Redis-specific middleware subclasses. Use the post-failure decision when concurrent failures open the circuit.
Replace Fortify’s cache counter calls with one canonical fixed-window policy shared by inspect, consume, and clear operations. Preserve attempts, lockout, retry, username, guard, and IP scoping, plus successful-login clearing, while using typed rate-limiter results.
Use AdmissionPolicy and atomic limiter attempts for exception-report throttling. Remove redundant key hashing and the hash opt-out while preserving Lottery, Unlimited, custom keys, and application overrides through the broader typed policy return.
Use the worker-array rate-limiter store for per-connection Reverb message limits and clear matching policy state on close. Replace Reverb’s duplicate Atomic stripe implementation with the shared pre-fork Core lock while retaining call-site ordering, deduplication, and post-release reporting coverage.
Remove Cache’s legacy limiter implementation, binding, config, tests, and timer wrapper now that every consumer uses the dedicated component. Move Swoole cache maintenance onto Coordinator Timer with validate-all, resolve-all, register ordering, captured stores, registration rollback, and worker-exit cancellation. Add the missing direct Swoole and coordinator package requirements and retain real worker-recycle coverage.
Route the existing blocking concurrency and duration limiters through evalWithShaCache instead of resending Lua bodies on every operation. Preserve connection selection, prefixes, cluster routing, builder APIs, and results while covering NOSCRIPT fallback and native-versus-script error classification.
Run no-merge config publishing through Testbench’s framework-configuration bootstrap and a disposable destination. Assert every target is absent before publication so tests cannot pass by comparing Foundation config sources to themselves, while retaining the intentional empty-stub branch.
Add a developer-only end-to-end benchmark for fixed-window and leaky-bucket decisions through the real manager, pools, drivers, and result decoding. Report throughput and latency percentiles for allowed-heavy, denied-heavy, single-client, and contended workloads without adding a production command.
Document typed policies, decisions, direct APIs, stores, configuration, database migration and pruning, distribution guarantees, failure behavior, and custom drivers. Point facade users at the canonical Hypervel RateLimiter manager and keep rate-limiting.md as the single user guide.
Update routing and middleware guidance for named stores, weighted costs, leaky buckets, response-based charging, headers, and the single throttle middleware. Remove the old Redis middleware switch and explain backend selection through rate-limiter stores.
Update queue, authentication, exception-reporting, and starter-kit examples to use the dedicated rate-limiter policies and store selectors. Remove Redis-specific queue middleware and old Cache namespace examples while preserving Laravel-style task-focused prose.
Record the dedicated Hypervel RateLimiter namespace and typed atomic API as an intentional Laravel difference for agents and porting work. Direct Cache users to the canonical rate-limiting guide and correct the Redis integration workflow inventory.
Remove completed legacy limiter and middleware defects from the framework TODO list. Retain the focused future INCREX revisit and connection-owned backend capability detection work with their real ecosystem constraints.
Bring the committed implementation plan into exact agreement with the reviewed final code. Record the final database locking, timer startup, shared clock, Redis portability, Testbench, Swoole release, consumer, testing, and cleanup decisions used during implementation.
Add a public working rule that prevents agents from spawning or delegating work to subagents unless the user explicitly requests or approves their use. This keeps delegation under the user's control across all work in the components repository.
Rewrite the canonical rate limiting guide around typed rate limits, atomic stores, fixed windows, leaky buckets, weighted operations, backoff, store selection, pruning, custom drivers, identity rules, and failure behavior. Align routing and queue guidance with the unified limiter API, registered store selection, ordered consumption, response-based charging, result headers, and queue backoff semantics while preserving the wording and examples inherited from Laravel wherever no Hypervel adaptation is required. Record public Laravel differences in the package and Foundation READMEs, fix the Redis throttle description and facade table of contents, document the retained native Redis optimization work, and keep benchmark terminology consistent with the public API. Add the matching real-time facade removal marker to the Foundation bootstrap test so the intentional omission is recorded alongside its source and README documentation.
Show the exact pruneExpired method required by custom durable stores so driver authors can integrate with the rate-limiter:prune command without leaving the canonical documentation.
Rename the LeakyBucket factory duration parameters and validation labels from decay to period so named arguments match the continuously replenished GCRA model. Convert each public duration unit directly to microseconds. This removes a redundant checked conversion and factory call while preserving every accepted range and reporting overflow errors in the unit supplied by the caller. Document the period multiplier with its sustained-rate meaning, update the implementation plan, and cover every named factory argument and unit-specific overflow boundary.
Bring the rate limiter branch up to date with the latest database transaction ownership, pooled SQLite cleanup, queue backoff typing, notification parity, gRPC documentation, package metadata, and agent guidance from 0.4. Resolve the cache package metadata conflict by retaining the rate limiter branch requirements for Swoole and Coordinator while adding the upstream Symfony Console dependency. The reviewed upstream behavior requires no rate limiter implementation changes.
PhpRedis only defines COMPRESSION_LZF when the extension is compiled with LZF support. The Redis and Valkey CI jobs use a phpredis build without optional compression codecs, so resolving the constant failed before the limiter behavior could be exercised. Guard the integration test with the same capability check used by the existing Redis and cache test suites. Builds with LZF continue to run the serializer and compression assertions, while builds without it report a clear skip instead of an error.
Warn that database limiter rows require scheduled pruning to prevent the rate_limits table from continuing to grow. Restrict the worker-array store guidance to automated tests, explaining its per-worker isolation and retained expired-key memory in both the public documentation and framework/Testbench configuration.
Inject a directly composed worker-local limiter into the Pusher server so internal per-connection admission cannot be changed by application rate-limiter store configuration. Allow key resolvers to omit named scope callbacks for direct package composition. Deliver the 4301 response before terminating rate-limited WebSocket connections, preserve termination when error reporting fails, require complete enabled settings, and normalize numeric config values from environment-backed or custom application providers. Expand Reverb and key resolver coverage for configuration independence, limiter cleanup, delivery ordering, failure cleanup, numeric strings, and deterministic transport behavior. Document the public Reverb settings and record the final design in the implementation plan.
Bring the completed rate limiter work onto the current 0.4 routing, validation, facade-documenter, testing, and CI baseline. Resolve the overlapping routing and Redis changes by preserving the dedicated Hypervel rate limiter APIs and unified throttle middleware while adopting the incoming routing correctness fixes, DurationLimiter result correction, generated facade metadata, and package dependency updates. Keep Redis integration suites grouped by service and run them through bounded ParaTest workers for Redis and Valkey. Record the shared Redis and Queue test-harness requirements in the implementation plan and clarify the reserved Reverb integration database guidance. Verified with all affected routing and Redis groups, the full 27,644-test framework suite, Testbench, package-mode dogfood, PHPStan, PHP CS Fixer, and the complete live-server Reverb integration suite.
Normalize every configured Redis connection to the current test database in both sequential and ParaTest runs. Named cache, session, queue, and Reverb connections now share the worker-isolated database instead of leaking through their configured database numbers. Reject non-empty connection URLs because a URL path is merged after raw connection options and would override the worker database. Keep empty URLs equivalent to unset values, skip reserved Redis configuration groups, and retain one FLUSHDB because first-party integration workflows expose one service and all named connections now select the same database. Remove the dead token-to-database trait helper and extend the focused harness coverage for sequential and parallel normalization, reserved entries, empty URLs, URL rejection before flushing, and lightweight opt-in setup.
Adapt QueueTestCase to Hypervel's automatically invoked InteractsWithRedis hooks instead of Laravel-only manual setup calls. Gate Redis setup on the live queue.default value and remember successful setup so teardown remains correct even when a test changes the configured driver. Remove the stale driver snapshot and its subclass synchronization assignments, restoring 121 sync and database tests that were silently skipped without REDIS_HOST. Normalize WorkCommandTest's environment setup order now that the driver is read from live configuration. Add an end-to-end Redis queue worker fixture under the service-specific integration directory so CI exercises the Redis lifecycle branch and proves a queued job is dispatched and processed through the real driver.
Bring in the latest View lifecycle audit, Pagination correctness work, request-owned timing support, portable migration APIs, and console and Testbench fixes from 0.4. Resolve the Testbench application conflict by keeping 0.4's removal of the duplicate static-state cleanup method. The authoritative AfterEachTestSubscriber retains this branch's removal of the obsolete ThrottleRequests reset while adopting the new View cleanup methods.
|
Important Review skippedToo many files! This PR contains 205 files, which is 105 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (205)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR replaces the cache-backed limiter with a dedicated first-party rate-limiter package and adds fixed-window, weighted sliding-window, leaky-bucket, and backoff policies.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains in the eligible follow-up review scope. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| src/rate-limiter/src/Concerns/CalculatesRateLimits.php | Implements shared fixed-window, sliding-window, leaky-bucket, and backoff calculations; reviewed sliding-window weighting, rotation, denial immutability, and retry arithmetic. |
| src/rate-limiter/src/RedisStore.php | Adds atomic Lua-backed limiter operations whose examined sliding-window arithmetic and state transitions match the PHP implementation. |
| src/rate-limiter/src/DatabaseStore.php | Adds transactional persistent limiter state using primary reads, locking, expiry, and secondary sliding-window values. |
| src/rate-limiter/src/SwooleStore.php | Adds key-scoped locked shared-table operations using the common policy calculations. |
| src/rate-limiter/src/WorkerArrayStore.php | Adds worker-local limiter state for tests and explicitly owned worker-local use cases. |
| src/rate-limiter/src/SlidingWindow.php | Defines the immutable weighted sliding-window policy and its admission-policy modifiers. |
| src/routing/src/Middleware/ThrottleRequests.php | Migrates route throttling to atomic typed limiter decisions, including named, stacked, weighted, and response-based policies. |
| src/queue/src/Middleware/RateLimited.php | Migrates queue rate limiting to named stores and serializable typed middleware state. |
| src/foundation/config/rate-limiter.php | Adds first-party rate-limiter store configuration and selects the database-backed default. |
| src/rate-limiter/src/Console/stubs/rate-limits.stub | Defines the persistent schema required by database-backed policies, including secondary sliding-window state. |
Reviews (2): Last reviewed commit: "Merge remote-tracking branch 'origin/0.4..." | Re-trigger Greptile
Record the approved weighted two-window algorithm, numeric bounds, retry arithmetic, and per-store state transitions.\n\nDocument the Redis one-key Lua design, schema changes, public API, test matrix, benchmark coverage, and deliberate exclusions so the implementation can be reviewed against one complete specification.
Introduce an immutable SlidingWindow policy with the same Laravel-style period factories and modifiers as the existing admission policies. Add stable key fingerprints and validate capacity, cost, exact-integer limits, and the complete two-window lifetime before store access.\n\nAlso make Limit factories validate their complete public-unit conversion so overflow errors consistently name the seconds, minutes, hours, or days supplied by the caller. Cover factories, immutability, key identity, validation order, boundaries, and error messages.
Add the constant-state weighted two-window transition to the shared calculator, including millisecond quantization, exact retry inversion, logical rotation, rollback-safe weighting, and immutable inspection and denial paths. Wire worker-array, Swoole, and database stores through the same behavior.\n\nGeneralize the shared second state value to secondary_value across memory, Swoole, database, generated migrations, and Testbench. Stop duplicating fixed-window expiry in that field and add exhaustive arithmetic, corruption, expiry, pruning, rotation, contention, schema, and database-driver coverage.
Implement sliding-window admission as one atomic cached Lua execution over a single two-field hash. Derive window position from PTTL, preserve raw reset timing, use HINCRBY for steady-state increments, and avoid Redis TIME, serializer paths, version branching, and additional keys or round trips.\n\nExercise initial, steady, rotated, denied, rollback, malformed-state, expiry, prefix, serializer, compression, NOSCRIPT fallback, and weighted contention behavior against the Redis integration store.
Extend the common store contract with weighted admission, denial and inspection immutability, boundary recovery, expiry, clearing, and parameter isolation so every first-party backend proves the same public behavior.\n\nAdd routing and queue integration coverage showing named sliding-window limits flow through the existing middleware paths and expose their remaining capacity and retry timing without consumer-specific source changes.
Add allowed-heavy and denied-heavy sliding-window cases to the existing Redis, Swoole, and database benchmark matrix. Keep the benchmark on the real manager and store paths so it measures connection handling, Lua execution or shared calculation, result decoding, and cleanup rather than an isolated arithmetic helper.
Explain when to choose fixed windows, sliding windows, leaky buckets, or backoff in the first-party rate-limiting guide. Document SlidingWindow factories, immutable modifiers, first-hit anchoring, weighted approximation, result timing, store support, and practical consumption examples in Laravel-style prose.\n\nUpdate the concise routing, queue, and package-difference references so every public policy list agrees without duplicating the main guide.
Update the original package design record to include SlidingWindow in the final typed-policy inventory, source layout, shared state model, documentation surface, and completion checklist. Point detailed arithmetic and backend behavior to the focused sliding-window plan while retaining Redis backoff's intentionally semantic available_at field.
# Conflicts: # src/foundation/README.md
|
@coderabbitai @greptile-apps Please review the latest changes. Since the previous review, this PR now:
Please take a fresh look at the sliding-window arithmetic, Redis atomic path, cross-store parity, schema change, public API consistency, performance characteristics, and test coverage. |
|
|
Summary
This PR replaces Hypervel's cache-backed rate limiter with a new first-party
hypervel/rate-limiterpackage.The package provides:
The public
Hypervel\Support\Facades\RateLimiterfacade remains in the same location. Rate limit definitions and the concrete manager now live underHypervel\RateLimiter.In local end-to-end fixed-window benchmarks, the dedicated Redis store delivered 2.7–5.5 times the throughput of the cache-backed implementation.
Why this is a package
Hypervel inherited Laravel's rate limiter design. Laravel places its limiter in the Cache component and injects a cache repository into it. Operations such as checking a limit, recording a hit, reading the remaining capacity, and reading the retry delay are separate cache calls.
That design has a few costs:
tooManyAttempts,hit,remaining, andavailableIninstead of receiving one result from one state change.The Redis path partly works around these limits with Redis-specific classes. This improves that one backend, but it also creates two implementations of the same middleware behavior and two ways to select rate limiting.
Rate limiting has its own state transitions, algorithms, timing, and result data. Other ecosystems treat it as its own component: Symfony has a RateLimiter component, Go provides
golang.org/x/time/rate, and .NET providesSystem.Threading.RateLimiting. Hypervel now takes the same general approach while using stores designed for its Swoole runtime and distributed backends.Performance
The dedicated store contract lets each backend perform one native rate limit decision instead of coordinating several general cache operations.
The old and new fixed-window paths were compared on the same machine and backend connections. Each repository booted through its own Testbench application and
vendordirectory. The environment used PHP 8.4.23, Swoole 6.2.2, phpredis 6.3.0, a local Redis server, and SQLite with identical durability settings.The old accepted path called
tooManyAttempts,hit, andremaining; its denied path calledtooManyAttemptsandavailableIn. The new path calledconsumeonce and read the decision, remaining capacity, and retry delay from its result.Each value below is the median of five independent runs:
Redis runs used 5,000 measured operations, 100 warmup operations, and one or 16 clients contending for the same key. SQLite runs used 300 measured operations, 20 warmup operations, and one client. Concurrent SQLite measurements were omitted because tagged Swoole releases still contain the AIO scheduler stall fixed by Swoole PR #6140.
Redis benefits directly from replacing several cache calls with one cached Lua execution. SQLite's accepted path is largely dominated by the durable transaction commit that both implementations require, while its denied path more clearly shows the benefit of returning one native decision.
These are local comparison results, not production capacity figures.
API
Rate limits are immutable typed values. The concrete type selects the algorithm, so there is no strategy string followed by an array of unrelated options.
Limitdefines a fixed window.SlidingWindowdefines a weighted sliding window.LeakyBucketdefines a continuously replenishing GCRA limit.Unlimitedbypasses storage.Backoffdefines failure-driven exponential delays.Admission rate limits share
by,cost,globally,after, andresponsemodifiers. Fixed windows, sliding windows, and leaky buckets provide the familiarperSecond,perMinute,perMinutes,perHour, andperDayfactories.The main operations are:
consumeto atomically check and consume capacity;inspectto read a decision without changing state;attemptto consume capacity before running a callback;recordFailureto update exponential backoff state; andclearto remove state.Consuming capacity
LimitResultcontainsallowed,denied,limit,remaining,retryAfter, andresetAfter. The store returns all of this data from the same decision. Callers do not issue another read after consuming capacity.Sliding windows
The sliding window is anchored by its first accepted operation. It keeps the current and previous counts, then gradually reduces how much the previous count contributes as the current window passes. This smooths the sharp reset at a fixed-window boundary while using constant state and bounded work on every store.
Leaky buckets and named stores
This sustains 100 operations per second and allows an initial burst of 200. The named limiter may be used by both route and queue middleware.
Applications may also select a store for a direct operation:
Store names accept strings or enums. Custom stores may be registered through the manager's existing
extendpattern.Response-based limits
Named route limits may decide whether to consume capacity after the response is known:
Hypervel inspects the limit before running the route and consumes capacity only when the callback returns
true.Exponential backoff
Backoff tracks failures rather than admitted requests. It is separate from the admission rate limit hierarchy because recording a failure is a different operation from consuming request capacity.
BackoffResultcontains the decision, failure count, and retry delay.Stores
Each store implements the rate limiter's small native-operation contract. Stores receive a validated policy and an already resolved physical key, then perform the complete state transition themselves.
Swoole\Tableprotected by striped locksAll first-party stores implement the same fixed-window, sliding-window, leaky-bucket, and backoff semantics. A denied consume does not change state or extend its expiry. Inspection does not create state. Invalid or corrupt state throws instead of silently allowing work.
Redis
Admission decisions, inspections, and failure records each use one Lua script. The normal path uses
EVALSHA, with the existingNOSCRIPTfallback on first use. Clearing state uses oneDEL. Fixed and sliding windows derive their position from key expiry, while leaky buckets and backoff use Redis server time. Each script sets expiry in the same atomic operation as the state change.Rate limiter state does not pass through cache serialization or compression. Each operation uses one key, so the scripts can be routed by Redis Cluster without a cross-slot operation. The same portable scripts run against Redis 8 and Valkey 9.
Database
The database store uses a dedicated
rate_limitstable with compact numeric state:MySQL, MariaDB, and PostgreSQL use row locks. SQLite acquires its writer lock before reading state because it does not implement
FOR UPDATE. Redis remains the recommended store for high-throughput distributed limiting; the database store is the portable shared fallback.The matching Hypervel application skeleton update includes this migration for fresh applications. Existing applications may generate it with:
Expired database rows are removed by
rate-limiter:prune, which should be scheduled regularly:The database store deliberately does not delete expired rows during a rate limit check. Without the prune command, the
rate_limitstable will continue growing as new limiter keys are encountered.Database limiter mutations cannot run inside an existing transaction on the selected connection. Doing so could allow an outer rollback to undo an accepted charge and would disable the limiter's own transaction retry behavior. Applications that need this may configure a separate rate limiter connection.
Swoole
The Swoole store allocates its table and locks before workers fork. It keeps three native integer columns and performs each operation in one short key-scoped critical section. It does not serialize PHP values.
Worker zero prunes expired entries and reports table pressure. If the table cannot allocate a new row after pruning expired state, the store throws instead of evicting a live rate limit and allowing excess traffic.
The Swoole store is shared by workers belonging to one server master. It is not a distributed store across independent server instances or hosts.
Worker array
The configured worker-array store is for automated tests only. Its state is not shared across workers or servers, and expired untouched keys remain in memory until the worker exits.
The underlying store remains available as a low-level package primitive for framework code with a proven worker-local ownership model. Reverb uses it directly for per-connection message limits, where one worker owns the connection for its full lifetime and clears its key when the connection closes. This internal use does not depend on application rate limiter configuration.
Key identity
Every physical key is a fixed 32-character hash. The identity includes the application prefix, named limiter and optional scope, caller key, policy type, stable algorithm settings, and global scope.
Operation cost and callbacks do not change identity. This allows the same rate limit to charge different costs. Changing the algorithm settings starts fresh state while the old entry expires naturally, and
clearmust receive the same settings that created the state.Keys are always hashed. There is no runtime switch that can cause one worker to address the same rate limit differently from another.
Framework integration
The package is installed and registered by default. Foundation owns
config/rate-limiter.php, and named stores merge using the same configuration rules as other manager-backed components. Fresh applications use the database store by default. Testbench uses the worker-array store while still providing the standard database migration for tests that select the database store.This PR moves all framework rate limiting onto the new API:
ThrottleRequestsimplementation for every store;RateLimitedandThrottlesExceptionsmiddleware use the same store selection and result types;attempt;Routing still supports named limiters,
throttle:60,1,ThrottleRequests::using,ThrottleRequests::with, custom 429 responses, response-based limits, and stacked limits. Queue middleware can use the store registered on a named limiter or override it withstore('redis').Stacked rate limits are consumed in order. If a later limit denies an operation, earlier accepted charges remain. An all-or-nothing multi-key operation cannot be implemented consistently across every store or Redis Cluster without adding races or a new distributed transaction system.
Laravel compatibility
This deliberately differs from Laravel in one area.
The facade remains
Hypervel\Support\Facades\RateLimiter, and familiar definition APIs such asRateLimiter::for,Limit::perMinute,by,after, andresponseremain. Route middleware syntax also remains familiar.The implementation is no longer exposed as
Hypervel\Cache\RateLimiter, and there is no compatibility shim under the Cache namespace. The old primitive counter methods and Redis-specific middleware classes are removed.A shim would create two plausible entry points for the same feature:
That is harder for developers and coding models to reason about than one documented difference. Code using
Hypervel\RateLimiteris an explicit signal that Hypervel's rate limiter has a different API from Laravel's cache-backed implementation.The removed Redis-specific surfaces include:
ThrottleRequestsWithRedis;RateLimitedWithRedis;ThrottlesExceptionsWithRedis;throttleWithRedismiddleware switch; andRedis is now selected through
rate-limiter.default, the optional store onRateLimiter::for, or the middlewarestoremodifier.Supporting changes
The new Swoole store needed the same short, key-scoped locking behavior already present in Cache and Reverb. This PR extracts that behavior into
Hypervel\Core\Swoole\StripedLockand uses it in all three packages.Cache's Swoole maintenance now uses the existing Coordinator timer instead of maintaining a second timer wrapper and worker-exit registry. Existing Redis duration and concurrency limiters now use the shared SHA-cached Lua path rather than sending their full scripts on every operation.
The implementation also exposed two small shared lifecycle issues:
Parallel::waitnow fails before running callbacks when called outside a coroutine; andMultipleInstanceManager::setApplicationandMailManager::setApplicationnow refresh their cached configuration reference when tests replace the application.Failure behavior
Rate limiter store failures propagate. Hypervel never silently allows an operation and never switches to a weaker or worker-local store after a backend failure.
This is important for both correctness and operations: a configured limit either uses the selected shared state or fails visibly.
Testing
The package has a shared behavioral contract that runs against worker-array, Swoole, Redis, SQLite, MySQL, MariaDB, and PostgreSQL stores. It covers fixed windows, weighted sliding windows, weighted consumption, leaky-bucket recovery, backoff thresholds and expiry, inspection, clearing, denied-operation immutability, and corrupt state.
Additional tests cover:
NOSCRIPTfallback, malformed state, and exact integer boundaries;SQLite multi-connection contention coverage remains in the shared database contract but is temporarily skipped on tagged Swoole releases. It links to Swoole PR #6140, which fixes the AIO scheduler stall. All non-concurrency SQLite store coverage remains active.
The complete rate limiting documentation is in
src/boost/docs/rate-limiting.md, with routing, queue, Fortify, Reverb, error handling, Redis, middleware, and facade documentation updated alongside it.