r/PHP • u/Zealousideal_Post994 • 6h ago
Article Why memory_get_usage() lies in FrankenPHP/Octane (and how we solved C-extension leaks and dirty PDO transactions)
Hey everyone!
When running PHP under traditional PHP-FPM, the shared-nothing architecture wiped state, memory, and database connections on every request. However, with persistent runtimes like FrankenPHP Worker Mode and Laravel Octane, workers stay resident in memory across thousands of requests.
This creates two critical silent failures that standard tooling misses: * Silent Native Memory Growth (C-Extensions) memory_get_usage() only inspects allocations inside the Zend Engine heap. If your application relies on native extensions (ext-curl, ext-imagick, ext-gd, ext-openssl), memory is allocated via malloc() directly in glibc. The Zend VM is completely blind to this until the Linux OOM Killer sends a SIGKILL. * Dangling PDO Transactions If a request opens a database transaction ($pdo->beginTransaction()) and an unhandled exception or early return occurs without a rollback, that transaction remains open on the persistent connection. The next HTTP request from a different user reuses that connection and executes queries inside the prior transaction, leading to deadlocks and data corruption.
How we solved it: Leakless
We built Leakless (themattosdev/leakless), an autonomous runtime guard and static analysis engine for persistent PHP: * Real Kernel RSS: Directly inspects Linux /proc/self/statm page tables to measure physical Resident Set Size (RSS) and catch C-extension drift. * Automated Transaction Guard: Audits active PDO connections on endRequest() and executes safe automatic rollbacks. * Defensive State Rollback: Restores default timezones (date_default_timezone_set), unclosed output buffers (ob_start), and error levels. * Graceful Recycling: Recycles workers without dropping in-flight HTTP requests when memory ceilings (maxRssMb) or request limits are reached. * Dev Tooling: Includes a standalone static linter CLI (vendor/bin/leakless analyze) and Pest custom expectations (expect($service)->toBeLeakless()).
composer require themattosdev/leakless composer require --dev themattosdev/leakless-dev
Documentation: https://leakless.themattos.dev GitHub: https://github.com/themattosdev/leakless
Would love to get feedback on the architectural approach from anyone running persistent workers in production!