Page 1 of 1

sockets and forking question

Posted: Thu Feb 26, 2004 5:42 pm
by itbegary
Hello,

I am working on a application that will run as a daemon. The first attempt at this was a product that worked but the problem is that each socket connection would run a process that might take 30 seconds. When this happened it would freeze up all of the other sockets. This was a single process of course.

We found some decent code on a multi-process php socket application that basically waits for a call then forks. We have it working to an extent. We spawn a connection each time someone calls. The problem is that I'm no expect on fork. I know that if 1000 people made a connection at them same time this thing would fork to death.

What I need is a way to be able to fork unless there are currently "N" active session. This would be a definable value of course. It would require me to keep track of how many active forks there are. But I don't know the best way to go about this.

Here is another odd tidbit. I have a section of code in the child used to shut the daemon down. When this function is called I close the active connection and issue a terminate signal to the parent process. By default the child will die because the connection is close (which is the control for that loop). The parent then dies. All other open children become their own parent. It seems that the signal_handler seems to work when it wants to. Is there an easy way to do this. Does someone have some a sample tcp daemon laying around that they would be willing to share.

We're not looking for something that is secure right now. Just something that is realiable.

One of the things this whole daemon is designed to do is accept XML packets and write them to a file or database. Sometimes these packets could be in the 2 MB range. We extract the XML data, do some parsing and then write the information to the file. The parsing is what takes the time. There are some other things this does as well.

Any help/code would be greatly appriciated. Mine is just a jumbled mess right now :D .

Re: sockets and forking question

Posted: Thu Feb 26, 2004 6:30 pm
by Weirdan
itbegary wrote: We found some decent code on a multi-process php socket application that basically waits for a call then forks. We have it working to an extent. We spawn a connection each time someone calls. The problem is that I'm no expert on fork. I know that if 1000 people made a connection at them same time this thing would fork to death.

What I need is a way to be able to fork unless there are currently "N" active session. This would be a definable value of course. It would require me to keep track of how many active forks there are. But I don't know the best way to go about this.

Here is another odd tidbit. I have a section of code in the child used to shut the daemon down. When this function is called I close the active connection and issue a terminate signal to the parent process. By default the child will die because the connection is close (which is the control for that loop). The parent then dies. All other open children become their own parent. It seems that the signal_handler seems to work when it wants to. Is there an easy way to do this. Does someone have some a sample tcp daemon laying around that they would be willing to share.
To shut daemon down you need to shut entire process group (if you keep track on which PIDs are assigned to children you can do this from parent easily). PID of the child process is returned to parent from pcntl_fork function, so you can store it somewhere. Then in parent signal handler kill all the children.

Posted: Fri Feb 27, 2004 2:09 pm
by itbegary
Okay, I'm with you...

So I've created an array that holds the child pid's. I've added the to the SIGCHLD section of the sig handler. I've also included a section in the SIGTERM section of the sig handler that will iterate through the array for the active elements and then it will do a posix_kill for each of the children.

In testing this works. 10 open connections and issuing a kill -TERM parentpid then all of the children die. This is GOOD.

But now I'm onto the next dilemma. The problem is when I kill the children the stop mid process. This wouldn’t be bad for what we are doing but the problem is that it leaves the connection closed but active. So if I restart the daemon it fails to bind to that socket.

What’s the best way to issue a graceful shutdown to the children?

I also notice that issuing a kill -9 parentid doesn’t kill any of the children. It seems that I need to accept the SIGKILL but php tells me that’s a bad signal handler

Posted: Fri Feb 27, 2004 5:33 pm
by itbegary
So I'll answer my own question (but I'm sure it's not necessarily right). The children aren't catching the SIGUSR1 for some odd reason. I added the signal after it has been forked. not sure if this is the proper way to do this but it seems to work (plus I changed a bunch of other code as well -- shame shame for not doing incremental testing).

Here is a code fragment to explain.

Code: Select all

function sig_handler($signo) {
  global $threads;
  global $sock;
  global $globalsock;
  
  print "................  SIGNO is ................     $signo\n";
  switch($signo) {
      case SIGTERM:
          // handle shutdown tasks
      print "I got the call to shutdown!\n";
      // Stop accepting new connections now.  Kill the socket and be done with it.
      socket_close($sock);
      // Do we have open children? Yes, Kill them now!
      for ($i = 0;$i<MAXTHREADS;$i++) &#123;
        //$threads&#1111;$i] = 0;
        if ($threads&#1111;$i] > 0) &#123;
          print "Killing thread &#1111;".$threads&#1111;$i]."]\n";
          posix_kill($threads&#1111;$i], SIGUSR1);
        &#125;
      &#125;
      print "I'm outta here!\n";
      exit;
      break;
    case SIGHUP:
             // handle restart tasks
             break;
    case SIGUSR1:
            print "Caught SIGUSR1...\n";
      socket_write($globalsock,"OK 200\r\n");
      socket_close($globalsock);
      unset($globalsock);
      exit();
       break;
        case SIGCHLD:
      print "I was passed SIGCHILD\n";
      $threadid = pcntl_waitpid(-1,$status,WNOHANG);
      for ($i = 0;$i<MAXTHREADS;$i++) &#123;
        //$threads&#1111;$i] = 0;
        if ($threads&#1111;$i] == $threadid) &#123;
          print "Setting state of $threadid to 0\n";
          $threadslot = $i;
          $threads&#1111;$i] = 0;
        &#125;
      &#125;
      print "Attemping to stop  thread &#1111;$threadid] in slot &#1111;$threadslot] of &#1111;".MAXTHREADS."]: ";
            while($threadid > 0) &#123; 
        print ".";
        $threadid = pcntl_waitpid(-1,$status,WNOHANG);
            &#125;
      print " &#1111;Stopped]\n";
            
      break;
        case SIGINT:
      exit;
      default:
      // not implemented yet...
      break;
     &#125;

&#125;
pcntl_signal(SIGHUP, "sig_handler");
pcntl_signal(SIGQUIT, "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler");
pcntl_signal(SIGUSR2, "sig_handler");
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGCHLD, "sig_handler");

...
<removed a bunch of guts>

while(!$quit) &#123;  
    $connection = @socket_accept($sock);
    if (!is_resource($connection)) &#123;
      //print "waiting\n";
      sleep(1);
    &#125;
    else &#123;
      // Get the number of children.  If count is < MAXTHREADS then we will
      // spawn a child.  Otherwise we will send a message to the child and kill
      // the connection.
      $threadslot = MAXTHREADS;
      for ($i = 0;$i<MAXTHREADS;$i++) &#123;
        //$threads&#1111;$i] = 0;
        if ($threads&#1111;$i] == 0) &#123;
          $threadslot = $i;
          break;
        &#125;
      &#125;
      if ($threadslot < MAXTHREADS) &#123;
        if (($child = pcntl_fork()) == -1 ) &#123;
          print  "Could not fork!!\n";
          print "Dying...\n";
          $quit++;
        &#125;
        elseif($child == 0) &#123;
          pcntl_signal(SIGUSR1, "sig_handler");        
          socket_close($sock);
          interact($connection, $properties);
          exit;
        &#125;
        else &#123;
          //  We are a parent so we need to keep track of where our children are.
          $threads&#1111;$threadslot] = $child;
          print "Created thread &#1111;$child] in slot &#1111;$threadslot] of &#1111;".MAXTHREADS."]\n";
        &#125;
      &#125;
      else &#123;
        // Issue a warning to the caller letting them know we are currently full.
      &#125;
      socket_close($connection);
    &#125;
  &#125;
Please note that this code is very rough... Just used for playing.

As you can see from the fragment above the code forks then defines the signal handler. If I don't do this the signal is never caught.

Am I on the right track with the way the I am handling the signals? I doesn't seem to be very well documented in the PHP side were these signals need to be declared and what their scope are.

Any additional feedback would be greatly appricated.

Posted: Sat Feb 28, 2004 11:25 am
by Weirdan
itbegary wrote: As you can see from the fragment above the code forks then defines the signal handler. If I don't do this the signal is never caught.
Strange enough. This is what the manual says:
man signal wrote: When a process which has installed signal handlers forks, the child pro-
cess inherits the signals. All caught signals may be reset to their
default action by a call to the execve(2) function; ignored signals
remain ignored.
.......
FreeBSD manual page.
itbegary wrote: Am I on the right track with the way the I am handling the signals? I doesn't seem to be very well documented in the PHP side were these signals need to be declared and what their scope are.
As I understand, sighandlers are installed in the context of process and inherited on fork. So if you install them in child they will not catch signals sent to parent process. On the other hand, if you install them in parent they should work in both the parent and child processes.

Nevertheless, you shouldn't rely on what php docs says regarding IPC. PHP doesn't implement this facility, it just provides the interface to your OS. Read mans instead.