PHP CLI: Detached Processes

 

MPG UniCenter

UniServer 5.0-Nano
PHP CLI.

PHP CLI detaching processes

This page looks at detaching processes from PHP scripts. It allows you to continue with a current scripts execution while running other processes either in the foreground or hidden in the background.

When a script starts another process it generally has to wait for that process to end before executing the next instruction. Not a problem if you are going to process data from that process. However if all you want to do is start a process and continue, your script will hang if that process never terminates. A cron script for example never terminates it sites in an infinite loop periodically kicking off some other process.

A solution to this scenario is to detach the cron process. This series of tutorials provides practical examples you can run alternatively jump straight to the solution.

Initial test setup

Edit our two test files Run.bat and test_1.php contained in folder UniServer to have the following content:

Run.bat
TITLE CLI TEST BAT
COLOR B0
@echo off
cls
echo.
usr\local\php\php.exe -n test_1.php
echo.
:pause

 

This batch file runs test script test1.php

Nano : Paths are for running on UniServer Nano

Mona : If you want to run the script on UniServer Mona change the path as shown

udrive\usr\local\php\php.exe -n test_1.php

Note: The pause has been disabled. Batch file runs and instantly closes.

test_1.php
<?php
  echo " Running process 1\n";
  $cmd1a = 'start "Process 1" cmd.exe /k "COLOR D0 && mode con:cols=20 lines=10 ';
  $cmd1b = '&& @echo off && usr\local\php\php.exe -n test_2.php &&exit"';
  $cmd1 = $cmd1a.$cmd1b;
  exec($cmd1);

usleep(2000000);
  echo " Running process 2\n";
  $cmd2a = 'start "Process 2" cmd.exe /k "COLOR E0 && mode con:cols=20 lines=10 ';
  $cmd2b = '&& @echo off && usr\local\php\php.exe -n test_2.php &&exit"';
  $cmd2 = $cmd2a.$cmd2b;
  system($cmd2);

usleep(2000000);
  echo " Running process 3\n";
  $cmd3a = 'start "Process 3" cmd.exe /k "COLOR F0 && mode con:cols=20 lines=10 ';
  $cmd3b = '&& @echo off && usr\local\php\php.exe -n test_2.php &&exit"';
  $cmd3 = $cmd3a.$cmd3b;
  `$cmd3`;
exit(0); // Script ran OK
?>

 

 

This script starts three command processes.

In PHP there are several ways to start a process.

I have shown three methods:

exec() - function

system() - function

`$cmd3` - back ticks


Nano : Paths are for running on UniServer Nano

Mona : If you want to run the script on UniServer Mona change the three paths as shown

udrive\usr\local\php\php.exe -n test_2.php

Create a new file test_2.php with the following content:

test_2.php
<?php
$a=0;
while($a !=10){
  usleep(1000000); 
 echo "  ".$a."\n";
 $a=$a+1;
}
exit(0); // Script ran OK
?>

 

Purpose of this script is to provide visual feedback to show something is happening.

The script uses a while loop to display digits 0 to 9

A delay of 1 second is introduced before displaying a digit

On completion script terminate.

Top

Example 1

The above contrived example demonstrates the problem.

Run the batch file (double click on Run.bat)

Intention is to run all three processes exec($cmd1), system($cmd2) and `$cmd3` at the same time and for the main script test1.php to instantly close.

However PHP functions such as exec(), system(), passthru() and even back ticks start a process and wait for any data to be returned from the process before continuing onto the next instruction.

Waiting for a process to complete, clearly is not desirable if you want to run a processes permanently in the background and have the initial script terminate.

Top

Problem

Running the example clearly shows this problem. The first process opens a command window, counts up to ten then closes. This is repeated by the next two processes finally closing the main script test1.php.

Note: The three different ways to start a process exec(), system() and back-ticks in test_1.php included only to show there are different ways to start a process.

Top

Example 2

Modify test file test_1.php to have the following content:

test_1.php
<?php
  echo " Running process 1\n";
  $cmd1a = 'start "Process 1" cmd.exe /k "COLOR D0 && mode con:cols=20 lines=10 ';
  $cmd1b = '&& @echo off && usr\local\php\php.exe -n test_2.php &&exit"';
  $cmd1 = $cmd1a.$cmd1b;
  pclose(popen($cmd1,'r'));    // Start a new forked process close file pointer  

  usleep(2000000); 
  echo " Running process 2\n";
  $cmd2a = 'start "Process 2" cmd.exe /k "COLOR E0 && mode con:cols=20 lines=10 ';
  $cmd2b = '&& @echo off && usr\local\php\php.exe -n test_2.php &&exit"';
  $cmd2 = $cmd2a.$cmd2b;
  pclose(popen($cmd2,'r'));    // Start a new forked process close file pointer  

  usleep(2000000); 
  echo " Running process 3\n";
  $cmd3a = 'start "Process 3" cmd.exe /k "COLOR F0 && mode con:cols=20 lines=10 ';
  $cmd3b = '&& @echo off && usr\local\php\php.exe -n test_2.php &&exit"';
  $cmd3 = $cmd3a.$cmd3b;
  pclose(popen($cmd3,'r'));    // Start a new forked process close file pointer  
exit(0); // Script ran OK
?>

 

 

This script starts three command processes.

Main difference these three methods exec(), system() and back ticks have been replaced with pclose(popen())


Nano : Paths are for running on UniServer Nano

Mona : If you want to run the script on UniServer Mona change the three paths as shown

udrive\usr\local\php\php.exe -n test_2.php

Run the batch file (double click on Run.bat). It clearly shows three detached processes with the main script terminated before either of the three detached process.

Top

Solution

A neat one line solution is provided using the PHP function popen().

Details taken from on-line manual:

resource popen ( string $command , string $mode )

Opens a pipe to a process executed by forking the command given by command. 
Returns a file pointer identical to that returned by fopen(), except that it is unidirectional (may only be used for reading or writing) and must be closed with pclose(). This pointer may be used with fgets(), fgetss(), and fwrite(). 

If an error occurs, returns FALSE.

It may not be obvious (I had to read it several times before it clicked) how the above provides a solution. What’s important a process is forked meaning that process is run concurrently and independent of the current process (calling script). A file pointer is the only thing linking the two, kill this and the two processes are detached. Combined these two snippets of information and we have a one-liner as follows:

pclose(popen($command,'r'));
  • It creates a pipe (popen) to a process for reading (‘r’)
  • Process is specified by $command
  • Instantly closes (pclose) the file pointer created hence detaching the process.

Top

Removing dross

The above demo (Example 2) adds unnecessary complexity because the CLI program php.exe when run starts its own command window and runs in that. However the above code forces this to run in a previously created command window.

The sole purpose of this first command window is to set window size and colour for the CLI command window to inherit. It makes the demo easier to see hence can be removed. Added to this is duplication, each process runs the same script test_2.php hence can remove this repetition.

Edit script test_1.php to have the following content:

test_1.php
<?php
  echo " Running process 1\n";
  $cmd = 'start usr\local\php\php.exe -n test_2.php';
  pclose(popen($cmd,'r'));    // Start a new forked process close file pointer  

  usleep(2000000); 
  echo " Running process 2\n";
  pclose(popen($cmd,'r'));    // Start a new forked process close file pointer  

  usleep(2000000); 
  echo " Running process 3\n";
  pclose(popen($cmd,'r'));    // Start a new forked process close file pointer  
exit(0); // Script ran OK
?>

 

 

Nano : Paths are for running on UniServer Nano

Mona : If you want to run the script on UniServer Mona change the path as shown

udrive\usr\local\php\php.exe -n test_2.php

Run the batch file (double click on Run.bat). OK pretty windows no longer exist but the core functionality is identical.

Top

Summary

I do like neat little snippets of code. The above fits that bill admirably and worth popping into any CLI toolbox for later use.

I have shown how easy it is to detach a process the above examples run in the foreground. Generally this is not an issue however if a process runs in an infinite loop such as a cron script the command window remains visible.

The next page addresses this problem, it covers hidden processes always good to have a few solutions and pop them in your CLI toolbox.

This tutorial series has a specific objective, which I will explain later.

Top


  MPG (Ric)