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: https://github.com/laravel/framework/issues/52416
Steps To Reproduce
- Create a fresh laravel application
- Install Laravel Octane and
beyondcode/laravel-dump-server
- 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),
];
});
- 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,
]);
});
}
}
- Migrate database and start octane + dump server
php artisan migrate # sqlite connection is fine
php artisan octane:start
php artisan dump-server
- 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
]