Page 1 of 1

pcntl_fork(), MySQL, and exit()

Posted: Sun Jan 17, 2010 12:44 am
by looper89
I am working on a program that forks based on certain queries from a mysql database. The program forks and when the child is done or has a problem it exits using the exit() function. This seems simple enough except that when the first child to accomplish its tasks exits, the parents link to the database goes away.

If I remove the exit command and use a posix_kill() the program continues to operate, but I get zombies.

I guess I don't fully understand the exit() function. Most of what I was able to find dealt with MySQL connection issues after the fork(). Why would an exit() in a child kill the parents connection?

Thanks


System info:
5.2.6-3ubuntu4.4
Display Errors: On
Error Level: Not E_ALL
Register Globals: Off

Enough code is supplied to illustrate the problem. After the code is the screen output.

Code: Select all

 
#!/usr/bin/php -q
<?php
error_reporting(E_ALL);
/*
mysql db named test with one table named devices and the following:
U_ID    LOG_DATA    ACTIVE  LOGGING
1       1           1       0
*/
 
$hostname = "localhost";
$user = "logger";
$password = "manes-69";
$db = "test";
 
//open db
$link = mysql_connect($hostname,$user,$password);
mysql_select_db($db, $link);
 
echo "mysql ".$link."\n";
 
// setup PID array for parent/child
$pids = array();
$x=1;
 
while (log_enabled($link)){
 
    $result = mysql_query("SELECT COUNT(*) AS num FROM devices WHERE LOGGING = '1'", $link) or die();
    $row = mysql_fetch_assoc($result);
    $logging_count = $row['num'];
    echo "logging count ".$logging_count."\n";
 
    $result = mysql_query("SELECT COUNT(*) AS num FROM devices WHERE ACTIVE = '1'", $link) or die();
    $row = mysql_fetch_assoc($result);
    $active_count = $row['num'];
    echo "active count ".$active_count."\n";
 
    if ($active_count > $logging_count){  // if_1
        // get active devices
        $query = "select * from devices where ACTIVE = '1'";
        $devsql = mysql_query($query, $link);
 
        while ($device = mysql_fetch_assoc($devsql)){  //while_1
 
            $pid = pcntl_fork();
 
            if($pid == -1) {
                die("could not fork");
            }else if ($pid) {
                // we are in the parent
                $x++;
                echo "PID of child ".$pid."\n";
                $pids[] = $pid;
            }else {  // child ===============================
                echo "child ".$x." \n";
 
                sleep(15);
                
                
                exit();
            }  // end child  ===============================    
            
        }  
    }  
 
    sleep(4);
}
 
foreach($pids as $pid) {
    pcntl_waitpid($pid, $status);
}
mysql_close($link);
echo "done\n";
 
 
 
function log_enabled($_link){
    $sql = mysql_query("select * from devices where U_ID = '1'", $_link);
    $settings = mysql_fetch_assoc($sql) or die(mysql_error());
    if ($settings['LOG_DATA'] == 'n'){
    echo "inside log enable 0 b\n";
        return 0;
    }
    else{
    echo "inside log enable 1 c\n";
        return 1;
    }
}
 
?>
 
~/prog/php_socket/test$ ./main.php
mysql Resource id #4
inside log enable 1 c
logging count 0
active count 1
child 1
PID of child 24191
inside log enable 1 c
logging count 0
active count 1
child 2
PID of child 24193
inside log enable 1 c
logging count 0
active count 1
child 3
PID of child 24195
inside log enable 1 c
logging count 0
active count 1
child 4
PID of child 24197

Warning: mysql_fetch_assoc(): supplied argument is not a valid MySQL result resource in /home/jcarroll/prog/php_socket/test/main.php on line 78
MySQL server has gone away

Re: pcntl_fork(), MySQL, and exit()

Posted: Sun Jan 17, 2010 2:24 am
by requinix
I had two explanations for the behavior. Since I forgot the second one, here's the first:

(Speculation)
When PHP forks it does not make a copy of everything. Instead it does a kind of "lazy copy", where PHP makes a copy only when you try to modify something.

Code: Select all

$a = 1;
pcntl_fork();
// here, $a refers to the *same memory location* as it still does in the parent
// for the most part, you could create 10000 additional processes without using more memory
// (except for the process management stuff, of course)
$a = 2; // modify
// before the modify, PHP made a copy of the int(1) it had
// for the modify, the child changed the copy
Since you don't actually modify the resource both the parent and children processes use the same database connection. When you close it (by exiting the child) it's closed for everybody.


Solution:
Your design is a bit off. You open the connection in the parent but close it in the child? Two choices:
A) I think a persistent connection (with mysql_pconnect) might fix it - just don't mysql_close until all children are gone
B) Open a new connection in each child and close them in each child

Have you done any testing to see what benefit you get from multiple processes? I wouldn't think there'd be much of an improvement.

Re: pcntl_fork(), MySQL, and exit()

Posted: Sun Jan 17, 2010 9:58 am
by looper89
Thanks for the reply.

I think I didn't quite explain things.

The code you see, when executed, illustrates the problem. There is a lot more code but, I didn't think it was necessary to post.

I wrote a program that looks at a db and from the result forks off several children. The main part (parent) periodically checks in with the db, looks for changes and forks off more children if needed. This part of the program, the parent, needs a db connection to keep doing what it needs to do.

The child, once it gets forked, creates it's own connection with this command:
mysql_connect($hostname, $user, $password, true); to get a unique connection.
The child does it business, then closes the resource which is unique to it. No problems yet.

The child needs to exit. All the examples I see say to use the exit() function, so I do it. For some reason the when the child exits using exit(), the parents db connection closes. None of the other children are affected.

Re: pcntl_fork(), MySQL, and exit()

Posted: Mon Jan 18, 2010 7:30 am
by Weirdan
DB connection is a socket, and socket is a file handle in the unixland. When program forks, child inherits all the file handles parent had open at the time of fork. If child wants to use the file handles it should either coordinate its usage with parent and other children, or close and reopen the file handle (to get its own independent copy). When php exits it would close all the file handles it has open, so your first-to-die child closes the connection.

In other words you need to close/reopen db connection in the child once it's forked.

Re: pcntl_fork(), MySQL, and exit()

Posted: Mon Jan 18, 2010 8:36 am
by looper89
I currently am using, in the children, mysql_connect($hostname, $user, $password, true); to get a new connection. I think the solution must be to close the parents db connection before the fork and open a new one after the fork.

The interesting part to me was that it wasn't the mysql close() that was causing the grief it was exit(). It took a long time to realize that.

Thanks again.

Re: pcntl_fork(), MySQL, and exit()

Posted: Thu Mar 04, 2010 12:44 pm
by fwm
> The interesting part to me was that it wasn't the mysql close() that was causing the grief it was exit(). It took a long time to realize that.

Hopefully, an experiment showed that mysql_close() in the beginning of the child branch does cause connection failure in the parent :)
Because I've almost broken my brain trying to understand why it doesn't.

But I still have a vague feeling like I've missed something. Really, why should it not cause?

Re: pcntl_fork(), MySQL, and exit()

Posted: Thu Mar 04, 2010 2:57 pm
by VladSun
looper89 wrote:I currently am using, in the children, mysql_connect($hostname, $user, $password, true); to get a new connection. I think the solution must be to close the parents db connection before the fork and open a new one after the fork.

The interesting part to me was that it wasn't the mysql close() that was causing the grief it was exit(). It took a long time to realize that.

Thanks again.
Description

mysql_close() closes a MySQL connection opened by mysql_connect() . The connection to close is specified with the connection argument. If no argument is specified, the last opened connection is closed.

Use of this function is not required. Connections opened by mysql_connect() close automatically on script exit.

Re: pcntl_fork(), MySQL, and exit()

Posted: Thu Mar 04, 2010 3:01 pm
by VladSun
http://php.net/manual/en/function.mysql-connect.php
resource mysql_connect ([ string $server = ini_get("mysql.default_host") [, string $username = ini_get("mysql.default_user") [, string $password = ini_get("mysql.default_password") [, bool $new_link = false [, int $client_flags = 0 ]]]]] )
...


[-] Parameters
....
new_link

If a second call is made to mysql_connect() with the same arguments, no new link will be established, but instead, the link identifier of the already opened link will be returned. The new_link parameter modifies this behavior and makes mysql_connect() always open a new link, even if mysql_connect() was called before with the same parameters. In SQL safe mode, this parameter is ignored.

Re: pcntl_fork(), MySQL, and exit()

Posted: Thu Mar 04, 2010 3:34 pm
by VladSun
So, you need to open a new mysql connection in the "parent block" after forking.
I think, a do{}while($row = mysql_fetch...) loop is more appropriate here