Laravel Octane: Worker Lifecycle &amp; State Leakage | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Octane Worker Lifecycle, State Leakage, and Memory Management in Production        On this page       1. [  Why Octane Changes Everything About Application State ](#why-octane-changes-everything-about-application-state)
2. [  The Worker Request Lifecycle ](#the-worker-request-lifecycle)
3. [  Detecting Leaks ](#detecting-leaks)
4. [  Fixing Leaks: Scoped Bindings ](#fixing-leaks-scoped-bindings)
5. [  Resetting Static State with octane:flush ](#resetting-static-state-with-codeoctaneflushcode)
6. [  Memory Management: --max-requests Is Not Optional ](#memory-management-code-max-requestscode-is-not-optional)
7. [  Practical Checklist ](#practical-checklist)
8. [  Takeaways ](#takeaways)

  ![Octane Worker Lifecycle, State Leakage, and Memory Management in Production](https://cdn.msaied.com/244/7a3a8f7b0761bfb73c6baae5f893de95.png)

  #laravel   #octane   #performance   #swoole   #roadrunner  

 Octane Worker Lifecycle, State Leakage, and Memory Management in Production 
=============================================================================

     19 Jun 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Why Octane Changes Everything About Application State  ](#why-octane-changes-everything-about-application-state)
2. [  02   The Worker Request Lifecycle  ](#the-worker-request-lifecycle)
3. [  03   Detecting Leaks  ](#detecting-leaks)
4. [  04   Fixing Leaks: Scoped Bindings  ](#fixing-leaks-scoped-bindings)
5. [  05   Resetting Static State with octane:flush  ](#resetting-static-state-with-codeoctaneflushcode)
6. [  06   Memory Management: --max-requests Is Not Optional  ](#memory-management-code-max-requestscode-is-not-optional)
7. [  07   Practical Checklist  ](#practical-checklist)
8. [  08   Takeaways  ](#takeaways)

 Why Octane Changes Everything About Application State
-----------------------------------------------------

In a traditional PHP-FPM setup every request boots a fresh process. Globals, static properties, and resolved service-container bindings are destroyed when the response is sent. Octane breaks that contract: a single worker process handles thousands of requests, so anything you store in a static property or register as a true singleton persists across all of them.

This is the source of the most insidious Octane bugs — user A's authenticated model leaking into user B's request, a cached database connection holding a stale transaction, or a counter that keeps incrementing across requests.

The Worker Request Lifecycle
----------------------------

Octane wraps each request in a sandbox. Before dispatching it calls `$app->resetScope()`, which re-resolves bindings tagged as `scoped`. After the response is sent it fires `RequestHandled` and runs any registered termination callbacks.

The key distinction:

- **Singleton** (`app()->singleton(...)`) — resolved once per worker, never reset.
- **Scoped** (`app()->scoped(...)`) — resolved once per request, flushed after it.
- **Bind** (`app()->bind(...)`) — resolved fresh on every `make()` call.

If you register something as a singleton that holds per-request state (e.g. the currently authenticated user, a request-specific logger context, a DTO built from `Request`), you have a leak.

Detecting Leaks
---------------

The fastest way to reproduce a leak locally:

```bash
php artisan octane:start --workers=1 --max-requests=0

```

With a single worker and no request cap, fire two requests with different authenticated users and dump the resolved singleton between them.

```php
// routes/web.php
Route::get('/debug-singleton', function (MyStatefulService $svc) {
    return $svc->userId(); // should differ per request
});

```

If both requests return the same user ID, `MyStatefulService` is a leaking singleton.

Fixing Leaks: Scoped Bindings
-----------------------------

The cleanest fix is to change `singleton` to `scoped` in your service provider:

```php
// Before — leaks across requests
$this->app->singleton(CurrentTenantResolver::class, function ($app) {
    return new CurrentTenantResolver($app->make(Request::class));
});

// After — Octane flushes this after every request
$this->app->scoped(CurrentTenantResolver::class, function ($app) {
    return new CurrentTenantResolver($app->make(Request::class));
});

```

Octane calls `$app->forgetScopedInstances()` at the end of each request cycle, so the next `make()` call gets a fresh instance.

Resetting Static State with `octane:flush`
------------------------------------------

For third-party packages that use static properties internally, register a flush callback:

```php
use Laravel\Octane\Facades\Octane;

Octane::tick('flush-static-cache', function () {
    SomePackage::resetStaticCache();
})->seconds(0); // runs after every request via the 'RequestHandled' event

```

Alternatively, listen to the Octane request lifecycle events directly:

```php
use Laravel\Octane\Events\RequestHandled;

Event::listen(RequestHandled::class, function () {
    MyStaticRegistry::flush();
});

```

Memory Management: `--max-requests` Is Not Optional
---------------------------------------------------

Even with perfect scoping, memory grows. PHP's garbage collector does not always reclaim cyclic references inside long-lived closures. Set a sane `--max-requests` limit:

```ini
# octane config or supervisor
max_requests=500

```

This tells Octane to gracefully restart the worker after 500 requests. Combine it with Supervisor's `autorestart=true` so the slot is immediately refilled. Monitor worker RSS with:

```bash
watch -n2 'ps aux | grep octane'

```

If RSS climbs linearly and does not plateau, you have a reference cycle or a collection that is never cleared.

### Practical Checklist

- Audit every `singleton` binding that touches `Request`, `Auth`, or tenant context — convert to `scoped`.
- Never store request-derived state in a static property without a flush callback.
- Use `--max-requests` in production; tune it based on observed memory growth.
- Run `php artisan octane:start --workers=1` locally and replay the same route twice with different inputs to catch leaks early.
- Add a `RequestHandled` listener in staging that dumps memory usage per worker.

Takeaways
---------

- Octane's persistent worker model means singletons live for the worker's entire lifetime, not just one request.
- Use `scoped` bindings for anything that depends on per-request context.
- Register `RequestHandled` listeners or `Octane::tick` callbacks to flush third-party static state.
- `--max-requests` is a safety valve, not a substitute for correct scoping.
- A single-worker local setup is the fastest way to reproduce and verify leaks before they reach production.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Foctane-worker-lifecycle-state-leakage-and-memory-management-in-production&text=Octane+Worker+Lifecycle%2C+State+Leakage%2C+and+Memory+Management+in+Production) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Foctane-worker-lifecycle-state-leakage-and-memory-management-in-production) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  What is the difference between `singleton` and `scoped` in Laravel Octane?        A `singleton` is resolved once per worker process and persists across all requests that worker handles. A `scoped` binding is also resolved once, but Octane flushes it after each request via `forgetScopedInstances()`, so the next request gets a fresh instance. 

      Q02  How do I safely use a package that stores state in static properties under Octane?        Listen to the `Laravel\Octane\Events\RequestHandled` event and call the package's reset or flush method inside the listener. If the package has no reset method, consider wrapping it in a scoped service that re-instantiates it per request. 

      Q03  Should I set `--max-requests` even if I have no obvious memory leaks?        Yes. PHP's garbage collector does not always reclaim cyclic references in long-lived closures. A bounded `--max-requests` value (e.g. 500–1000) provides a safety net and keeps worker RSS predictable in production. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Laravel AI SDK: Tool-Calling Agents and Conversation Persistence](https://cdn.msaied.com/260/8c84f424e42da01993c9ba4b8eb19655.png) laravel ai agents 

### Laravel AI SDK: Tool-Calling Agents and Conversation Persistence

Build reliable tool-calling AI agents in Laravel using the Prism package. Learn how to wire tools, persist con...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 21 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-ai-sdk-tool-calling-agents-and-conversation-persistence) [ ![Laravel Livewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration](https://cdn.msaied.com/259/e8ce445f021c2b26ebe4dd5da50014f8.png) livewire laravel alpine 

### Laravel Livewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration

Go beyond the docs: understand how Livewire v3 diffs the DOM with morph markers, intercept the lifecycle with...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 21 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-livewire-v3-internals-morph-markers-js-hooks-and-alpine-integration) [ ![Laravel Package Development: Service Providers, Auto-Discovery, and Config Merging](https://cdn.msaied.com/258/673a80fa8e42ae375a4bba21bdcd92ea.png) laravel packages service-providers 

### Laravel Package Development: Service Providers, Auto-Discovery, and Config Merging

Build a production-ready Laravel package from scratch — covering service provider design, auto-discovery via c...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 21 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-package-development-service-providers-auto-discovery-and-config-merging-1) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
