Laravel

The Laravel Octane Memory Leak in Long-Running Processes with Closure Binding

Kholil · 11 May 2026 · 4 min read · 1 views
The Laravel Octane Memory Leak in Long-Running Processes with Closure Binding

Pelajari cara mengidentifikasi dan memperbaiki memory leak yang sulit dideteksi di Laravel Octane yang disebabkan oleh improper closure binding dalam long-running processes.

Pendahuluan

Laravel Octane telah merevolusi cara kita menjalankan aplikasi Laravel dengan menyediakan server application HTTP yang ultra-cepat berbasis RoadRunner atau Swoole. Namun, seperti halnya teknologi powerful lainnya, Octane membawa tantangan uniknya sendiri—terutama ketika berbicara tentang memory management dalam long-running processes. Salah satu pitfall yang paling sulit dideteksi adalah memory leak yang muncul dari improper closure binding, sebuah masalah yang jarang dibahas di komunitas mainstream.

Mengapa Memory Leak Terjadi pada Octane?

Berbeda dengan model request-response tradisional PHP-FPM yang membuat process baru untuk setiap request, Laravel Octane menjaga satu process tetap berjalan dan melayani multiple requests secara berurutan atau concurrent. Ini berarti object dan state dapat bertahan di memory untuk waktu yang lama jika tidak dihandle dengan hati-hati.

Ketika menggunakan closure (anonymous function), terutama dalam konteks binding atau dependency injection, closure akan menangkap (capture) variabel dari scope parent-nya. Jika variabel yang ditangkap ini adalah object yang besar atau memiliki referensi sirkular, memory tidak akan di-release hingga closure itu sendiri di-garbage collect—yang mungkin tidak pernah terjadi dalam long-running process.

Contoh Kasus: Closure Binding yang Bermasalah

Mari kita lihat kode yang tampak innocent namun dapat menyebabkan memory leak:

// Dalam Service Provider atau middleware
app()->bind('expensive-service', function ($app) {
    $largeData = new Collection(range(1, 1000000));
    $config = config('services.expensive');
    
    return new ExpensiveService($largeData, $config);
});

// Di controller atau job
class ProcessDataController extends Controller
{
    public function process()
    {
        $service = app('expensive-service');
        // Closure di atas akan tetap menyimpan $largeData dan $config
        // di memory meski hanya digunakan saat instansiasi pertama
    }
}

Dalam contoh di atas, setiap kali container di-resolve, closure tetap menyimpan referensi ke $largeData dan $config. Pada traditional PHP-FPM, process akan di-terminate setelah request selesai. Tetapi pada Octane, closure dan data-nya tetap exist di memory.

Pattern yang Lebih Bermasalah: Event Listeners dengan Closure

Problem ini menjadi lebih parah ketika terlibat event listeners:

Event::listen('order.created', function (OrderCreated $event) {
    $logger = Log::channel('orders');
    $emailService = Mail::mailer('sendgrid');
    $notificationQueue = Queue::connection('redis');
    
    // Closure ini menangkap ketiga dependencies
    // dan akan persist di memory selama application running
    $this->sendNotification($event, $logger, $emailService, $notificationQueue);
});

Listener ini, meski berfungsi sempurna dalam environment traditional, dapat menyebabkan memory usage yang terus bertambah pada Octane karena closure tetap menahan reference ke logger, email service, dan queue connection.

Detecting the Memory Leak

Mendeteksi leak ini memerlukan tools dan patience. Cara paling efektif adalah menggunakan memory profiler:

// Di awal request
$startMemory = memory_get_usage(true);

// Jalankan operasi
for ($i = 0; $i < 1000; $i++) {
    event(new OrderCreated($order));
}

// Di akhir request
$endMemory = memory_get_usage(true);
Log::info('Memory used: ' . (($endMemory - $startMemory) / 1024 / 1024) . ' MB');

Jika memory terus bertambah setelah banyak requests, itu adalah indikasi kuat dari memory leak. Tools seperti Blackfire atau php-memory-profiler juga dapat membantu mengidentifikasi object yang tidak di-garbage collect.

Solusi: Best Practices untuk Octane

1. Gunakan Singleton Binding dengan Hati-hati

// Hindari menyimpan state yang besar
app()->singleton('expensive-service', function ($app) {
    // Setiap dependency harus "lazy-loadable"
    return new ExpensiveService();
});

2. Explicitly Release References

Event::listen('order.created', function (OrderCreated $event) {
    try {
        $this->processOrder($event);
    } finally {
        // Force cleanup
        unset($event);
    }
});

3. Hindari Closure, Gunakan Class-Based Listeners

// Lebih baik
Event::listen(OrderCreated::class, OrderNotificationListener::class);

// Listener class tidak menangkap scope parent
class OrderNotificationListener
{
    public function __invoke(OrderCreated $event)
    {
        // Dependencies di-inject, bukan di-capture
    }
}

4. Reset Service Container Antar Request (jika diperlukan)

// Dalam Octane middleware atau boot
app()->booted(function () {
    // Clear listeners yang tidak perlu
    Event::flush();
});

Testing untuk Memory Leaks

Buatlah test yang mensimulasikan long-running process:

public function test_no_memory_leak_on_repeated_events()
{
    $initialMemory = memory_get_usage(true);
    
    for ($i = 0; $i < 10000; $i++) {
        event(new OrderCreated($this->createOrder()));
    }
    
    gc_collect_cycles();
    $finalMemory = memory_get_usage(true);
    $memoryIncrease = ($finalMemory - $initialMemory) / 1024 / 1024;
    
    // Memory increase seharusnya minimal
    $this->assertLessThan(5, $memoryIncrease, 'Memory leak detected');
}

Kesimpulan

Memory leak melalui improper closure binding adalah subtle namun devastating problem untuk Laravel Octane applications. Kunci untuk menghindarinya adalah memahami lifecycle closure dalam long-running environment, menggunakan class-based listeners ketika possible, dan secara proaktif testing aplikasi Anda untuk memory issues. Dengan pendekatan yang tepat, Anda dapat menuai benefits dari Octane's performance tanpa jatuh ke pitfall tersembunyi ini.

Ingatlah: dalam Octane, setiap byte of memory yang tidak di-release adalah debt teknis yang akan dibayar dengan performance degradation over time. Investasi waktu untuk understand memory management akan memberikan returns yang signifikan dalam production.