Improving mod_perl Driven Site's Performance -- Part VI: Forking and Executing Subprocesses from mod_perl Page 6

By Stas Bekman (Send Email)
Posted Feb 27, 2001


This code example will allow you to verify that indeed the spawned child process has its own life, and its parent is free as well. Simply issue a request that will run this script, watch that the warnings are started to be written into the /tmp/log file and issue a complete server stop and start. If everything is correct, the server will successfully restart and the long term process will still be running. You will know that it's still running, if the warnings are still printed into the /tmp/log file. You may need to raise the number of warnings to do above 20, to make sure that you don't miss the end of the run.

If there are only 5 warnings to be printed, you should see the following output in this file:

  started
  1
  2
  3
  4
  5
  completed

Starting a Long Running External Program

But what happens if we cannot just run a Perl code from the spawned process and we have a compiled utility, i.e. a program written in C. Or we have a Perl program which cannot be easily converted into a module, and thus called as a function. Of course in this case we have to use system(), exec(), qx() or <''>(back ticks) to start it.

When using any of these methods and when the Taint mode is enabled, we must at least add the following code to untaint the PATH environment variable and delete a few other insecure environment variables. This information can be found in the perlsec manpage.


  {'PATH'} = '/bin:/usr/bin';
  delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

Now all we have to do is to reuse the code from the previous section.

First we move the core program into the external.pl file, add the shebang first line so the program will be executed by Perl, tell the program to run under Taint mode (-T) and possibly enable the warnings mode (-w) and make it executable:

  external.pl
  -----------
  #!/usr/bin/perl -Tw

  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, '>/tmp/log' or die "Can't write to /tmp/log: ";

  select STDERR;
  local $|=1;
  warn "started\n";
  # do something time-consuming
  sleep 1, warn "sh\n" for 1..20;
  warn "completed\n";

Now we replace the code that moved into the external program with exec() to call it:

  proper_fork_exec.pl
  -------------------
  use strict;
  use POSIX 'setsid';
  use Apache::SubProcess;

  {'PATH'} = '/bin:/usr/bin';
  delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

  my  = shift;
  ->send_http_header("text/html");

  {CHLD} = 'IGNORE';

  defined (my  = fork) or die "Cannot fork: \n";
  if () {
    print "Parent has finished, kid's PID: \n";
  } else {
      ->cleanup_for_exec(); # untie the socket
      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, '>&STDOUT'  or die "Can't dup stdout: ";
      setsid or die "Can't start a new session: ";

      exec "/home/httpd/perl/external.pl" or die "Cannot execute exec: ";
  }



Comment and Contribute

Your name/nickname

Your email

(Maximum characters: 1200). You have characters left.