Tutorials Courses Interview Prep (100 Q&A) AI Tools (50) Developer Tools (50) Tracks Pricing Plans Blog & Articles Contact Us
Laravel Advanced & Architecture 8-10 Years (Tech Lead) Expert Production Scenario

How does Laravel Octane handle state persistence across requests, and how do you prevent catastrophic memory leaks and data bleeding in singletons?

Real-World Scenario / Incident Description

In a high-throughput production environment running Laravel Octane with Swoole/FrankenPHP serving 10,000 req/sec, users reported seeing other users' authenticated profiles, and worker memory consumption steadily increased until pods crashed with OOM.

106 views • Updated 1 month ago

Root Cause Analysis: Traditional PHP-FPM vs. Long-Running Octane

Under traditional PHP-FPM, the entire application lifecycle (memory, static variables, singletons) is instantiated on request arrival and wiped completely when the request terminates (shared-nothing architecture).

Under Laravel Octane (Swoole, RoadRunner, FrankenPHP), the PHP application boots once into memory. Worker processes persist across thousands of requests. Any state attached to a singleton, static property, or service provider persists across subsequent HTTP requests from completely different clients.

Critical Failure Vectors

  1. Data Bleeding: Storing request-scoped data (like Auth::user() or tenant IDs) inside a singleton service constructor or static class property. Subsequent requests will read the prior user's cached identity.
  2. Memory Leak: Appending items into static array caches (private static array $cache = []) or listening to events without unregistering listeners.

Architectural Solution

  1. Use bindScoped Instead of singleton: Laravel Octane resets scoped bindings between requests:

    // BAD in Octane (persists across requests)
    $this->app->singleton(TenantManager::class, fn () => new TenantManager($request->header('X-Tenant-ID')));
    
    // GOOD in Octane (purged after each request finishes)
    $this->app->scoped(TenantManager::class, fn () => new TenantManager(request()->header('X-Tenant-ID')));
    
  2. Octane Warmers & Listeners: Flush state explicitly in config/octane.php listeners:

    OperationTerminated::class => [
        FlushTemporaryCaches::class,
        ResetTenantContext::class,
    ],
    
  3. Pass Callbacks or Resolvers: Instead of caching an instantiated $user in a service, pass a Closure or resolve dynamically via Auth::user().

Related Concepts: #Octane #Swoole #Memory Leaks #Singletons #State Bleed