How to send long running PHP to the background
Recently I’ve created an API to manage and control a SaaS PHP App that I’ve built for my job. This API manages everything, deploying new versions of the App, provisioning new clients on the App, etc.
The deploy new version call is quite a long running one. It zips up a clone of the repo, puts it in S3, copies the static CSS, JS, images, fonts, etc into S3 for static content serving behind CloudFront and SSHs into all the EC2 instances of an AutoScaling group to deploy the app to a subfolder.
That whole process, mainly due to PHPs single-thread-ness is very slow and long running. It can take upwards of 15 minutes to deploy.
We have a control panel that issues these commands, but the problem is the browser. Browsers don’t always like 15 minute requests via Ajax as well as Elastic Load Balancers having a hard timeout limit of 1-2 minutes, and its just not reliable enough.
We could set ignore_user_abort(true) but to me, that doesn’t seem clean enough. I want to return a 200 from the Version Create API call.
Solution
Send a PHP-CLI process to the background of the server to do the long running hard work for you. This’ll only work in linux with php-cli installed.
This method requires two files: one to create the process and the long running PHP file you’d like to execute in the background.
Lets say you have a 15 minute script called long/deploy.php. This is how you’d fork of the script into the background to run for as long as it needs.
exec("php ".__dir__."/long/deploy.php >/dev/null 2>&1 &");
This would simply send the long deploy script of into the background. Sending all of it’s output to /dev/null and the error stream there too (that’s the 2>&1 bit).
Going further
Because we are sending the long running php process to the abyss, we aren’t able to ask it how it’s doing, unless we make the script write how it’s doing to a text file or a database.
In the example of my API’s deploy call, I find out how my long running script is going by updating a version_status INT(4) column in my versions table. The catch is, I create this row before I start the deploy script. This is so that I’m able to return the new version_id of the version created. But means we have to pass the long running process some arguments so it knows what row to update.
Sending arguments can be done easily by adding to the exec command above:
$result_id = $mysqli->insert_id; /* EG: 1234215 */
$args = array(0 => $result_id);
exec("php ".__dir__."/long/deploy.php ".implode(" ", $args)." >/dev/null 2>&1 &");
Inside long/deploy.php
print_r($argv);
/* Would output: */
Array (
0 => 1234215 /* The $result_id above */
)
So this means you’ll be able to pass any string or int via the command and use it in your long running php process later on.