I'm trying to write and run a PHP extension that spins off a pthread (using the POSIX library in C) which detaches and runs forever (it reads messages from a queue, but for the purposes of this question that isn't important, and I'm just trying to get it to spin off a function that just detaches and returns for proof of concept).
To clarify: This is not using the pthreads extension in php, but using the POSIX pthreads library in a custom php extension. I've confirmed that the environment can compile and execute C code that uses the POSIX pthreads library.
The function that spins off the thread is:
static int init_thread()
{
int ret = 0;
pthread_t thread;
ret = pthread_create(&thread, NULL, threadFunc, NULL);
if (ret != 0)
{
fprintf(stderr, "pthread_create failed with error code %d\n", ret);
return -1;
}
return 0;
}
But this seems to stop any of the extension from executing.
Note: I've also tried using a php extension global variable pthread_t instead of instantiating a pthread_t in init_thread but to the same results...
This function is called in RINIT and is only called the very first time a PHP worker process starts, not every request (I've confirmed this separately). For debugging I've tested all this with threadFunc simply detaching and returning with pthread_detach(pthread_self()); and return 0;.
I've confirmed that the extension is set up properly and works without the pthread_create line by printing to log files from MINIT, GINIT, and even in RINIT.
However, whenever the ret = pthread_create(&thread, NULL, threadFunc, NULL); line in init_thread is uncommented, it seems like nothing in the extension runs.
For example, MINIT supposedly runs before any time RINIT runs, and I create and write to a file to confirm that MINIT is running (I use this file as "proof" that the extension is executing code as I expect). However, when the pthread_create line is uncommented, that file that MINIT creates is never even made or written too, even though MINIT should run before that pthread_create is ever reached.
There's no clear error in the nginx or php7.4 log files, and the site works normally even when the extension seems to be broken.
I'm really at my wits end with why this isn't working, as my use of pthread_create is just to spin off a function that detaches and returns. Building with ZTS shouldn't even be required since the thread doesn't interact with PHP at all (Just in case, I've also tried defining ZTS and COMPILE_DL_EXTNAME to run ZEND_TSRMLS_CACHE_DEFINE but to no avail).
I'm running all this with php7.4, and am building the extension as a .so file with phpize, ./configure, and make, then placing the .so file appropriately and adding the extension=EXTENSION_NAME in /etc/php/7.4/fpm/php.ini.
Any and all ideas are greatly appreciated!
If all you're trying to do is spin up a child process that outlives its parent, there are easier ways, eg just call something like this from your main PHP thread:
shell_exec('bash -c "php myotherthread.php" 2>1 >/dev/null &');
Related
Let me start by saying I am totally not a PHP programmer - this was dumped on me to fix.
I have a function that sits in a file by itself, that basically looks like this:
<?php
function UploadFile($source, $destination){
$debugLogPath = '/biglongpath/debug.log';
file_put_contents($debugLogPath,PHP_EOL . "Beginning UploadFile function", FILE_APPEND);
set_include_path(get_include_path() . PATH_SEPARATOR . 'phpseclib');
require_once('Net/SFTP.php');
...Rest of the ftp code here...
}
?>
It's using phpseclib. If I run the main PHP script (that calls this function...) via a web browser, everything works great. When I run that same script via a CRON job, it dies as soon as this function is called. I've verified this by writing out a debug log right before calling the function - the first statement before the function is written to the log, but the "Beginning UploadFile function" is never written.
I'm guessing that maybe it has something to do with the require_once statement - maybe either a path problem or permissions issue when it's executed via CRON?
I've tried wrapping the entire function contents in a try/catch and writing out the Exception, but it still just dies.
I wonder why there are 3 helpful flags, when the question states, that the file is being written? However, this is not the CLI error log and therefore will not automagically log any errors there.The second one suggestion made appears more likely, while there are even more possibilities:
Make sure that these modules are being loaded for the PHP-CLI:
libsodium, openssl, mcrypt, gmp (as the composer.json hints for).
Running php --ini should show which INI files were loaded. Even if the corresponding INI files are there, make sure the instructions inside them are not commented out with a ;.
Manually running the script from CLI, as the user which runs the cronjob suggested, with error reporting enabled. If this shouldn't help, single-step into it with xdebug, to see where exactly it bugs out (NetBeans, Eclipse, VS Code and a few other IDE do support PHP debugging). This requires some effort to set it up, but then it provides a far better debugging methodology.
What function or bit of code serves as the main entry point for executing/interpreting a PHP program in the source of PHP itself? Based on things I've googled or read in books, I know that PHP is designed to work with a server of some kind (even the CLI command works by starting up "the command line SAPI", which acts as a mini-server designed to process a single request), and that the server will ask PHP to execute a program.
I know about the minit and rinit lifecycle functions, which serve as entry points for a PHP extension.
What I don't know is where does the PHP source code have this conversation with itself
Hey look, there's a PHP program in this file/string. I ought to run it
I'm not trying to accomplish any specific task here. I'm trying to understand how the internals of PHP does what it does, and find a main entry point where I can start following its execution.
Where is the entry point of the code of some SAPI?
The CLI is a standalone application. As any other application written in C, its entry point is the function main() (file sapi/cli/php_cli.c, line 1200):
int main(int argc, char *argv[])
There are two versions of the CLI for Windows, one of them is a console application and starts with the main() function described above, the other is a Windows GUI application (it doesn't create a console when it starts and uses message boxes for output) that starts with the WinMain() function (file sapi/cli/php_cli.c, line 1198).
main() and WinMain() use the same code here. They have different name and different code fragments here and there by checking if the symbol PHP_CLI_WIN32_NO_CONSOLE is defined. It is defined in file sapi/cli/cli_win32.c that is used to generate the Windows GUI application.
</Windows>
The CGI version is also a standalone console application. Its entry point is also the main() function in file sapi/cgi/cgi_main.c, line 1792.
Similar, the FPM version starts with main() in file sapi/fpm/fpm/fpm_main.c, line 1570.
Apache2 handler is a dynamically loadable module (.dll on Windows, .so on Unix-like systems). It registers some functions as event handlers for the events published by the web server (server start, pre/post configuration loaded, process request etc). These handlers are registered by the php_ap2_register_hook() function in file sapi/apache2handler/sapi_apache2.c, line 738.
(You can find details about how a loadable module integrates with Apache in the Apache documentation.)
The handler that is interesting to us is the function php_handler() that is invoked to handle a HTTP request.
In a similar manner, every SAPI has an entry point (either main() or a function that is invoked by the web server).
All these entry points do similar processing:
initialize themselves;
parse the command line arguments (only if it's CLI, CGI or other kind of standalone application);
read php.ini and/or other configuration they have (the Apache module configuration can be overridden in .htaccess);
create a stream using the input file and pass it to the function php_execute_script() defined in file main/main.c, line 2496;
cleanup and return an exit code to the calling process (the shell or the web server).
Where is the code that actually executes a PHP script?
The function php_execute_script() is a wrapper; it interprets the php.ini configuration entries auto_prepend_file and auto_append_file, prepares the list of files (auto-prepend file, main script, auto-append file) and passes the list to zend_execute_scripts() that processes them.
php_execute_script() is not always invoked, some SAPIs and command line arguments of the CLI produce the direct invocation of zend_execute_scripts().
zend_execute_scripts() is where the interesting things happen.
It compiles the PHP file (and returns a list of OP codes in op_array then, if the compilation succeeds (the returned op_array is not NULL) it executes the OP-codes. There is also exception handling and cleanup; boring work but as important as the parsing and executions nevertheless.
The compilation is a tedious process. It is done by the function zendparse() defined in the file Zend/zend_language_parser.c. The definition of the zendparse() function and the file Zend/zend_language_parser.c are nowhere to be seen in the Git repo; the parser is generated using bison and re2c that read the language syntax rules and the definition of lexical tokens from Zend/zend_language_parser.y and Zend/zend_language_scanner.l and generate the actual compiler in file Zend/zend_language_parser.c.
However, even if the hard work is not visible in the repo, the interesting parts of the compilation process are visible in the files mentioned above.
The execution of the compiled script (the list of OP codes) is done by function zend_execute() that is defined in the file Zend/zend_vm_execute.h. This is also a generated file and the interesting part is that it is generated by a PHP script.
The generator script (Zend/zend_vm_gen.php) uses zend_vm_def.h and zend_vm_execute.skl to generate zend_vm_execute.h and zend_vm_opcodes.h.
zend_vm_def.h contains the actual interpreter code that is executes to handle each OP code.
Where is the code of some function provided by the PHP core or one of its bundled extensions?
The code of the PHP functions and functions provided by extensions is somehow easier to follow. The functions included in the PHP core are located in files in the ext/standard directory, the functions provided by other extensions are located in files in the corresponding ext subdirectories.
In these files, the C functions that implement PHP functions are declared using the PHP_FUNCTION() macro. For example, the implementation of the PHP function strpos()
starts in file ext/standard/string.c, line 1948. The function strchr() being an alias of strstr() is declared using the PHP_FALIAS() macro in file ext/standard/basic_functions.c on line 2833.
And so on, and so forth.
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.
I'm using php 5.2.9 on a production server, and it seems that the exec() function behaves "non-standard".
If i run exec("ls", $output, $return_var) then $output will contain the list of files in the current folder as expected, but $return_var will be set to -1 instead of 0, as expected.
I'm using the $return_var to determine wherever the command finished successfully, and on every other server tested this works as expected:)
Anyone ever hit a situation like this?
edit:
<?php
$command = "asd";
$t1 = time();
$output = Array();
$result = -5;
$r = exec($command, $output, $result);
$t2 = time();
echo "<pre>";
var_export(Array(
'command'=>$command,
'result'=>$result,
'output'=>implode("\n", $output),
'r'=>$r,
't2-t1'=>$t2-$t1,
));
echo "</pre>";
Whatever command i put in $command, $result will always be -1, even for nonexistent commands...this is very weird
Assuming the system returning $result == -1 is Unix-like based (I don't know how would behave Windows with the same code)
The PHP (5.2.9) exec() function does not call the C exec() primitive (which returns -1 if it could not replace/execute the process, which is not the case here). Instead it calls popen() that creates a pipe, performs a fork() and execute a shell with your command.
The return_value, -1, is not the direct result from a C primitive, but rather is built by PHP internally, depending on the way your command was processed. In other terms, the "ls" command may have been well executed, while for instance PHP could not close properly the pipe.
Looking at the C code, in ext/standard/exec.c, there could be two reasons why the return code is -1, triggered by an error ; the 2nd one happens after the popen() call
fp = VCWD_POPEN(cmd_p, "r");
if (!fp) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to fork [%s]", cmd);
goto err;
}
// ...
err:
pclose_return = -1;
goto done;
However in this case, you wouldn't see the result, and the log would show an error.
Later, the return_value is set via the line
pclose_return = php_stream_close(stream);
Looking at _php_stream_free() (php_stream_close() is a macro replaced with _php_stream_free()), the most likely candidate that could return -1 is
ret = stream->ops->close(stream, preserve_handle ? 0 : 1 TSRMLS_CC);
Which in turn calls indirectly the C primitive pclose(). According to the manual
The pclose() function returns -1 if wait4(2) returns an error, or some other error is detected.
There seem to be an error detected during the closing of the pipe, that does not prevent the resulting data to be set. To find the reason rigorously, one need to check the operating system setup and logs, the PHP configuration and compilation parameters.
I would recommend
to apply patches for your OS, and maybe update to a more recent version (if applicable),
to update PHP to 5.3.3 (latest as of now) since the PHP exec() code changed significantly.
Be aware that there were changes related to the PHP suhosin module in the version 5.3 that enhance by default the security when running PHP files.
Make sure you're not running in safe mode and that exec isn't listed in disable_functions in php.ini.
Either of these situations would cause exec() to fail, though I think a notice would be raised.
Can we get the output of strace'ing the PHP process? That will likely contain the answer we're looking for.
Also 5.2.14 is the newest of the 5.2 series. Any chance you could try it there? If you're on a shared hosting provider, you can still likely get it to run locally to see if the behavior changes.
I tried it out on two different Linux PC's (PHP 5.03 and PHP 5.2.10) - both worked just fine.
PHP 5.2.10 example:
array (
'command' => 'ls',
'result' => 0,
'output' => 'atmail
...
vhosts',
'r' => 'vhosts',
't2-t1' => 0,
)
I'd double-check for any security-related directives in your php.ini file, check file permissions in the directory you're trying to search, and see if you have SELinux and/or AppArmor running.
You might also consider an altenrative, like opendir()/readdir ().
IMHO .. PSM
Seems like the problem got fixed by the server admin. I have no idea what he did, but it now works. The thing is that the server admin is pretty "strict" and maybe he got a little to restrictive with some system config. From the SSH shell for example i could not see where the php binaries were installed. I'm pretty sure that the SSH shell was chrooted, and also the webserver (either that or they were totally different servers, but i don't know how was that possible without using any kind of mount)...
Can anyone help me with the compilation of C++ code in PHP on the Windows platform? I am using Microsoft Visual C++ 6.0.
I have tried the following options which I knew, but none of them work:
system('test.c')
exec('test.c')
The file 'test.c' has been placed in my php/www/ folder.
I need to first compile the code, which makes test.exe and then using exec( ) I need to run the exe file.
Edit:
In this code "test.c", I am reading from a file "data.txt".
The contents of data.txt is changed every 5 seconds. That's why first I need to compile in PHP and then run the new exe file.
Do a .bat file that:
runs the vcvars32.bat file that comes with Visual Studio
runs "cl.exe yourprogram.c"
Then launch that .bat file from PHP.
(dunno how you would do that btw)
You need to call the compiler (cl.exe) and linker explicitly (See compiler reference) or use makefiles (g++ reference, but shows the point)
http://4answered.com/questions/view/7e1694/Compile-C-file-Using-PHP This helps me to compile and run C program (see video on this link). Use this I wrote this php code and it wokrs
<?php
putenv("PATH=C:\\MinGW\\bin");
exec("gcc C:\\test\\q.c -o C:\\test\\q1.exe");
system("C:\\test\\q1.exe",$output);
echo $output;
my q.c
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("Hello world");
return 0;
}
So you will see "Hello world0" in $output
I am assuming you want to invoke your compiler from a php script?
If that's the case, you'll first have to invoke the compiler itself - not just pass the source code's filename to system. In the case of MSVC++, you will want to look for "cl.exe".
Also, if the corresponding path isn't set in your environment, you will need to use the full path.
Make sure to first understand, how to invoke cl.exe manually, from a command prompt.
Once you understand that, you can try to invoke it from a php script.
However, you need to realize that there are additional factors to consider, such as for example the time required to compile a file vs. the time allowed for a php script to execute before timing out.
I think, you may need to provide us with additional information on what exactly you want to do, and why you want to do it.