PHP CLI: Detached Processes
PHP CLI : Introduction | Paths | PHP INI | Process Running | Detached Processes | Hidden Process | User Input | Files | Search & Replace | Recursive Search & Replace
|
|
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
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. |
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.
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.
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())
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.
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.
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.
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.
MPG (Ric) |