AdamTheHutt
AdamTheHutt

Reputation: 8637

Asynchronous shell exec in PHP

I've got a PHP script that needs to invoke a shell script but doesn't care at all about the output. The shell script makes a number of SOAP calls and is slow to complete, so I don't want to slow down the PHP request while it waits for a reply. In fact, the PHP request should be able to exit without terminating the shell process.

I've looked into the various exec(), shell_exec(), pcntl_fork(), etc. functions, but none of them seem to offer exactly what I want. (Or, if they do, it's not clear to me how.) Any suggestions?

Upvotes: 218

Views: 196867

Answers (14)

Kamshory
Kamshory

Reputation: 11

I can not use > /dev/null 2>/dev/null & on Windows, so I use proc_open instead. I run PHP 7.4.23 on Windows 11.

This is my code.


function run_php_async($value, $is_windows)
{
    if($is_windows)
    {
        $command = 'php -q '.$value." ";
        echo 'COMMAND '.$command."\r\n";
        proc_open($command, [], $pipe);    
    }
    else
    {
        $command = 'php -q '.$value." > /dev/null 2>/dev/null &";
        echo 'COMMAND '.$command."\r\n";
        shell_exec($command);    
    }
}
$tasks = array();

$tasks[] = 'f1.php';
$tasks[] = 'f2.php';
$tasks[] = 'f3.php';
$tasks[] = 'f4.php';
$tasks[] = 'f5.php';
$tasks[] = 'f6.php';

$is_windows = true;

foreach($tasks as $key=>$value)
{
    run_php_async($value, $is_windows);
    echo 'STARTED AT '.date('H:i:s')."\r\n";
}

In each files to be execute, I put delay this:

<?php
sleep(mt_rand(1, 10));
file_put_contents(__FILE__.".txt", time());

All files are executed asynchronously.

Upvotes: 0

Anton Pelykh
Anton Pelykh

Reputation: 2390

I also found Symfony Process Component useful for this.

use Symfony\Component\Process\Process;

$process = new Process('ls -lsa');
// ... run process in background
$process->start();

// ... do other things

// ... if you need to wait
$process->wait();

// ... do things after the process has finished

See how it works in its GitHub repo.

Upvotes: 5

Darryl Hein
Darryl Hein

Reputation: 145137

On linux you can do the following:

$cmd = 'nohup nice -n 10 php -f php/file.php > log/file.log & printf "%u" $!';
$pid = shell_exec($cmd);

This will execute the command at the command prompty and then just return the PID, which you can check for > 0 to ensure it worked.

This question is similar: Does PHP have threading?

Upvotes: 26

LF-DevJourney
LF-DevJourney

Reputation: 28564

without use queue, you can use the proc_open() like this:

    $descriptorspec = array(
        0 => array("pipe", "r"),
        1 => array("pipe", "w"),
        2 => array("pipe", "w")    //here curaengine log all the info into stderror
    );
    $command = 'ping stackoverflow.com';
    $process = proc_open($command, $descriptorspec, $pipes);

Upvotes: 2

LucaM
LucaM

Reputation: 819

To all Windows users: I found a good way to run an asynchronous PHP script (actually it works with almost everything).

It's based on popen() and pclose() commands. And works well both on Windows and Unix.

function execInBackground($cmd) {
    if (substr(php_uname(), 0, 7) == "Windows"){
        pclose(popen("start /B ". $cmd, "r")); 
    }
    else {
        exec($cmd . " > /dev/null &");  
    }
} 

Original code from: http://php.net/manual/en/function.exec.php#86329

Upvotes: 40

Ronald Conco
Ronald Conco

Reputation: 845

You can also run the PHP script as daemon or cronjob: #!/usr/bin/php -q

Upvotes: 2

warren
warren

Reputation: 33473

If it "doesn't care about the output", couldn't the exec to the script be called with the & to background the process?

EDIT - incorporating what @AdamTheHut commented to this post, you can add this to a call to exec:

" > /dev/null 2>/dev/null &"

That will redirect both stdio (first >) and stderr (2>) to /dev/null and run in the background.

There are other ways to do the same thing, but this is the simplest to read.


An alternative to the above double-redirect:

" &> /dev/null &"

Upvotes: 240

Gordon Forsythe
Gordon Forsythe

Reputation: 406

The only way that I found that truly worked for me was:

shell_exec('./myscript.php | at now & disown')

Upvotes: 4

Czimi
Czimi

Reputation: 2534

I used at for this, as it is really starting an independent process.

<?php
    `echo "the command"|at now`;
?>

Upvotes: 58

philfreo
philfreo

Reputation: 43894

I used this...

/** 
 * Asynchronously execute/include a PHP file. Does not record the output of the file anywhere.  
 * Relies on the PHP_PATH config constant.
 *
 * @param string $filename  file to execute
 * @param string $options   (optional) arguments to pass to file via the command line
 */ 
function asyncInclude($filename, $options = '') {
    exec(PHP_PATH . " -f {$filename} {$options} >> /dev/null &");
}

(where PHP_PATH is a const defined like define('PHP_PATH', '/opt/bin/php5') or similar)

It passes in arguments via the command line. To read them in PHP, see argv.

Upvotes: 6

geocar
geocar

Reputation: 9303

Use a named fifo.

#!/bin/sh
mkfifo trigger
while true; do
    read < trigger
    long_running_task
done

Then whenever you want to start the long running task, simply write a newline (nonblocking to the trigger file.

As long as your input is smaller than PIPE_BUF and it's a single write() operation, you can write arguments into the fifo and have them show up as $REPLY in the script.

Upvotes: 1

pierre_moon
pierre_moon

Reputation:

the right way(!) to do it is to

  1. fork()
  2. setsid()
  3. execve()

fork forks, setsid tell the current process to become a master one (no parent), execve tell the calling process to be replaced by the called one. so that the parent can quit without affecting the child.

 $pid=pcntl_fork();
 if($pid==0)
 {
   posix_setsid();
   pcntl_exec($cmd,$args,$_ENV);
   // child becomes the standalone detached process
 }

 // parent's stuff
 exit();

Upvotes: 6

Leo
Leo

Reputation: 2898

In Linux, you can start a process in a new independent thread by appending an ampersand at the end of the command

mycommand -someparam somevalue &

In Windows, you can use the "start" DOS command

start mycommand -someparam somevalue

Upvotes: 6

Mark Biek
Mark Biek

Reputation: 150949

php-execute-a-background-process has some good suggestions. I think mine is pretty good, but I'm biased :)

Upvotes: 13

Related Questions