Featured image of post Troubleshooting Laravel-S Memory Leak with Swoole Tracker

Troubleshooting Laravel-S Memory Leak with Swoole Tracker

After adopting laravel-s, the speed improvement was significant, but it introduced new challenges

Environment Setup

  • Built using swoole:alpine Docker image
FROM phpswoole/swoole:php7.4-alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && cat /etc/apk/repositories

# Quick PHP extension installation
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions pcntl redis pdo_mysql

WORKDIR /var/www
COPY . .
RUN chmod -R 0777 storage && \
    chmod -R 0777 bootstrap/cache && \
    composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ && \
    composer install --optimize-autoloader --no-dev && \
    php artisan config:cache && \
    php artisan route:cache && \
    php artisan view:cache && \
    php artisan laravels publish --no-interaction

CMD ["php", "bin/laravels", "start", "--env=product"]
  • For initial setup, comment out the last two lines in Dockerfile:
docker build . -t test-image
docker run -d -it -p 80:5200 -v [/mnt/d/xxxx]:/var/dev --name=test-service -w /var/dev test-image /bin/sh
docker exec -it memory-service /bin/sh

Error Logs

[2022-01-01 02:29:45 $19.0]	WARNING	Server::check_worker_exit_status(): worker(pid=1641, id=5) abnormal exit, status=255, signal=0
[2022-01-01 02:30:24 *1642.0]	ERROR	php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted...
... (truncated for brevity)

Official Documentation Guidance

Memory Leak Prevention

  • Avoid global variables or manually clean them
  • Prevent infinite appends to static variables/singletons
// Bad practice example
class Test {
    public static $array = [];
    public static $string = '';
}

Memory Leak Detection

  1. Modify config/laravels.php:
    'worker_num' => 1,
    'max_request' => 1000000,
    
  2. Add debug route:
    Route::get('/debug-memory-leak', function () {
        global $previous;
        $current = memory_get_usage();
        $stats = [
            'prev_mem' => $previous,
            'curr_mem' => $current,
            'diff_mem' => $current - $previous,
        ];
        $previous = $current;
        return $stats;
    });
    
  3. Monitor memory changes using:
    watch -n 1 curl 127.0.0.1:5200/debug-memory-leak
    

Using Swoole Tracker 3.1

  1. Install free version following official guide
  2. Add tracker hooks:
    trackerHookMalloc();
    
  3. Analyze leaks:
    php -r "trackerAnalyzeLeak();"
    

Sample Leak Report

[29260 (Loop 4252)] /var/dev/vendor/facade/ignition/src/QueryRecorder/QueryRecorder.php:44 => [-192]
... (truncated)

Root Cause Analysis

The memory leak originated from repeated command registration in AdminServiceProvider:

public function register()
{
    // Problematic code
    $this->commands($this->commands);
    
    if (config('app.debug')) {
        $this->commands($this->devCommands);
    }
}

Each request appended commands to Artisan::$bootstrappers, causing memory growth:

public static function starting(Closure $callback)
{
    static::$bootstrappers[] = $callback; // Memory leak source
}

Solution

  1. Remove unnecessary service provider registration in laravels.register_providers
  2. Implement proper cleanup mechanisms
  3. Use max_request as safety net:
    'max_request' => 500, // Handle 500 requests per worker
    

Final Docker CMD:

CMD ["php", "bin/laravels", "start", "--env=product"]