Annotation of loncom/lonmaxima, revision 1.25

1.1       www         1: #!/usr/bin/perl
                      2: #
                      3: # The LearningOnline Network with CAPA
                      4: # Connect to MAXIMA CAS
                      5: #
1.25    ! raeburn     6: # $Id: lonmaxima,v 1.24 2007/02/08 06:28:30 raeburn Exp $
1.1       www         7: #
                      8: # Copyright Michigan State University Board of Trustees
                      9: #
                     10: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                     11: #
                     12: # LON-CAPA is free software; you can redistribute it and/or modify
                     13: # it under the terms of the GNU General Public License as published by
                     14: # the Free Software Foundation; either version 2 of the License, or
                     15: # (at your option) any later version.
                     16: #
                     17: # LON-CAPA is distributed in the hope that it will be useful,
                     18: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     19: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     20: # GNU General Public License for more details.
                     21: #
                     22: # You should have received a copy of the GNU General Public License
                     23: # along with LON-CAPA; if not, write to the Free Software
                     24: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                     25: #
                     26: # /home/httpd/html/adm/gpl.txt
                     27: #
                     28: 
                     29: # 
                     30: # http://www.lon-capa.org/
                     31: #
1.2       www        32: 
1.16      www        33: use Expect; 
1.1       www        34: use IO::Select;
1.2       www        35: use IO::Socket;
                     36: use IO::File;
                     37: use Symbol;
                     38: use POSIX;
                     39: use lib '/home/httpd/lib/perl/';
                     40: use LONCAPA::Configuration;
                     41:  
1.3       albertel   42: use strict;
                     43: 
                     44: # global variables
                     45: my $PREFORK                = 5;        # number of children to maintain
1.17      www        46: my $MAX_CLIENTS_PER_CHILD  = 50;       # number of clients each child should process
1.3       albertel   47: my %children               = ();       # keys are current child process IDs
                     48: my $children               = 0;        # current number of children
                     49: my $status;                            # string for current status
1.5       albertel   50: my $pidfile;                           # file containg parent process pid
                     51: my $port;                              # path to UNIX socket file
                     52: my %perlvar;                           # configuration file info
                     53: my $lastlog;                           # last string that was logged
1.18      www        54: 
1.16      www        55: use vars qw($PREFORK $MAX_CLIENTS_PER_CHILD %children $children $status
1.12      www        56: 	    $pidfile $port %perlvar $lastlog);
1.2       www        57:  
                     58: # ------------------------------------------------------------ Service routines 
                     59: sub REAPER {                        # takes care of dead children 
                     60:                                     # and MAXIMA processes
                     61:     $SIG{CHLD} = \&REAPER;
                     62:     my $pid = wait;
1.6       albertel   63:     $children--;
                     64:     delete($children{$pid});
1.2       www        65: }
                     66:  
                     67: sub HUNTSMAN {                      # signal handler for SIGINT
                     68:     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
1.6       albertel   69:     kill('INT' => keys(%children));
1.2       www        70:     unlink($pidfile);
                     71:     unlink($port);
                     72:     &logthis('---- Shutdown ----');
                     73:     exit;                           # clean up with dignity
                     74: }
                     75: 
                     76: 
                     77:  
                     78: # --------------------------------------------------------------------- Logging
                     79:  
                     80: sub logthis {
1.4       albertel   81:     my ($message)=@_;
1.2       www        82:     my $execdir=$perlvar{'lonDaemons'};
                     83:     my $fh=IO::File->new(">>$execdir/logs/lonmaxima.log");
                     84:     my $now=time;
                     85:     my $local=localtime($now);
                     86:     $lastlog=$local.': '.$message;
                     87:     print $fh "$local ($$): $message\n";
                     88: }
                     89:  
                     90: # -------------------------------------------------------------- Status setting
                     91:  
                     92: sub status {
1.4       albertel   93:     my ($what)=@_;
1.2       www        94:     my $now=time;
                     95:     my $local=localtime($now);
                     96:     $status=$local.': '.$what;
                     97:     $0='lonmaxima: '.$what.' '.$local;
                     98: }
                     99:  
                    100: # -------------------------------------------------------- Escape Special Chars
                    101:  
                    102: sub escape {
1.4       albertel  103:     my ($str)=@_;
1.2       www       104:     $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
                    105:     return $str;
                    106: }
                    107:  
                    108: # ----------------------------------------------------- Un-Escape Special Chars
                    109:  
                    110: sub unescape {
1.4       albertel  111:     my ($str)=@_;
1.2       www       112:     $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
                    113:     return $str;
                    114: }
                    115:  
                    116: # ------------------------ grabs exception and records it to log before exiting
                    117: sub catchexception {
                    118:     my ($signal)=@_;
                    119:     $SIG{QUIT}='DEFAULT';
                    120:     $SIG{__DIE__}='DEFAULT';
                    121:     chomp($signal);
1.5       albertel  122:     &logthis("<font color=\"red\">CRITICAL: "
                    123: 	     ."ABNORMAL EXIT. Child $$ died through "
                    124: 	     ."\"$signal\"</font>");
1.2       www       125:     die("Signal abend");
                    126: }
1.5       albertel  127: 
1.16      www       128: 
                    129: 
1.2       www       130: # ---------------------------------------------------------------- Main program
                    131: # -------------------------------- Set signal handlers to record abnormal exits
                    132:  
                    133:  
                    134: $SIG{'QUIT'}=\&catchexception;
                    135: $SIG{__DIE__}=\&catchexception;
                    136:  
                    137: # ---------------------------------- Read loncapa_apache.conf and loncapa.conf
                    138: &status("Read loncapa.conf and loncapa_apache.conf");
1.3       albertel  139: %perlvar=%{&LONCAPA::Configuration::read_conf('loncapa.conf')};
1.2       www       140:  
                    141: # ----------------------------- Make sure this process is running from user=www
                    142: my $wwwid=getpwnam('www');
                    143: if ($wwwid!=$<) {
1.5       albertel  144:     my $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
                    145:     my $subj="LON: User ID mismatch";
                    146:     system("echo 'User ID mismatch.  lonmaxima must be run as user www.' |\
1.2       www       147:  mailto $emailto -s '$subj' > /dev/null");
1.5       albertel  148:     exit 1;
1.2       www       149: }
                    150:  
                    151: # --------------------------------------------- Check if other instance running
                    152:  
                    153: $pidfile="$perlvar{'lonDaemons'}/logs/lonmaxima.pid";
                    154:  
                    155: if (-e $pidfile) {
1.5       albertel  156:     my $lfh=IO::File->new("$pidfile");
                    157:     my $pide=<$lfh>;
                    158:     chomp($pide);
1.6       albertel  159:     if (kill(0 => $pide)) { die "already running"; }
1.2       www       160: }
1.5       albertel  161: 
1.2       www       162: # ------------------------------------------------------- Listen to UNIX socket
                    163: &status("Opening socket");
                    164:  
                    165: $port = "$perlvar{'lonSockDir'}/maximasock";
                    166:  
                    167: unlink($port);
                    168:  
                    169: 
1.6       albertel  170: my $server = IO::Socket::UNIX->new(Local  => $port,
                    171: 				   Type   => SOCK_STREAM,
                    172: 				   Listen => 10 );
                    173: if (!$server) {
                    174:     my $st=120+int(rand(240));
                    175: 
                    176:     &logthis("<font color=blue>WARNING: ".
                    177: 	     "Can't make server socket ($st secs):  .. exiting</font>");
                    178: 
                    179:     sleep($st);
                    180:     exit;
                    181: }
1.2       www       182:     
                    183:  
                    184: # ---------------------------------------------------- Fork once and dissociate
                    185:  
                    186: my $fpid=fork;
                    187: exit if $fpid;
1.6       albertel  188: die("Couldn't fork: $!") unless defined($fpid);
1.2       www       189:  
                    190: POSIX::setsid() or die "Can't start new session: $!";
                    191:  
                    192: # ------------------------------------------------------- Write our PID on disk
                    193:  
                    194: my $execdir=$perlvar{'lonDaemons'};
1.5       albertel  195: open(PIDSAVE,">$execdir/logs/lonmaxima.pid");
1.2       www       196: print PIDSAVE "$$\n";
                    197: close(PIDSAVE);
                    198: &logthis("<font color='red'>CRITICAL: ---------- Starting ----------</font>");
                    199: &status('Starting');
1.6       albertel  200:      
1.2       www       201: 
1.10      albertel  202: # Install signal handlers.
                    203: $SIG{CHLD} = \&REAPER;
                    204: $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
1.16      www       205:  
1.2       www       206: # Fork off our children.
                    207: for (1 .. $PREFORK) {
1.16      www       208:     &make_new_child($server);
1.2       www       209: }
                    210:  
                    211: # And maintain the population.
                    212: while (1) {
                    213:     &status('Parent process, sleeping');
                    214:     sleep;                          # wait for a signal (i.e., child's death)
1.3       albertel  215:     for (my $i = $children; $i < $PREFORK; $i++) {
1.2       www       216:         &status('Parent process, starting child');
1.16      www       217:         &make_new_child($server);           # top up the child pool
1.2       www       218:     }
                    219: }
                    220:                                                                                 
                    221: sub make_new_child {
1.16      www       222:     my ($server) = @_;
1.4       albertel  223: 
1.2       www       224:     # block signal for fork
1.4       albertel  225:     my $sigset = POSIX::SigSet->new(SIGINT);
1.2       www       226:     sigprocmask(SIG_BLOCK, $sigset)
1.6       albertel  227:         or die("Can't block SIGINT for fork: $!\n");
1.2       www       228:      
1.6       albertel  229:     die("fork: $!") unless defined(my $pid = fork);
1.2       www       230:      
                    231:     if ($pid) {
                    232:         # Parent records the child's birth and returns.
                    233:         sigprocmask(SIG_UNBLOCK, $sigset)
1.6       albertel  234:             or die("Can't unblock SIGINT for fork: $!\n");
1.16      www       235:         $children{$pid} = 1;
1.2       www       236:         $children++;
                    237:         return;
                    238:     } else {
                    239:         # Child can *not* return from this subroutine.
                    240:         $SIG{INT} = 'DEFAULT';      # make SIGINT kill us as it did before
                    241:      
                    242:         # unblock signals
                    243:         sigprocmask(SIG_UNBLOCK, $sigset)
1.6       albertel  244:             or die("Can't unblock SIGINT for fork: $!\n");
1.15      www       245: 
1.17      www       246:         &logthis('New process started');
                    247: 
1.16      www       248:         my $command=Expect->spawn('maxima');
                    249:         $command->log_stdout(0);
                    250: 
                    251:         &getmaximaoutput($command);
                    252: 
1.13      www       253:         for (my $i=0; $i < $MAX_CLIENTS_PER_CHILD; $i++) {
1.16      www       254:            &status('Accepting connections');
                    255:            my $client = $server->accept()     or last;
1.22      www       256:            print $command "kill(all);\n";
1.20      www       257: 	   &getmaximaoutput($command);
1.19      www       258:            &sync($command);
1.16      www       259:            while (my $cmd=<$client>) {
                    260:               &status('Processing command');
1.17      www       261:               print $command &unescape($cmd);
1.25    ! raeburn   262:               my ($reply,$finished,$syntaxerr) = &getmaximaoutput($command,1);
1.17      www       263:               print $client &escape($reply)."\n";
                    264:               if ($reply=~/^Error\:/) {
                    265:                  &logthis('Died through '.$reply);
                    266:                  $client->close();
                    267:                  $command->hard_close();     
                    268:                  exit;
1.19      www       269:               }	      
                    270: 	      &sync($command);
1.17      www       271:               &status('Waiting for commands');
1.16      www       272:            }
1.13      www       273:         }
1.4       albertel  274: 
1.12      www       275:         # tidy up gracefully and finish
1.4       albertel  276: 
1.16      www       277:         $command->soft_close();
1.1       www       278: 
1.2       www       279:         # this exit is VERY important, otherwise the child will become
                    280:         # a producer of more and more children, forking yourself into
                    281:         # process death.
                    282:         exit;
                    283:     }
                    284: }
1.4       albertel  285: 
1.18      www       286: {
                    287:     my $counter;
                    288:     sub sync {
                    289: 	my ($command)=@_;
                    290: 	$counter++;
                    291: 	my $expect=$counter.time;
                    292: 	print $command "$expect;\n";
                    293: 	while (1) {
1.25    ! raeburn   294: 	    my $output=&getmaximaoutput($command,1);
1.18      www       295: 	    if (($output=~/\Q$expect\E/) || ($output=~/^Error\:/)) {
                    296: 		return;
                    297: 	    }
                    298: 	}
                    299:     }
                    300: }
                    301: 
1.16      www       302: sub getmaximaoutput {
1.25    ! raeburn   303:     my ($command,$numcheck)=@_;
        !           304:     my $regexp = '\(\%i\d+\)';
        !           305:     if ($numcheck) {
        !           306:         if ($command->match() =~ /\(\%i(\d+)\)/) {
        !           307:             my $nextmatch = $1+1;
        !           308:             $regexp = '\(\%i'.$nextmatch.'\)';
        !           309:         }
        !           310:     }
        !           311:     my (undef,$error,undef,$output)=$command->expect(20, -re => $regexp);
1.17      www       312:     if ($error) {
                    313:        return 'Error: '.$error;
                    314:     }
1.23      raeburn   315:     $output =~ s/\r+//g; # Remove Windows-style linebreaks
                    316:     my $hasoutput=0;
1.16      www       317:     my $foundoutput=0;
1.23      raeburn   318:     my $syntaxerr=0;
1.16      www       319:     my $realoutput='';
                    320:     foreach my $line (split(/\n/,$output)) {
                    321:        if ($line=~/\;/) { $foundoutput=1; next; }
                    322:        if (!$foundoutput) { next; }
1.23      raeburn   323:        if ($line=~/^Incorrect syntax:/) { $syntaxerr = 1; next; }
                    324:        (my $label, $line) = ($line=~ /^(\(\%o\d+\))(.+)$/);
1.16      www       325:        if ($label) {
                    326:           $label=~s/\S/ /g;
                    327:           $line=$label.$line;
1.23      raeburn   328:           $hasoutput = 1;
1.16      www       329:        }
                    330:        $realoutput.=$line."\n";
                    331:     }
1.23      raeburn   332:     if (wantarray) {
                    333:         return ($realoutput,$hasoutput,$syntaxerr);
                    334:     } else {
                    335:         return $realoutput;
                    336:     }
1.15      www       337: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.