php execute a background process
Asked Answered
I

23

287

I need to execute a directory copy upon a user action, but the directories are quite large, so I would like to be able to perform such an action without the user being aware of the time it takes for the copy to complete.

Any suggestions would be much appreciated.

Iolaiolande answered 5/9, 2008 at 14:43 Comment(1)
This #5367761 explains how to do this under windowsHatchway
R
395

Assuming this is running on a Linux machine, I've always handled it like this:

exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, $outputfile, $pidfile));

This launches the command $cmd, redirects the command output to $outputfile, and writes the process id to $pidfile.

That lets you easily monitor what the process is doing and if it's still running.

function isRunning($pid){
    try{
        $result = shell_exec(sprintf("ps %d", $pid));
        if( count(preg_split("/\n/", $result)) > 2){
            return true;
        }
    }catch(Exception $e){}

    return false;
}
Rascally answered 5/9, 2008 at 14:49 Comment(20)
I tried your sample. But unfortunately it never worked in this case. $cmd = "sudo -u sun mplayer /tmp/demo.wav>/dev/null &"; exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, $outputfile, $pidfile) ); echo $pidfile; // so that i can kill later gives nothing.Arachne
Is sudo setup to run without prompting for a password? Any commands that require user input aren't going to work.Rascally
Yes its disabled, nopassword mode is on but still did not work. E.g: #Defaults requiretty root ALL=(ALL) ALL apache ALL=(ALL) NOPASSWD: ALLArachne
Please see a small example where i explain all details but i am getting failed E.g: gist.github.com/1010541Arachne
This #5367761 explains how to do the same under windowsHatchway
I used this, but updated it slightly so I didn't have to write the PID to a file. So I use this format: <code> exec(sprintf("$s > $s 2>&1 & echo $1", $cmd, $outputfile),$pidArr); </code> The resulting process PID is in $pidArr[0]Deloris
@Kaiesh: it should be "echo $!"Neuropsychiatry
@Neuropsychiatry Yes, you're right! Thanks for the correction! Typo :-)Deloris
If you want to handle exit codes, just replace the first "%s" with "(%s; printf \"\\n$?\")". This will put the exit code of the command in the last line of the log.Ecumenicity
Does anyone know how to use sprintf on Ubunut 12.04? It's not available and there doesn't seem to be any good solutions from my Googling.Carmencita
@BrianRuff sprintf() in the context above is a PHP builtin function. It is available on all PHP installations, and not an OS-level operation.Chrystel
Kaiesh made anoter typo, '$' instead of '%'. Corrected version: exec(sprintf("%s > %s 2>&1 & echo $!", $cmd, $outputfile),$pidArr)Forficate
This seems to break quite a lot for me. I ended up using this: symcbean.blogspot.co.uk/2010/02/…Horribly
Interesting answer, but I think ultimately PHP is the wrong tool for the job in this case. This example will only work with bash commands - Using Threads (and perhaps Queue for handling workload for some number of threads) works in Python and lets you run any function routine. See pymotw.com/2/QueueHernando
The pid might have been taken by another process. In that case, isRunning() will return true - while at the same time the process has really stopped.Ovenbird
Made this answer into a simple composer package. You can find it on github: github.com/diversen/background-jobFormula
when you use 'ps h -o pid <pid>' you will only retrieve an output, if the process is still alive. so there's no need for preg_split or the likeMonogram
You should use escapeshellcmd() or escapeshellarg() on your variables before using them in exec()!Jaejaeger
Instead of "ps %s", you could use posix_kill($pid, 0) which I think is much faster (not creating an extra process with fork()+exec(), etc.)Silence
Does sprintf offer anything better than simple interpolation does? viz exec("$cmd > $outputfile 2>&1 & echo $! >> $pidfile");Ferrante
S
29

Write the process as a server-side script in whatever language (php/bash/perl/etc) is handy and then call it from the process control functions in your php script.

The function probably detects if standard io is used as the output stream and if it is then that will set the return value..if not then it ends

proc_close( proc_open( "./command --foo=1 &", array(), $foo ) );

I tested this quickly from the command line using "sleep 25s" as the command and it worked like a charm.

(Answer found here)

Saturninasaturnine answered 5/9, 2008 at 15:13 Comment(2)
This is the only way I could get it to work on centos. Thank you!Durkin
Thanks ! There is an incompatibility of PHP and BASH. If you run a newer system it will fail to launch in background using system() or exec(). The file descriptors seem to get stuck even if you redirect. Your method worked. another alternative is to use an old bash binary for PHP, but that's nutsPerihelion
F
27

You might want to try to append this to your command

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

eg.

shell_exec('service named reload >/dev/null 2>/dev/null &');
Fate answered 14/8, 2012 at 14:42 Comment(3)
I meant >/dev/null 2>/dev/null &Fate
It`s work for me, and I will use the makefile do something at server site, thank you! (ex. make, make restart)Alcahest
This is much simpler than the accepted answer! (As long you don't need anything more complex, like checking if the process is still running.)Chimkent
S
18

I'd just like to add a very simple example for testing this functionality on Windows:

Create the following two files and save them to a web directory:

foreground.php:

<?php

ini_set("display_errors",1);
error_reporting(E_ALL);

echo "<pre>loading page</pre>";

function run_background_process()
{
    file_put_contents("testprocesses.php","foreground start time = " . time() . "\n");
    echo "<pre>  foreground start time = " . time() . "</pre>";

    // output from the command must be redirected to a file or another output stream 
    // http://ca.php.net/manual/en/function.exec.php

    exec("php background.php > testoutput.php 2>&1 & echo $!", $output);

    echo "<pre>  foreground end time = " . time() . "</pre>";
    file_put_contents("testprocesses.php","foreground end time = " . time() . "\n", FILE_APPEND);
    return $output;
}

echo "<pre>calling run_background_process</pre>";

$output = run_background_process();

echo "<pre>output = "; print_r($output); echo "</pre>";
echo "<pre>end of page</pre>";
?>

background.php:

<?
file_put_contents("testprocesses.php","background start time = " . time() . "\n", FILE_APPEND);
sleep(10);
file_put_contents("testprocesses.php","background end time = " . time() . "\n", FILE_APPEND);
?>

Give IUSR permission to write to the directory in which you created the above files

Give IUSR permission to READ and EXECUTE C:\Windows\System32\cmd.exe

Hit foreground.php from a web browser

The following should be rendered to the browser w/the current timestamps and local resource # in the output array:

loading page
calling run_background_process
  foreground start time = 1266003600
  foreground end time = 1266003600
output = Array
(
    [0] => 15010
)
end of page

You should see testoutput.php in the same directory as the above files were saved, and it should be empty

You should see testprocesses.php in the same directory as the above files were saved, and it should contain the following text w/the current timestamps:

foreground start time = 1266003600
foreground end time = 1266003600
background start time = 1266003600
background end time = 1266003610
Saintjust answered 12/2, 2010 at 20:40 Comment(1)
Only a little thing, but you need to watch out for the "<? " in the background.php file . It should be "<?php"Procreant
E
10

If you need to just do something in background without the PHP page waiting for it to complete, you could use another (background) PHP script that is "invoked" with wget command. This background PHP script will be executed with privileges, of course, as any other PHP script on your system.

Here is an example on Windows using wget from gnuwin32 packages.

The background code (file test-proc-bg.php) as an exmple ...

sleep(5);   // some delay
file_put_contents('test.txt', date('Y-m-d/H:i:s.u')); // writes time in a file

The foreground script, the one invoking ...

$proc_command = "wget.exe http://localhost/test-proc-bg.php -q -O - -b";
$proc = popen($proc_command, "r");
pclose($proc);

You must use the popen/pclose for this to work properly.

The wget options:

-q    keeps wget quiet.
-O -  outputs to stdout.
-b    works on background
Eduino answered 28/1, 2011 at 20:23 Comment(0)
F
7

Well i found a bit faster and easier version to use

shell_exec('screen -dmS $name_of_screen $command'); 

and it works.

Fundus answered 3/6, 2014 at 21:34 Comment(2)
@Alpha.Dev I tested it on linux only I do not know if it's gonna work on another sytem.Fundus
Well, it should work on any OS that has screen installed :-) One of my favourite commands, even though it's dated and everybody swears that tset is far superior...Decomposer
Y
7

Here is a function to launch a background process in PHP. Finally created one that actually works on Windows too, after a lot of reading and testing different approaches and parameters.

function LaunchBackgroundProcess($command){
  // Run command Asynchroniously (in a separate thread)
  if(PHP_OS=='WINNT' || PHP_OS=='WIN32' || PHP_OS=='Windows'){
    // Windows
    $command = 'start "" '. $command;
  } else {
    // Linux/UNIX
    $command = $command .' /dev/null &';
  }
  $handle = popen($command, 'r');
  if($handle!==false){
    pclose($handle);
    return true;
  } else {
    return false;
  }
}

Note 1: On windows, do not use /B parameter as suggested elsewhere. It forces process to run the same console window as start command itself, resulting in the process being processed synchronously. To run the process in a separate thread (asynchronously), do not use /B.

Note 2: The empty double quotes after start "" are required if the command is a quoted path. start command interprets the first quoted parameter as window title.

Yolandoyolane answered 5/1, 2016 at 13:31 Comment(0)
S
5

Use this function to run your program in background. It cross-platform and fully customizable.

<?php
function startBackgroundProcess(
    $command,
    $stdin = null,
    $redirectStdout = null,
    $redirectStderr = null,
    $cwd = null,
    $env = null,
    $other_options = null
) {
    $descriptorspec = array(
        1 => is_string($redirectStdout) ? array('file', $redirectStdout, 'w') : array('pipe', 'w'),
        2 => is_string($redirectStderr) ? array('file', $redirectStderr, 'w') : array('pipe', 'w'),
    );
    if (is_string($stdin)) {
        $descriptorspec[0] = array('pipe', 'r');
    }
    $proc = proc_open($command, $descriptorspec, $pipes, $cwd, $env, $other_options);
    if (!is_resource($proc)) {
        throw new \Exception("Failed to start background process by command: $command");
    }
    if (is_string($stdin)) {
        fwrite($pipes[0], $stdin);
        fclose($pipes[0]);
    }
    if (!is_string($redirectStdout)) {
        fclose($pipes[1]);
    }
    if (!is_string($redirectStderr)) {
        fclose($pipes[2]);
    }
    return $proc;
}

Note that after command started, by default this function closes the stdin and stdout of running process. You can redirect process output into some file via $redirectStdout and $redirectStderr arguments.

Note for windows users:
You cannot redirect stdout/stderr to nul in the following manner:

startBackgroundProcess('ping yandex.com', null, 'nul', 'nul');

However, you can do this:

startBackgroundProcess('ping yandex.com >nul 2>&1');

Notes for *nix users:

1) Use exec shell command if you want get actual PID:

$proc = startBackgroundProcess('exec ping yandex.com -c 15', null, '/dev/null', '/dev/null');
print_r(proc_get_status($proc));

2) Use $stdin argument if you want to pass some data to the input of your program:

startBackgroundProcess('cat > input.txt', "Hello world!\n");
Stevestevedore answered 4/7, 2016 at 8:6 Comment(0)
S
4

Can you arrange to fork off a separate process, and then run your copy in the background? It's been a while since I did any PHP, but the function pcntl-fork looks promising.

Spiry answered 5/9, 2008 at 14:49 Comment(3)
This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post.Hardened
Thanks - Comments didn't exist in '08, but I'll keep that in mind :)Spiry
In any case, your question-disguised-as-answer has the answer "yes, if PHP is running as a CLI; no in all other cases" :-)Decomposer
F
3

Thanks to this answer: A perfect tool to run a background process would be Symfony Process Component, which is based on proc_* functions, but it's much easier to use. See its documentation for more information.

Forestation answered 5/9, 2008 at 14:44 Comment(0)
W
3

You might try a queuing system like Resque. You then can generate a job, that processes the information and quite fast return with the "processing" image. With this approach you won't know when it is finished though.

This solution is intended for larger scale applications, where you don't want your front machines to do the heavy lifting, so they can process user requests. Therefore it might or might not work with physical data like files and folders, but for processing more complicated logic or other asynchronous tasks (ie new registrations mails) it is nice to have and very scalable.

Wife answered 14/2, 2013 at 23:57 Comment(0)
N
3

A working solution for both Windows and Linux. Find more on My github page.

function run_process($cmd,$outputFile = '/dev/null', $append = false){
                    $pid=0;
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        $cmd = 'wmic process call create "'.$cmd.'" | find "ProcessId"';
                        $handle = popen("start /B ". $cmd, "r");
                        $read = fread($handle, 200); //Read the output 
                        $pid=substr($read,strpos($read,'=')+1);
                        $pid=substr($pid,0,strpos($pid,';') );
                        $pid = (int)$pid;
                        pclose($handle); //Close
                }else{
                    $pid = (int)shell_exec(sprintf('%s %s %s 2>&1 & echo $!', $cmd, ($append) ? '>>' : '>', $outputFile));
                }
                    return $pid;
            }
            function is_process_running($pid){
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        //tasklist /FI "PID eq 6480"
                    $result = shell_exec('tasklist /FI "PID eq '.$pid.'"' );
                    if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                        return true;
                    }
                }else{
                    $result = shell_exec(sprintf('ps %d 2>&1', $pid));
                    if (count(preg_split("/\n/", $result)) > 2 && !preg_match('/ERROR: Process ID out of range/', $result)) {
                        return true;
                    }
                }
                return false;
            }
            function stop_process($pid){
                    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                            $result = shell_exec('taskkill /PID '.$pid );
                        if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                            return true;
                        }
                    }else{
                            $result = shell_exec(sprintf('kill %d 2>&1', $pid));
                        if (!preg_match('/No such process/', $result)) {
                            return true;
                        }
                    }
            }
Nowhither answered 7/10, 2017 at 5:50 Comment(0)
E
1

Instead of initiating a background process, what about creating a trigger file and having a scheduler like cron or autosys periodically execute a script that looks for and acts on the trigger files? The triggers could contain instructions or even raw commands (better yet, just make it a shell script).

Enriqueenriqueta answered 26/4, 2012 at 11:22 Comment(0)
C
1

If using PHP there is a much easier way to do this using pcntl_fork:

http://www.php.net/manual/en/function.pcntl-fork.php

Cirque answered 2/7, 2014 at 15:40 Comment(1)
... which only works when running PHP on the CLI.Decomposer
A
1

I am heavily using fast_cgi_finish_request()

In combination with a closure and register_shutdown_function()

$message ='job executed';
$backgroundJob = function() use ($message) {
     //do some work here
    echo $message;
}

Then register this closure to be executed before shutdown.

register_shutdown_function($backgroundJob);

Finally when the response was sent to the client you can close the connection to the client and continue working with the PHP process:

fast_cgi_finish_request();

The closure will be executed after fast_cgi_finish_request.

The $message will not be visible at any time. And you can register as much closures as you want, but take care about script execution time. This will only work if PHP is running as a Fast CGI module (was that right?!)

Arica answered 2/10, 2014 at 23:0 Comment(0)
R
1

If you are looking to execute a background process via PHP, pipe the command's output to /dev/null and add & to the end of the command.

exec("bg_process > /dev/null &");

Note that you can not utilize the $output parameter of exec() or else PHP will hang (probably until the process completes).

Rigdon answered 10/11, 2020 at 23:21 Comment(0)
A
1

From PHP official documentation(php.net)

<?php
function execInBackground($cmd) {
    if (substr(php_uname(), 0, 7) == "Windows"){
        pclose(popen("start /B ". $cmd, "r")); 
    }
    else {
        exec($cmd . " > /dev/null &");  
    }
}
?>
Agoraphobia answered 26/7, 2022 at 6:38 Comment(2)
This will break if $cmd contains quotes (which are needed to support paths with spaces). This is because start treats the first quotes like a title. So it should be pclose(popen("start /B " . chr(34) . chr(34) . chr(32) . $cmd, "r"));, which makes sure titles are defined on their own. You might want to edit and add that.Riegel
It must be pointed out that, although this snippet is apparently from the official article on exec, it’s from the comment section which is far from giving it an official nature.Expertism
M
1

I really appreciate Mark Biek's answer. In fact I wrote a little script to show it in action:

message.php:

$arg1 = $argv[1];
$arg2 = $argv[2];
sleep($arg2*2);
print "Hello world $arg1 $arg2\n";

threads.php:

$pidIJs = array();
for ($i=0; $i<5; $i++)
{
    for ($j=0; $j<5; $j++)
    {
        $cmd        = "php message.php $i $j";
        $outputFile = "threads-out.txt";
        print "Launching $cmd ... ";
        $pid = exec(sprintf("%s >> %s 2>&1 & echo $!", $cmd, $outputFile));
        print "pid: $pid\n";
        $pidIJs[$pid] = array($i, $j);
    }
}

function isRunning($pid)
{
    $result = shell_exec(sprintf("ps %d", $pid));
    if (count(preg_split("/\n/", $result)) > 2) {
        return true;
    }
}


$pids = array_keys($pidIJs);
while (sizeof($pids) > 0)
{
    $endedPids = array();
    foreach($pids as $pid)
    {
        if (!isRunning($pid))
        {
            $ij = $pidIJs[$pid];
            print("message.php $ij[0] $ij[1] has completed.\n");
            array_push($endedPids, $pid);
        }
    }
    $pids = array_diff($pids, $endedPids);
}

Slight difference in the exec command, I'm sending PID to standard output instead of to a file so we can attach it to an i/j combination.

This code will loop from 0 to 4, and then 0 to 4 internally, running the message.php script with arguments of $i and $j.

message.php will sleep for $j*2 seconds, and then print Hello World $i $j.

Running threads.php, you can see it fire off the messages.php script in the order of (0,0), (0,1), ... (0,4), (1,0), (1,1), ... (4,4), but then when testing the PID status, we see each process terminate in the order: (0,0), (1,0), ..., (4,0), (0,1), (1,1), (2,1), ..., (4,4).

This is because it takes $j*2 seconds for each messages.php thread to run. So you get back all of the $j=0 threads first, then all the $j=1 threads, etc.

Moradabad answered 13/11, 2023 at 19:28 Comment(0)
S
0

PHP scripting is not like other desktop application developing language. In desktop application languages we can set daemon threads to run a background process but in PHP a process is occuring when user request for a page. However It is possible to set a background job using server's cron job functionality which php script runs.

Shanell answered 3/8, 2014 at 12:50 Comment(0)
B
0

For those of us using Windows, look at this:

Reference: http://php.net/manual/en/function.exec.php#43917

I too wrestled with getting a program to run in the background in Windows while the script continues to execute. This method unlike the other solutions allows you to start any program minimized, maximized, or with no window at all. llbra@phpbrasil's solution does work but it sometimes produces an unwanted window on the desktop when you really want the task to run hidden.

start Notepad.exe minimized in the background:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("notepad.exe", 7, false); 
?> 

start a shell command invisible in the background:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("cmd /C dir /S %windir%", 0, false); 
?> 

start MSPaint maximized and wait for you to close it before continuing the script:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("mspaint.exe", 3, true); 
?> 

For more info on the Run() method go to: http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

Edited URL:

Go to https://technet.microsoft.com/en-us/library/ee156605.aspx instead as the link above no longer exists.

Bissau answered 5/6, 2015 at 17:5 Comment(5)
What needs to be done to run this code? I get Fatal error: Class 'COM' not foundYolandoyolane
P.s. Link to MSDN is brokenYolandoyolane
@Alph.Dev: The msdn doc seems to have been temporarily removed. It contained more examples and explanations of how to use the WScript.Shell.Bissau
@Alph.Dev: For the class COM not found, that's another thing altogether. Check that out on google. You might also check this php.net/manual/en/com.installation.phpBissau
@Alph.Dev please check technet.microsoft.com/en-us/library/ee156605.aspx for updated information on the Run() functionBissau
I
0

New answer to an old question. Using this library, the following code would spawn an asynchronous/parallel PHPThread to do background work.

  • Must have pcntl, posix, and socket extensions
  • Designed for/tested in CLI mode.

EZ code sample:

 function threadproc($thread, $param) {
 
    echo "\tI'm a PHPThread.  In this example, I was given only one parameter: \"". print_r($param, true) ."\" to work with, but I can accept as many as you'd like!\n";
 
    for ($i = 0; $i < 10; $i++) {
        usleep(1000000);
        echo "\tPHPThread working, very busy...\n";
    }
 
    return "I'm a return value!";
}
 

$thread_id = phpthread_create($thread, array(), "threadproc", null, array("123456"));
 
echo "I'm the main thread doing very important work!\n";
 
for ($n = 0; $n < 5; $n++) {
    usleep(1000000);
    echo "Main thread...working!\n";
}
 
echo "\nMain thread done working.  Waiting on our PHPThread...\n";
 
phpthread_join($thread_id, $retval);
 
echo "\n\nOur PHPThread returned: " . print_r($retval, true) . "!\n";
Intrusive answered 29/3, 2022 at 1:27 Comment(0)
M
0

None of the previous answers work in the context of an AJAX call, or specifically an AJAX to a function in a controller.php (eg. contollerClass.php/ajaxfunction).

They're only fine if you want to run 1 function per php.page or run a linux command - they won't help for php functions with arguments.

The only method which has worked for me (after researching exec/shell_exec/spatie/amphp/gearman/etc.. ) is:

ending session for later just before the background task

function someFn(){
   session_write_close();

 /* code bits in here */

and restarting the session just before the end of the background task


   session_start();
   return / echo json_encode($response)

} // end of function
Maryannamaryanne answered 23/3, 2023 at 7:54 Comment(0)
D
-14

I know it is a 100 year old post, but anyway, thought it might be useful to someone. You can put an invisible image somewhere on the page pointing to the url that needs to run in the background, like this:

<img src="run-in-background.php" border="0" alt="" width="1" height="1" />

Dispassion answered 12/8, 2011 at 2:54 Comment(2)
Really bad solution. If user would leave the page then process will be interrupted.Z
the "invisble image" and its link are exposed in the page source code.Exorcist

© 2022 - 2024 — McMap. All rights reserved.