Skip to content

Memory leak in Octane on large DB insertions #56652

@rasmuscnielsen

Description

@rasmuscnielsen

Laravel Version

12.20.0

PHP Version

8.4.3

Database Driver & Version

Mysql 8 / Sqlite

Description

When performing large DB inserts with APP_DEBUG=true, memory usage doesn't reset across requests.

This can cause memory leaks worth of > 100mb during local development.

I have isolated the issue to Illuminate\Foundation\Exceptions\Renderer\Listener which keeps the most recent 100 queries in memory.
Even though efforts has clearly gone into making it Octane compatible by resetting the queries on each request, my testing shows that the actual memory consumption doesn't go down.

I've yet to pinpoint exactly what the root cause is, but my best guess is that it's related to some stale version of the Listener instance persisting in memory with all the old queries.

While the bug may seem minor since it only occurs in debug mode, it's still problematic to have such persistent memory leaks in local development since it creates significantly different behaviour than production.
Also it's time consuming for everyone spending hours or days hunting down memory leaks in their applications, only to find it's a framework issue only present in local development.

Previously reported and dismissed here: #52416

Steps To Reproduce

  1. Create a fresh laravel application
  2. Install Laravel Octane and beyondcode/laravel-dump-server
  3. Add this route in routes/web.php
Route::get('/import', function () {
    ini_set('memory_limit', '256M');

    $startTime = microtime(true);
    $startMem = memory_get_usage(true) / (1024 * 1024);

    \App\Models\User::truncate();

    for ($i = 0; $i < 105; $i++) {
        $users = \Illuminate\Support\Collection::times(6000)
            ->map(fn($u) => [
                'name' => 'Some very very very very very very very very very very very very very very very very long name',
                'email' => "and-a-very-very-very-very-very-very-very-very-very-very-very-long-email-{$i}-{$u}@example.org",
                'password' => 'secret',
            ])
            ->all();

        \App\Models\User::query()->insert($users);
    }

    return [
        'time' => microtime(true) - $startTime,
        'startMem' => $startMem,
        'endMem' => memory_get_usage(true) / (1024 * 1024),
        'peakMem' => memory_get_peak_usage(true) / (1024 * 1024),
    ];
});
  1. Replace the registerListeners method in Illuminate\Foundation\Exceptions\Renderer\Listener with this (so we can see what's going on)
    public function registerListeners(Dispatcher $events)
    {
        $events->listen(QueryExecuted::class, $this->onQueryExecuted(...));

        $events->listen([JobProcessing::class, JobProcessed::class], function () {
            $this->queries = [];
        });

        if (isset($_SERVER['LARAVEL_OCTANE'])) {
            $events->listen([RequestReceived::class, TaskReceived::class, TickReceived::class, RequestTerminated::class], function ($e) {
                $preClearMem = memory_get_usage(true) / (1024 * 1024);
                $preQueries = count($this->queries);

                $this->queries = [];

                $postClearMem = memory_get_usage(true) / (1024 * 1024);

                gc_collect_cycles();

                $postGarbageCollectionMem = memory_get_usage(true) / (1024 * 1024);

                dump([
                    'event' => get_class($e),
                    'preClearQueries' => $preQueries,
                    'preClearMem' => $preClearMem,
                    'postClearMem' => $postClearMem,
                    'postGarbageCollectionMem' => $postGarbageCollectionMem,
                ]);
            });
        }
    }
  1. Migrate database and start octane + dump server
php artisan migrate # sqlite connection is fine
php artisan octane:start
php artisan dump-server
  1. Trigger 2 or more requests to route. You will see that memory usage doesn't go down, despite queries are in fact being reset after each request.
curl -X GET http://127.0.0.1:8000/import

RESULTS

~/www » curl -X GET http://127.0.0.1:8000/import                               
{"time":4.247610092163086,"startMem":10,"endMem":148,"peakMem":148}%                                      ----------------------------------------------------------------------------------------------------------
~/www » curl -X GET http://127.0.0.1:8000/import                               
{"time":2.93687105178833,"startMem":144,"endMem":154,"peakMem":154}%                                      ----------------------------------------------------------------------------------------------------------
~/www » curl -X GET http://127.0.0.1:8000/import                               
{"time":3.2113161087036133,"startMem":142,"endMem":154,"peakMem":154}%                                    ----------------------------------------------------------------------------------------------------------
~/www »
~/www/laravel-12 » phpa dump-server                                                                                                                                             rasmuscnielsen@MIAMLT-3338

Laravel Var Dump Server
=======================

                                                                                                                        
 [OK] Server listening on tcp://127.0.0.1:9912                                                                          
                                                                                                                        

 // Quit the server with CONTROL-C.                                                                                     


GET http://localhost/
---------------------

 ------------ ------------------------------------------------------------------------------------- 
  date         Thu, 14 Aug 2025 08:17:54 +0000                                                      
  controller   null                                                                                 
  source       Listener.php on line 50                                                              
  file         vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php  
 ------------ ------------------------------------------------------------------------------------- 

array:5 [
  "event" => "Laravel\Octane\Events\RequestReceived"
  "preClearQueries" => 0
  "preClearMem" => 8
  "postClearMem" => 8
  "postGarbageCollectionMem" => 8
]

 ------------ ------------------------------------------------------------------------------------- 
  date         Thu, 14 Aug 2025 08:17:58 +0000                                                      
  controller   null                                                                                 
  source       Listener.php on line 50                                                              
  file         vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php  
 ------------ ------------------------------------------------------------------------------------- 

array:5 [
  "event" => "Laravel\Octane\Events\RequestTerminated"
  "preClearQueries" => 100
  "preClearMem" => 148
  "postClearMem" => 144
  "postGarbageCollectionMem" => 144
]

 ------------ ------------------------------------------------------------------------------------- 
  date         Thu, 14 Aug 2025 08:18:01 +0000                                                      
  controller   null                                                                                 
  source       Listener.php on line 50                                                              
  file         vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php  
 ------------ ------------------------------------------------------------------------------------- 

array:5 [
  "event" => "Laravel\Octane\Events\RequestReceived"
  "preClearQueries" => 0
  "preClearMem" => 144
  "postClearMem" => 144
  "postGarbageCollectionMem" => 144
]

 ------------ ------------------------------------------------------------------------------------- 
  date         Thu, 14 Aug 2025 08:18:04 +0000                                                      
  controller   null                                                                                 
  source       Listener.php on line 50                                                              
  file         vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php  
 ------------ ------------------------------------------------------------------------------------- 

array:5 [
  "event" => "Laravel\Octane\Events\RequestTerminated"
  "preClearQueries" => 100
  "preClearMem" => 154
  "postClearMem" => 142
  "postGarbageCollectionMem" => 142
]

 ------------ ------------------------------------------------------------------------------------- 
  date         Thu, 14 Aug 2025 08:18:05 +0000                                                      
  controller   null                                                                                 
  source       Listener.php on line 50                                                              
  file         vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php  
 ------------ ------------------------------------------------------------------------------------- 

array:5 [
  "event" => "Laravel\Octane\Events\RequestReceived"
  "preClearQueries" => 0
  "preClearMem" => 142
  "postClearMem" => 142
  "postGarbageCollectionMem" => 142
]

 ------------ ------------------------------------------------------------------------------------- 
  date         Thu, 14 Aug 2025 08:18:08 +0000                                                      
  controller   null                                                                                 
  source       Listener.php on line 50                                                              
  file         vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Renderer/Listener.php  
 ------------ ------------------------------------------------------------------------------------- 

array:5 [
  "event" => "Laravel\Octane\Events\RequestTerminated"
  "preClearQueries" => 100
  "preClearMem" => 154
  "postClearMem" => 142
  "postGarbageCollectionMem" => 142
]

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions