php forking child process to execute infinte loops - php

I'm relatively new to php, but I can program in other languages (js, java, c/c++). I have a problem and I can't seem to be able to solve, so I'm hoping that someone can help me out here :)
I created a server_class.php file which manages connections between multiple clients. The clients connect to the php server via the web. When I launch the server_class.php it executes two applications (they are in an endless loop) and print data to the terminal. When a client connects to the php server, I want the server to start sending the output of each application to each client, so the clients can see the current output of each application. I have partially achieved this. However, it only sends the output of one application and not the other.
The function below is executed when the connection between the server and the client is performed:
private function startProc($client) {
$this->output("Start a client process");
$pid = pcntl_fork();
if($pid == -1) {
$this->output("fork terminated!");
}
elseif($pid) { // process
$client->setPid($pid);
}
else {
$this->output("Starting app1 data pipe...");
exit($this->launchAppOneProc($client));
$this->output("Starting app2 data pipe...");
exit($this->launchAppTwoProc($client));
}
}
Ok, once the connection between the client and the server is done, this function is executed. As you can see, I create a new process which then executes two methods: launchAppOneProc and launchAppTwoProc. These two functions contain the following code:
private function launchAppOneProc($client) {
while (# ob_end_flush()); // end all output buffers if any
while (!feof($this->appone_proc))
{
$appone_text = fread($this->appone_proc, 4096);
$this->send($client, $appone_text);
flush();
}
$this->output("AppOne has stopped running!");
}
The function above is the same as for launchAppTwoProc(). The function $this->output prints the text specified into the terminal of the server_class.php
So the problem is that it only executes launchAppOneProc() function and does not execute the next function launchAppTwoProc().
Any ideas on how I can execute both functions?
Thank you
David

Ok, I found a solution to my problem. To clear some confusion, I have two applications that are running in the background and writing to a buffer. When a client connects to the server, they connect to both buffers and start receiving data. So what I did was to create a new process for each channel. When the client connects to the server, two processes are created and assigned to the client. Each process is connected to a buffer and starts sending data to the client.
private function startProc($client) {
$this->output("Start a client process");
for ($i = 0; $i < 2; ++$i) {
// Create new process.
$pid = pcntl_fork();
switch ($pid) {
case -1: // Failed to create new process.
$this->output("Failed to create new process!");
break;
case 0: // Child process created, execute code...
switch ($i) {
case 0:
$this->output("Connect to AppOne data pipe...");
$this->launchAppOneProc($client);
break;
case 1:
$this->output("Connect to AppTwo data pipe...");
$this->launchAppTwoProc($client);
break;
}
default: // Back to parent.
switch ($i) {
case 0:
$client->setAppOnePid($pid);
break;
case 1:
$client->setAppTwoPid($pid);
break;
}
}
}
}
I know I can use an array to store the process IDs, etc. But for now, this works. Once the client disconnects, each process for the client that just left has to be terminated as such:
posix_kill($client->getAppOnePid(), SIGTERM);
posix_kill($client->getAppTwoPid(), SIGTERM);
I hope this make sense and helps anyone who runs into the same problem.

Related

Request to launch background php script

I'm currently working on an internal website displaying a lot of statistics, and some pages or ajax scripts are extremely slow due to large datas.
What I'm searching is a way to launch theses scripts in background with a request, and then launch ajax requests to know the progress of the background script.
Is there any way to achieve this? I work with php7.0 and an apache2 server (I don't have direct access to apache server configuration, so if it's possible, I search for a client-side option)
If ever someone else is searching for a way to achieve this, here is the solution I found:
I call in Ajax a script, it forks itself and save the PID of the child process in the database.
Then I call session_write_close() in the child process to allow user making new requests, and the father process exits (not waiting for child end).
As the father exits, the user receive an answer to his request, and the child process continue his job.
Then in Ajax I call another script to get the evolution of the worker, and finally I get the result and kill the child process when everything is done.
Here is the code of my worker class:
class AsyncWorker
{
private $pid;
private $worker;
private $wMgr;
public function __construct($action, $content, $params = NULL)
{
$this->wMgr = new WorkersManager();
$pid = pcntl_fork(); // Process Fork
if ($pid < 0) {
Ajax::Response(AJX_ERR, "Impossible de fork le processus");
} else if ($pid == 0) { // In the child, we start the job and save the worker properties
sleep(1);
$this->pid = getmypid();
$this->worker = $this->wMgr->fetchBy(array("pid" => $this->pid));
if (!$this->worker) {
$this->worker = $this->wMgr->getEmptyObject();
$this->wMgr->create($this->worker);
}
$this->worker->setPid($this->pid);
$this->worker->setAction($action);
$this->worker->setContent($content);
$this->worker->setPercent(0.00);
$this->worker->setResult("");
$this->wMgr->update($this->worker);
$this->launch($params);
} else { // In the father, we save the pid to DB and answer the request.
$this->worker = $this->wMgr->fetchBy(array("pid" => $this->pid));
if (!$this->worker) {
$this->worker = $this->wMgr->getEmptyObject();
$this->worker->setPid($pid);
$this->wMgr->create($this->worker);
}
Ajax::Response(AJX_OK, "Worker started", $this->worker->getId());
}
}
// Worker job
private function launch($params = NULL)
{
global $form, $_PHPPATH, $url, $session;
session_write_close(); // This is useful to let the user make new requests
ob_start(); // Avoid writing anything
/*
** Some stuff specific to my app (include the worker files, etc..)
*/
$result = ob_get_contents(); // Get the wrote things and save them to DB as result
$this->worker->setResult($result);
$this->worker->setPercent(100);
ob_end_clean();
}
}
It's a bit tricky but I had no choices, as I have no access to server plugins and libraries.
you can make php script to execute shell bash script , or using exec() method for that

How do you delete or edit a file in PHP when it is being read?

Supposing that there is a file on PHP. The file is constantly being read.
I want to stop users from accessing the file first, then delete or edit the file.
How can I do this?
Please refer to this answer.
file locking in php
That covers the locking part. However, to access the file you need to do a loop until the lock is released. Here is a sample algorithm.
define(MAX_SLEEP, 3); // Decide a good value for number of tries
$sleep = 0; // Initialize value, always a good habit from C :)
$done = false; // Sentinel value
$flock = new Flock; // You need to implement this class
do {
if (! $flock->locked()) { // We have a green light
$flock->lock(); // Lock right away
//DO STUFF;
$flock->unlock(); // Release the lock so others can access
$done = true; // Allows the loop to exit
} else if ($sleep++ > MAX_SLEEP) { // Giving up, cannot write
// Handle exception, there are many possibilities:
// Log exception and do nothing (definitely log)
// Force a write
// See if another process has been running for too long
// Check for timestamp of the lock file, maybe left behind after a reboot
} else {
sleep(SLEEP_TIME);
}
} while(! $done);

PHP CodeIgniter: How to run processes in the background without getting my whole website stuck

I have a website on an Ubuntu LAMP Server - that has a form which gets variables and then they get submitted to a function that handles them. The function calls other functions in the controller that "explodes" the variables, order them in an array and run a "for" loop on each variable, gets new data from slow APIs, and inserts the new data to the relevant tables in the database.
Whenever I submit a form, the whole website gets stuck (only for my IP, on other desktops the website continue working regularly), and I get redirected until I get to the requested "redirect("new/url);".
I have been researching this issue for a while and found this post as an example:
Continue PHP execution after sending HTTP response
After studding how this works in the server side, which is explained really good in this video: https://www.youtube.com/watch?v=xVSPv-9x3gk
I wanted to start learning how to write it's syntax and found out that this only work on CLI and not from APACHE, but I wasn't sure.
I opened this post a few days ago: PHP+fork(): How to run a fork in a PHP code
and after getting everything working from the server side, installing fork and figuring out the differences of the php.ini files in a server (I edited the apache2 php.ini, don't get mistaked), I stopped getting the errors I used to get for the "fork", but the processes don't run in the background, and I didn't get redirected.
This is the controller after adding fork:
<?php
// Registers a new keyword for prod to the DB.
public function add_keyword() {
$keyword_p = $this->input->post('key_word');
$prod = $this->input->post('prod_name');
$prod = $this->kas_model->search_prod_name($prod);
$prod = $prod[0]->prod_id;
$country = $this->input->post('key_country');
$keyword = explode(", ", $keyword_p);
var_dump($keyword);
$keyword_count = count($keyword);
echo "the keyword count: $keyword_count";
for ($i=0; $i < $keyword_count ; $i++) {
// create your next fork
$pid = pcntl_fork();
if(!$pid){
//*** get new vars from $keyword_count
//*** run API functions to get new data_arrays
//*** inserts new data for each $keyword_count to the DB
print "In child $i\n";
exit($i);
// end child
}
}
// we are the parent (main), check child's (optional)
while(pcntl_waitpid(0, $status) != -1){
$status = pcntl_wexitstatus($status);
echo "Child $status completed\n";
}
// your other main code: Redirect to main page.
redirect('banana/kas');
}
?>
And this is the controller without the fork:
// Registers a new keyword for prod to the DB.
public function add_keyword() {
$keyword_p = $this->input->post('key_word');
$prod = $this->input->post('prod_name');
$prod = $this->kas_model->search_prod_name($prod);
$prod = $prod[0]->prod_id;
$country = $this->input->post('key_country');
$keyword = explode(", ", $keyword_p);
var_dump($keyword);
$keyword_count = count($keyword);
echo "the keyword count: $keyword_count";
// problematic part that needs forking
for ($i=0; $i < $keyword_count ; $i++) {
// get new vars from $keyword_count
// run API functions to get new data_arrays
// inserts new data for each $keyword_count to the DB
}
// Redirect to main page.
redirect('banana/kas');
}
The for ($i=0; $i < $keyword_count ; $i++) { is the part that I want to get running in the background because it's taking too much time.
So now:
How can I get this working the way I explained? Because from what I see, fork isn't what I'm looking for, or I might be doing this wrong.
I will be happy to learn new techniques, so I will be happy to get suggestions about how I can do this in different ways. I am a self learner, and I found out the great advantages of Node.js for exmaple, which could have worked perfectly in this case if I would have learnt it. I will consider to learn working with Node.js in the future. sending server requests and getting back responses is awesome ;).
***** If there is a need to add more information about something, please tell me in comments and I will add more information to my post if you think it's relevant and I missed it.
What you're really after is a queue or a job system. There's one script running all the time, waiting for something to do. Once your original PHP script runs, it just adds a job to the list, and it can continue it's process as normal.
There's a few implementations of this - take a look at something like https://laravel.com/docs/5.1/queues

PHP CLI continue checking MYSQL database every 2 seconds until user decided to quit

I don't very much find too much information over the internet about PHP CLI so I am having a hard time figuring out how to finish my code.
Basically, the application should continue checking the MYSQL database every 2 seconds without exiting, unless otherwise the user entered the letter 'q'.
I started it out by just printing the word 'pro' continuously before I implement MYSQL so my code looked like this:
<?php
fwrite(STDOUT, "This should print word 'pro' continuously\n");
fwrite(STDOUT, "\tto exit, simply press 'q' and enter\n");
do {
fwrite(STDOUT, "pro\n");
}while (fgetc(STDIN) != 'q');
?>
Pretty much when the user entered 'q', the app terminates, but the problem is it only prints out 'pro' once, and when I pressed enter.
fgetc() will block until there is data to read - in other words, when the script reaches the fgetc() call, the execution will halt until the user inputs something.
In order to work around this, you will need to check whether there is any data to read using stream_select(). You can also use stream_select() to limit the MySQL poll to every 2 seconds. A basic framework would look something like this:
<?php
// Do all your init work here, connect to DB etc
$tickerSecs = 2;
echo "Hello! I've started\n";
do {
// Do actual work here
echo "I'm polling the database\n";
// See stream_select() docs for an explanation of why this is necessary
$r = array(STDIN);
$w = $e = NULL;
if (stream_select($r, $w, $e, $tickerSecs) > 0) {
// The user input something
echo "You input something\n";
$char = fread(STDIN, 1);
if ($char == 'q') {
// The user pressed 'q'
echo "You told me to quit\n";
break;
} else {
echo "I don't understand '$char'\n";
}
}
} while (TRUE); // Loop forever
// Do shutdown work here
echo "I'm shutting down\n";
Note that it is likely that you will have to require your user to press q + enter rather than just q because of the nature of the way these things work - and I don't really understand why this is, maybe someone else can provide the missing piece here?
Rather than stopping when Q is pressed, you could use pcntl_signal() to register a handler for SIGQUIT (ie Ctrl-C)

socket_select() with 2 dimensional array

I'm trying to make my PHP server a bit more efficient.
I've built an object named Client which contains the connected client (which has an open socket connection with the server) information such as name, id etc.
For now I have one array of socket connections, and one array of Client objects.
When I'm referring a connection, I'm searching inside my Client array to find the right client who matches this connection.
It works great, but it's a bit inefficient.. For small amount of clients in the server you don't feel it, but I'm afraid that if I'll have thousands of connection it will slow down the server.
As a solution I thought about 2 dimensional array, but I have a logic problem designing it.
Can I do something like this:
$clients = array();
$temp = array($newsock, new Client());
$clients[] = $temp;
I want my $clients[] to be the socket and the $clients[][] to be the client object.
In each row of $client I will have only $client[$index][0] which will be my client object for that connection.
Will I be able to send this to the socket_select() function?
You say that you have within your client object an id attribute. Why not use that id as the key for both arrays?
Socket connections array
Client object array
You might even be able to hold the connection and the client object in one array, each in one object under the same key I talked about before - the clients id.
In any case, wherever you decide to store your clients connection object, you will be able to pass it to all the relevant socket functions -
socket_select();
socket_accept();
socket_write();
etc...
With regard to the efficiency of your server, I implemented some forking for broadcasting data to large amounts of clients (all of them in the example of a chat server).
This is the implementation that I used for forking the broadcasts -
function broadcastData($socketArray, $data){
global $db;
$pid = pcntl_fork();
if($pid == -1) {
// Something went wrong (handle errors here)
// Log error, email the admin, pull emergency stop, etc...
echo "Could not fork()!!";
} elseif($pid == 0) {
// This part is only executed in the child
foreach($socketArray AS $socket) {
// There's more happening here but the essence is this
socket_write($socket,$msg,strlen($msg));
// TODO : Consider additional forking here for each client.
}
// This is where the signal is fired
exit(0);
}
// The child process is now occupying the same database
// connection as its parent (in my case mysql). We have to
// reinitialize the parent's DB connection in order to continue using it.
$db = dbEngine::factory(_dbEngine);
}
The code above was lifted from a previous question of mine (that was self answered).
Terminating zombie child processes forked from socket server
Perhaps it might assist you if you chose to start forking processes.

Categories