Search code examples
phpsupervisord

Troubleshooting Freezing Issues in a php Infinite Loop Script with Supervisor


I had an infinite loop that receives updates from a third-party service. This while loop is supervised, meaning that if it breaks, the script will be automatically restarted.

while (true) {
    try {
        // Long polling requests to retrieve messages from the bot
    } catch (BreakLoopException $exception) {
        break;
    }
}

However, I encountered a problem where the while loop would freeze on occasion. I suspected that this might be due to memory leaks. So, I adjusted my approach and decided to run the long polling logic in a separate process like this:

$round = 0;
while ($round < 100) {
    exec("php " . base_path() . "/artisan bot:get-updates");
    $round++;
}

Additionally, I made sure the while loop breaks automatically after 100 rounds, so the supervisor will restart it. Despite these changes, the script still froze, even though the while loop should have run 100 times only.

As of now, it has been 24 hours since the last script restart, which is quite unusual.

Why this is happening?


Solution

  • Looks like your external program exec("php " . base_path() . "/artisan bot:get-updates"); freezes. I suggest to use PHP processes to have more control over the execution (including killing the external process if a timeout occurs).

    For testing I used operation.php

    <?php
    echo 'TEST01' . PHP_EOL;
    sleep(5);
    echo 'TEST02' . PHP_EOL;
    

    And called it with:

    <?php
    
    $command = 'php operation.php';
    $timeout = 0.1; // Timeout in seconds
    
    $descriptorspec = array(
        0 => array("pipe", "r"),
        1 => array("pipe", "w"),
        2 => array("pipe", "w") 
     );
    
    for($i=0; $i<100; $i++) {
        $process = proc_open($command, $descriptorspec, $pipes);
        if(is_resource($process)) {
            $starttime = microtime(true);
            while(proc_get_status($process)['running']) {
                if((microtime(true)-$starttime) > $timeout) {
                    proc_terminate($process);
                    break;
                }
                usleep(10000); // Sleep for 10 ms
            }
            fclose($pipes[0]);
            $stdout = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            $stderr = stream_get_contents($pipes[2]);
            fclose($pipes[2]);
            $exit_code = proc_close($process);
            echo 'OUTPUT: ' . $stdout . PHP_EOL;
        }
        usleep(10000); // Sleep for 10 ms
    }
    

    which is going to output TEST01 but never TEST02, unless the $timeout is >5.