Forking to background doesn't work!?

patrik

Verified User
Joined
Sep 6, 2006
Messages
128
I'm having some problems to fork a process to the background from a DA plugin I'm writing. It all comes down to the following:
I have a test.php script which contains:
PHP:
#!/usr/local/bin/php -c /usr/local/lib/php.ini
<?php
 echo system("nohup nice -n 20 /usr/bin/sa-learn --spam --dbpath /home/testuser1/.spamassassin/ \\ 
--mbox /var/mail/testuser1 >/dev/null &");
?>
If I run it in the terminal: php test.php, it does what I expect which is: forking the sa-learn process to the background and continues with the script (= ends it, forked process still running). But when I access it from DirectAdmin (http://testbed1.kontrollpanelen.se:2222/CMD_PLUGINS/salearn/test.php) it does NOT continue with the script, it waits for the process to end before it generates the page and sends it to the browser.
Why doesn't it work when accessing the script from DA!?

Thanks,
 
Last edited:

JTE

Verified User
Joined
Jun 20, 2006
Messages
88
An option is to fork the PHP process instead. If you want to fork the PHP process, recompile PHP with the PCNTL extension (--enable-pcntl in your configure line). Then you can call pcntl_fork() inside CLI scripts that will allow them to fork properly and run in the background. Then you should be able of invoking other commands from that PHP process without any issues.
Doing so will not affect the PHP scripts that are executed for HTTP requests; enabling PCNTL only gives extra functions in the command-line PHP binary, the Apache module remains unchanged.
I am currently using it on my own box without any problems.

For more information on using the PCNTL extension, go to php.net ;)
 

patrik

Verified User
Joined
Sep 6, 2006
Messages
128
Thanks for the answer!
I have read about pcntl_fork() but I don't really like the idea of having to recompile php. I want the plugin to run with as few prerequisites as possible.
 

patrik

Verified User
Joined
Sep 6, 2006
Messages
128
JTE,
Okay, I'm starting to think that pcntl_fork() is the only way to go. Just a question. In every example I find the parent process is waiting for the child to terminate, just to avoid getting zombie processes. But I don't want the parent process to wait? I want it to die, along with the script that generates the page so that it is delivered to the browser before the child process has finished. The stuff that the child does could take several hours in worst case I guess. How do I avoid zombie process but still have the parent to finish?
 

patrik

Verified User
Joined
Sep 6, 2006
Messages
128
I have tried the solution with double fork:

PHP:
$pid = pcntl_fork();
if(!$pid)
{ // Child
  $pid = pcntl_fork();
  if($pid)
    exit(); // Parent exists directly (double fork paradigm)
  else
  {
    // here's some code that actually does something, one thing is to start a new process with exec.
  }
}

pcntl_waitpid($pid, &$status, 0);
When I invoke this code by browsing to the site trough DA interface the process is actually started and the browser waits a few seconds (don't really know why), then the page is fully generated and sent to the browser and in the same time the background process dies, before it is actually done.

Note, if I run the same file directly from the terminal it does what I expect. The code finish and the process is still running in the background.
 

JTE

Verified User
Joined
Jun 20, 2006
Messages
88
Personally I never had any zombie process issues using the function. I tend to build in some sort of self-kill function when experimenting with a new script, that automatically kills the process if something goes wrong, and of course I let it create/delete pidfiles.
For a while I've been experimenting with using pcntl_fork() to create a basic daemon process in the background, and it never gave any trouble. In the beginning I just had it kill itself after a while and later on I always had the ability to send it a message over a socket to terminate it.
Besides, in my eyes there's not much point in forking if the parent process is then going to sit around doing nothing but waiting for its child to complete its task. In that case the parent process could have just done that task itself without any problems.

That the child process dies when calling it from DA might be a security feature in DA that kills any started processes, maybe to prevent a user from starting anything on the background, but I can't be sure since I never used the function in a plugin script.
 

patrik

Verified User
Joined
Sep 6, 2006
Messages
128
JTE [/i]Besides said:
That the child process dies when calling it from DA might be a security feature in DA that kills any started processes, maybe to prevent a user from starting anything on the background, but I can't be sure since I never used the function in a plugin script. [/B]
Hmm, that could explain it, too bad if that's actually the case.
 

DirectAdmin Support

Administrator
Staff member
Joined
Feb 27, 2003
Messages
9,051
Hello,

No "kill children" feature in DA..

but.. it will kill the parent after a timeout, but that's all.

If you want the child process to be independant and not defunct after the parent is killed, then set session leader setsid(); (c++ anyway) in the child.

If the parent is waiting around for the child to finish, then you don't need to do that.. but if DA kill's the parent due to a timeout, then the child will be left without a parent, thus giving you the defunct zombie process.

John
 

patrik

Verified User
Joined
Sep 6, 2006
Messages
128
Hmm,
From what I have read by googling on this, the latest code I pasted does give the child process to init. What I mean is that the init process adopts the child, thus it will not defunct.
I have also seen the setsid function in Perl and tried to use it but with no luck (the PHP script still waited for the child do finish).
Maybe I can invoke the perl script that uses the setsid function in the child process from the code above.
 

DirectAdmin Support

Administrator
Staff member
Joined
Feb 27, 2003
Messages
9,051
I've only done it in c/c++ so don't know if it's any different. Just on a side note, with setsid enabled, the child is now a parent and doesn't need to come home ;) .. the parent can basically abandon the "wait for child".. and exit before the child is even done. If you're waiting for a response from the child, then setsid wouldln't work for you in that case. It's best used with you're trying to create an independant process that will do its own thing, and not return any results to the parent.

John
 

patrik

Verified User
Joined
Sep 6, 2006
Messages
128
DirectAdmin Support said:
I've only done it in c/c++ so don't know if it's any different. Just on a side note, with setsid enabled, the child is now a parent and doesn't need to come home ;) .. the parent can basically abandon the "wait for child".. and exit before the child is even done. If you're waiting for a response from the child, then setsid wouldln't work for you in that case. It's best used with you're trying to create an independant process that will do its own thing, and not return any results to the parent.

John
Yepp, I know, and I'm not waiting for any result from the child. I will try setsid in perl later...
 

patrik

Verified User
Joined
Sep 6, 2006
Messages
128
This is getting ridiculous ;)

Now the code looks like this:
PHP:
$pid = pcntl_fork();
if(!$pid)
{ // Child
  $pid = pcntl_fork();
  if($pid)
    exit(); // Parent exists directly (double fork paradigm)
  else
  {
    exec("/usr/local/directadmin/plugins/salearn/user/learn_fork.pl");
  }
}

pcntl_waitpid($pid, &$status, 0);
And learn_fork.pl looks like this:
Code:
#!/usr/local/bin/perl

use POSIX qw(setsid);

chdir '/'                 or die "Can't chdir to /: $!";
open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!";
open STDERR, '>/dev/null' or die "Can't write to /dev/null: $!";
defined(my $pid = fork)   or die "Can't fork: $!";
exit if $pid;
setsid                    or die "Can't start a new session: $!";

exec("/usr/bin/sa-learn --spam --dbpath /home/testuser1/.spamassassin/ --mbox /var/mail/testuser1 >/dev/null");
But the child is killed when the PHP script ends.

DirectAdmin runs PHP in CLI right? I'm not sure how I should tackle this problem. Every attempt fails :/
 

DirectAdmin Support

Administrator
Staff member
Joined
Feb 27, 2003
Messages
9,051
Hello,

Try running it without directadmin at all.

cd /usr/local/directadmin/plugins/salearn/user
./index.html


if you need to read in an environmental variables, run it like this, (for example):

USERNAME=bob HOME=/home/bob ./index.html

And yes, php is compiled for CLI.

John
 

patrik

Verified User
Joined
Sep 6, 2006
Messages
128
Thanks for the answer,

This particular file is learn_fork.php and if I run it from the terminal the process is running in the background even after learn_fork.php has finished. It's when I run the script from DirectAdmin it fails.

CMD_PLUGINS/salearn/learn_fork.php

Ideas?
 
Last edited:
Top