We are running PHP on a Windows server (a source of many problems indeed, but migrating is not an option currently). There are a few points where a user-initiated action will need to kick off a few things that take a while and about which the user doesn't need to know if they succeed or fail, such as sending off an email or making sure some third-party accounts are updated. If I could just fork with pcntl_fork()
, this would be very simple, but the PCNTL functions are not available in Windows.
It seems the closest I can get is to do something of this nature:
exec( 'php-cgi.exe somescript.php' );
However, this would be far more complicated. The actions I need to kick off rely on a lot of context that already will exist in the running process; to use the above example, I'd need to figure out the essential data and supply it to the new script in some way. If I could fork, it'd just be a matter of letting the parent process return early, leaving the child to work on a few more things.
I've found a few people talking about their own work in getting various PCNTL functions compiled on Windows, but none seemed to have anything available 开发者_开发知识库(broken links, etc).
Despite this question having practically the same name as mine, it seems the problem was more execution timeout than needing to fork. So, is my best option to just refactor a bit to deal with calling php-cgi, or are there other options?
Edit: It seems exec()
won't work for this, at least not without me figuring some other aspect of it, as it waits until the call returns. I figured I could use START
, sort of like exec( 'start php-cgi.exe somescript.php' );
, but it still waits until the other script finishes.
how about installing psexec and use the -d
(don't wait) option
exec('psexec -d php-cgi.exe somescript.php');
Get PSExec and run the command:
exec("psexec -d php-cgi.exe myfile.php");
PSTools are a good patch in, but I'll leave this here:
If your server runs windows 10 and it has the latest updates, you can install a Linux subsystem, which has its own Kernel that supports native forking.
This is supported by Microsoft officially.
Here's a good guide on how to do it.
Once you've installed the subsystem itself, you need to install php on the subsystem.
Your windows "c:\" drive can be found under "/mnt/c", so you can run your php from the subsystem, which supports forking (and by extension the subsystem's php can use pcntl_fork).
Example: php /mnt/c/xampp/htdocs/test.php
If you want to run the subsystem's php directly from a windows command line you can simply use the "wsl" command.
Assuming you're running this from under "C:\xampp\htdocs\"
Example: wsl php main.php
The "wsl" command will resolve the path for you, so you don't need to do any dark magic, if you call the command under c:\xampp\htdocs, the subsystem will resolve it as "/mnt/c/xampp/htdocs/".
If you're running your server as an apache server, you don't really need to do anything extra, just stop the windows apache server and start the linux one and you're done. Obviously you'll need to install all the missing php modules that you need on the subsystem.
You can create a daemon/background process to run the code (e.g. sending emails) and the request would just have to add items to the queue, let the deamon do the heavy lifting.
For example, a file send_emails.bat:
cls
C:\PHP533\php.exe D:\web\server.php
exit
open windows task scheduler, and have the above send_emails.bat run every 30 minutes. Make sure only one instance runs at a time or you might run each task in multiples, or send each email twice. I say 30 minutes in case something breaks temporarily (memory issues, database unavailable, etc), it will re-start every 30 minutes rather than having a never ending process that just stops. The following is a skeleton daemon... not complete or tested I am just typing out an example:
<?php
set_time_limit(60*30); // don't run
$keepgoing = true;
$timeout = time()+ 60*29; // 29 minutes
while(time() < $timeout)
{
// grab emails from database
$result = $db->query('select subject, body, to_email FROM email_queue');
if($result->num_rows == 0)
{
sleep(10); // so we are not taxing the database
}
else
{
while($row = $result->fetch_assoc())
{
// send email
}
}
}
exit;
?>
Finally you just need the request to add the item to the queue in a database, and let the daemon handle the heavy lifting.
$db->query('insert into email_queue(to,subject,body) values ('customer@email.com','important email','<b>html body!</b>');
精彩评论