Hello and thank you in advance for your interest.
During the past two weeks I've been struggling with something that is driving me nuts. I have APACHE (2.2.22) and PHP (5.4.3) installed on my Windows box and I'm trying to call a program from a PHP script which calls another program at the same time. Both programs are written in C/C++ and compiled with MINGW32. Regarding to Windows version, I've tested Windows 2003 Server and Windows 7 Professional and both give me the same problems.
Let me introduce these two programs:
1) mytask.exe: this is a program that is to be executed in background and that periodically populates its status to a file.
2) job.exe: this is the program I want to call from the PHP script. Its goal is to spawn mytask.exe as an independent process (not as a thread).
If I run from a Console window the command below, then job.exe immediately returns and leaves mytask.exe running on the background until it terminates.
> job.exe spawn mytask.exe
jobid=18874111458879FED
Note that job.exe dumps an identifier which is used to manage mytask.exe. For example:
> job.exe status 18874111458879FED
RUNNING
I've checked that if I run the first command from a PHP script, the PHP script randomly blocks forever. If I look to the Windows's task manager, I can see that job.exe is there in a zombie-like state. I can assert that job.exe effectively reaches the usual return 0; statement in its main() routine, so it seems to be something under the wood, in the C runtime.
Furthermore, if I write a simple mytask.exe that simply sleeps for 10 seconds then the PHP script blocks for 10 seconds too (or blocks forever following the random behavior I've just mentioned). In other words, I have no way to make job.exe spawning a process without waiting for it to end, when I call job.exe from a PHP script.
So: there's something I'm doing wrong when spawning mytask.exe and, now, here comes the second part of this digression.
I use the WINAPI function CreateProcess() to spawn the tasks from job.exe. As of the MSDN documentation, I call CreateProcess with bInheritHandles = FALSE, to avoid the child process to yield I/O deadlocks with the PHP script. I also close the process handles returned by CreateProcess() in the PROCESS_INFORMATION structure. The only thing I don't do is waiting for the process to end. On the other hand, regarding to the PHP side, I've tried both exec() and proc_open() PHP functions to call job.exe with no success.
My last observations, though, seem to be in the right way, yet they do not convince me because I don't understand why they work somehow. The fact is that if mytask.exe does fclose(stdout) before sleeping, then the PHP script returns immediately. BUT, HOW??? I told CreateProcess() to not inherit handles, so why am I getting these results? Anyway, I cannot stick with this patch because programs launched by job.exe may not know about who is calling them, so closing stdout from those programs is not a good solution. In UNIX, things are so simple... One just calls fork(), closes standard streams and then calls execve to call the program. In Windows, I've also tried to create a wrapper thread with CreateThread() (to emulate fork()) and then call CreateProcess() from that thread after closing standard streams... but that closed the streams of job.exe too!
All this question could be synthesized in a single one: how can I execute from PHP a program that creates other processes?
I hope somebody could shed some light on this issue... Thank you very much!
I think I've nailed the solution, which is divided in two parts:
1) Regarding the fact the main process stops until the child process ends.
As of MSDN documentation, this is the definition of CreateProcess():
BOOL WINAPI CreateProcess(
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFO lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);
As I said in my question, I pass FALSE to bInheritHandles, but I was also passing 0 to dwCreationFlags. After a little bit of more research, I found that there's a flag called DETACHED_PROCESS, for which MSDN says:
For console processes, the new process does not inherit its parent's console (the default). The new process can call the AllocConsole function at a later time to create a console. For more information, see Creation of a Console.
Now, job.exe returns immediately despite the fact the child process continues its execution.
2) Regarding the fact the PHP script randomly hangs when calling exec()
It seems to be a bug of PHP. Running exec() family functions in the context of a PHP session may make APACHE to randomly hang, being necessary to restart the server. I found a thread in the Internet in which a user noticed that closing the session (thru session_write_close()) before calling exec() would prevent the script from hanging. The same applies for the proc_open/proc_close functions. So, my script now looks like this:
session_write_close(); //Close the session before proc_open()
$proc = proc_open($cmd,$pipedesc,$pipes);
//do stuff with pipes...
//... and close pipes
$retval = proc_close($proc);
session_start(); //restore session
Hope this helps.
Related
I'm trying to execute a python script using php with an exec command like this:
exec("python /address/to/script.py");
I don't need the script to run to completion, so after it does what I need, I call sys.exit() from within it. Execution is passed back to the php script, which is great, however the python process is still running. I can see it in my server's process list. Is there more that's required to fully kill it?
Additional Info
The python script was written by a third party.
I know very little about python, just enough to add the sys.exit() call.
The script could still be executing some cleanup code, or you could be calling sys.exit() from a child process which will essentially be calling thread.exit(), leaving the parent process running.
Check that the sys.exit() call is in the main part of the script and that no error handling is interfering with the SystemExit exception, or alternatively you could try os._exit(). Also ensure that an ampersand (&) is not present within the command passed to exec() as this will cause the script to run as a background process.
Note that os._exit() is not favourable since it doesn't do any cleanup, and essentially ends the process immediately.
Edit To end the script from within your try block you could do something like this:
try:
# Existing Code
except SysExit:
os._exit() # quit the process
except:
# Existing error handling
Ideally the application logic should make use of message passing or something similar so that a child thread could notify the main thread that it should terminate.
I have the following PHP 5.6.19 code on a Ubuntu 14.04 server. This code simply connects to a MySQL 5.6.28 database, waits a minute, launches another process of itself, then exits.
Note: this is the full script, and it's purpose is to demonstrate the problem - it doesn't do anything useful.
class DatabaseConnector {
const DB_HOST = 'localhost';
const DB_NAME = 'database1';
const DB_USERNAME = 'root';
const DB_PASSWORD = 'password';
public static $db;
public static function Init() {
if (DatabaseConnector::$db === null) {
DatabaseConnector::$db = new PDO('mysql:host=' . DatabaseConnector::DB_HOST . ';dbname=' . DatabaseConnector::DB_NAME . ';charset=utf8', DatabaseConnector::DB_USERNAME, DatabaseConnector::DB_PASSWORD);
}
}
}
$startTime = time();
// ***** Script works fine if this line is removed.
DatabaseConnector::Init();
while (true) {
// Sleep for 100 ms.
usleep(100000);
if (time() - $startTime > 60) {
$filePath = __FILE__;
$cmd = "nohup php $filePath > /tmp/1.log 2>&1 &";
// ***** Script sometimes exits here without opening the process and without errors.
$p = popen($cmd, 'r');
pclose($p);
exit;
}
}
I start the first process of the script using nohup php myscript.php > /tmp/1.log 2>&1 &.
This process loop should go on forever but... based on multiple tests, within a day (but not instantly), the process on the server "disappears" without reason. I discovered that the MySQL code is causing the popen code to fail (the script exits without any error or output).
What is happening here?
Notes
The server runs 24/7.
Memory is not an issue.
The database connects correctly.
The file path does not contain spaces.
The same problem exists when using shell_exec or exec instead of popen (and pclose).
I also know that popen is the line that fails because I did further debugging (not shown above) by logging to a file at certain points in the script.
Is the parent process definitely exiting after forking? I had thought pclose would wait for the child to exit before returning.
If it isn't exiting, I'd speculate that because the mySQL connection is never closed, you're eventually hitting its connection limit (or some other limit) as you spawn the tree of child processes.
Edit 1
I've just tried to replicate this. I altered your script to fork every half-second, rather than every minute, and was able to kill it off within about 10 minutes.
It looks like the the repeat creation of child processes is generating ever more FDs, until eventually it can't have any more:
$ lsof | grep type=STREAM | wc -l
240
$ lsof | grep type=STREAM | wc -l
242
...
$ lsof | grep type=STREAM | wc -l
425
$ lsof | grep type=STREAM | wc -l
428
...
And that's because the child's inheriting the parent's FDs (in this case for the mySQL connection) when it forks.
If you close the mySQL connection before popen with (in your case):
DatabaseConnector::$db = null;
The problem will hopefully go away.
I had a similar situation using pcntl_fork() and a MySQL connection. The cause here is probably the same.
Background info
popen() creates a child process. The call to pclose() closes the communication channel and the child process continues to run until it exits. This is when the things start to go out of control.
When a child process completes, the parent process receives a SIGCHLD signal. The parent process here is the PHP interpreter that runs the code you posted. The child process is the one launched using popen() (it doesn't matter what command it runs).
There is a small thing here you probably don't know or you have found in the documentation and ignored it because it doesn't make much sense when one programs in PHP. It is mentioned in the documentation of sleep():
If the call was interrupted by a signal, sleep() returns a non-zero value.
The sleep() PHP function is just a wrapper of the sleep() Linux system call (and usleep() PHP function is a wrapper of the usleep() Linux system call.)
What is not told in the PHP documentation is clearly stated in the documentation of the system calls:
sleep() makes the calling thread sleep until seconds seconds have elapsed or a signal arrives which is not ignored.
Back to your code.
There are two places in your code where the PHP interpreter calls the usleep() Linux system function. One of them is clearly visible: your PHP code invokes it. The other one is hidden (see below).
What happens (the visible part)
Starting with the second iteration, if a child process (created using popen() on a previous iteration) happens to exit while the parent program is inside the usleep(100000) call, the PHP interpreter process receives the SIGCHLD signal and its execution resumes before the time being out. The usleep() returns earlier than expected. Because the timeout is short, this effect is not observable by the naked eye. Put 10 seconds instead of 0.1 seconds and you'll notice it.
However, apart from the broken timeout, this doesn't affect the execution of your code in a fatal manner.
Why it crashes (the invisible part)
The second place where an incoming signal hurts your programs execution is hidden deep inside the code of the PHP interpreter. For some protocol reasons, the MySQL client library uses sleep() and/or usleep() in several places. If the interpreter happens to be inside one of these calls when the SIGCHLD arrives, the MySQL client library code is resumed unexpectedly and, many times, it concludes with the erroneous status "MySQL server has gone away (error 2006)".
It's possible that your code ignores (or swallows) the MySQL error status (because it doesn't expect it to happen in that place). Mine didn't and I spent a couple of days of investigation to find out the facts summarized above.
A solution
The solution for the problem is easy (after you know all the internal details exposed above). It is hinted in the documentation quote above: "a signal arrives which is not ignored".
The signals can be masked (ignored) when their arrival is not desired. The PHP PCNTL extension provides the function pcntl_sigprocmask(). It wraps the sigprocmask() Linux system call that sets what signals can be received by the program from now on (in fact, what signals to be blocked).
There are two strategies you can implement, depending of what you need.
If your program needs to communicate with the database and be notified when the child processed complete then you have to wrap all your database calls within a pair of calls to pcntl_sigprocmask() to block then unblock the SIGCHLD signal.
If you doesn't care when the child processes complete then you just call:
pcntl_sigprocmask(SIG_BLOCK, array(SIGCHLD));
before you start creating any child process (before the while()).
It makes your process ignore the termination of the child processes and lets it run its database queries without undesired interruption.
Warning
The default handling of the SIGCHLD signal is to call wait() in order to let the system cleanup after the completed child process. What happens if the signal is not handled (because its delivery is blocked) is explained in the documentation of wait():
A child that terminates, but has not been waited for becomes a "zombie". The kernel maintains a minimal set of information about the zombie process (PID, termination status, resource usage information) in order to allow the parent to later perform a wait to obtain information about the child. As long as a zombie is not removed from the system via a wait, it will consume a slot in the kernel process table, and if this table fills, it will not be possible to create further processes. If a parent process terminates, then its "zombie" children (if any) are adopted by init(1), which automatically performs a wait to remove the zombies.
In plain English, if you block the reception of SIGCHLD signal, then you have to call pcntl_wait() in order to cleanup the zombie child processes.
You can add:
pcntl_wait($status, WNOHANG);
somewhere inside the while loop (just before it ends, for example).
the script exits without any error or output
Not surprising when there's no error checking in the code. However if it really is "crashing", then:
if the cause is trapped by the PHP runtime then it will be trying to log an error. Have you tried delibertely creating an error scenario to varify that the reorting/logging is working as you expect?
if the error is not trapped by the PHP runtime, the the OS should be dumping a corefile - have you checked the OS config? Looked for the core file? Analyzed it?
$cmd = "nohup php $filePath > /tmp/1.log 2>&1 &";
This probably doesn't do what you think it does. When you run a process in the background with most versions of nohup, it still retains a relationship with the parent process; the parent cannot be reaped until the child process exits - and a child is always spawning another child before it does.
This is not a valid way to keep your code running in the background / as a daemon. What the right approach is depends on what you are trying to achieve. Is there a specific reason for attempting to renew the process every 60 seconds?
(You never explicitly close the database connection - this is less of an issue as PHP should do this when exit is invoked).
You might want to read this and this
I suggest that process doesn't exit after pclose. In this case every process holds it's own connection to db. After some time connectons limit of MySQL is reached and new connection fails.
To understand what's going on - add some logs before and after strings DatabaseConnector::Init(); and pclose($p);
I noticed that when I have an endless worker I cannot profile PHP shell scripts. Because when it's killed it doesn't send the probe.
What changes shall I do?
When you are trying to profile a worker which is running an endless loop. In this case you have to manually edit your code to either remove the endless loop or instrument your code to manually call the close() method of the probe (https://blackfire.io/doc/manual-instrumentation).
That's because the data is sent to the agent only when the close() method is called (it is called automatically at the end of the program unless you killed it).
You can manually instrument some code by using the BlackfireProbe class that comes bundled with the Blackfire's probe:
// Get the probe main instance
$probe = BlackfireProbe::getMainInstance();
// start profiling the code
$probe->enable();
// Calling close() instead of disable() stops the profiling and forces the collected data to be sent to Blackfire:
// stop the profiling
// send the result to Blackfire
$probe->close();
As with auto-instrumentation, profiling is only active when the code is run through the Companion or the blackfire CLI utility. If not, all calls are converted to noops.
I don't know, maybe in 2015 following page did not exist, but now you can do profiling in following way: https://blackfire.io/docs/24-days/17-php-sdk
$blackfire = new LoopClient(new Client(), 10);
$blackfire->setSignal(SIGUSR1);
$blackfire->attachReference(7);
$blackfire->promoteReferenceSignal(SIGUSR2);
for (;;) {
$blackfire->startLoop($profileConfig);
consume();
$blackfire->endLoop();
usleep(400000);
}
Now you can send signal SIGUSR1 to process of this worker and LoopClient will start profiling. It'll listen 10 iterations of method consume and send last probe. After that it'll stop profiling.
I have a PHP script that runs on a shared hosting environment server. This PHP script takes a long time to run. It may take 20 to 30 minutes to finish a run. It is a recurring background process. However I do not have control over when the process starts (it could be triggered every five minutes, or every three hours, no one knows).
Anyway, at the beginnin of this script I would like to detect if the previous process is still running, if the earlier run is still running and has not finished, then I would not run the script again. If it is not running, then I run the new process.
In other words, here is a pseudo code. Let's call the script abc.php
1. Start script abc.php
2. Check if an older version of abc.phh is still running. If it is running, then terminate
3. If it is not running, then continue with abc.php and do your work which might take 30 minutes or more
How can I do that? Please keep in mind this is shared hosting.
UPDATE: I was thinking of using a DB detection mechanism. So, when the script starts, it will set a value in a DB as 'STARTED=TRUE', when done, it will set 'STARTED=FALSE'. However this solution is not proper, because there is no garantee that the script will terminate properly. It might get interrupted, and therefore may not update the STARTED value to FALSE. So the DB solution is out of the question. It has to be a process detection of some sort, or maybe a different solution that I did not think off. Thanks.
If this is a CGI process, I would try using exec + ps, if the latter is available in your environment. A quick SO search turns up this answer: https://stackoverflow.com/a/7182595/177920
You'll need to have a script that is responsible for (and separate from) checking to see if your target script is running, of course, otherwise you'll always see that your target script is running based on the order of ops in your "psuedo code".
You can implement a simple locking mechanism: Create a tmp lock file when script starts and check before if the lock file already exists. If it does, dont run the script, it it doesnt create a lock file and run the script. At then end of successful run, delete the lock file so that it will run properly next time.
if(!locked()) {
lock();
// your code here
unlock();
} else {
echo "script already running";
}
function lock() { file_put_contents("write.lock", 'running'); }
function locked() { return file_exists("write.lock"); }
function unlock() { return unlink("write.lock"); }
I've stumbled upon a phenomenon that I can't explain myself.
I'm using popen to execute php and then execute a php script that way, and pclose to close that.
So far so fine. I ran into quite some severe troubles as the script where I used this didn't execute and instead after trying it 3 times in a row I crashed the zend-server (no page would open any more). I found out that the reason to this was that I used a wrong directory for the php.exe. Example:
if (pclose(popen("C:\wrongDir\php\php.exe C:\Zend\Apache2\htdocs\myApp\public\mytest.php 57 > C:\Logs\1\0\jobOut.log 2> C:\Logs\1\0\jobErr.log"))>-1)
{
.....
}
Aside from the "wrongDir" all other dirs were correct....the popen even created the jobOut and jobErr files (which were empty). (remark: PHP is not in a searchpath that is why it wasn't found without the correct path)
Even though I now solved the problem....I have the question if this is a normal behaviour there, or if I have done something wrong (maybe even server settings). As from what I read in the manual about both commands it sounded to me that in the my case I should have had a return value of either -1 or 0 and not the problem I ran into with the process and then the server hanging).
Thanks.
It appears that pclose() doesn't return the exit status of the process but rather of it's ability to close the process.
To get the process termination 'code' use pcntl_wifexited() and pcntl_wexitstatus()
http://php.net/manual/en/function.pclose.php
http://php.net/manual/en/function.pcntl-wexitstatus.php