Annotation of loncom/lonmaxima, revision 1.37

1.1       www         1: #!/usr/bin/perl
                      2: #
                      3: # The LearningOnline Network with CAPA
                      4: # Connect to MAXIMA CAS
                      5: #
1.37    ! bisitz      6: # $Id: lonmaxima,v 1.36 2007/11/09 18:54:46 albertel 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.35      albertel   47: my $extra_children         = 0;
1.3       albertel   48: my %children               = ();       # keys are current child process IDs
                     49: my $children               = 0;        # current number of children
                     50: my $status;                            # string for current status
1.5       albertel   51: my $pidfile;                           # file containg parent process pid
                     52: my $port;                              # path to UNIX socket file
                     53: my %perlvar;                           # configuration file info
                     54: my $lastlog;                           # last string that was logged
1.18      www        55: 
1.16      www        56: use vars qw($PREFORK $MAX_CLIENTS_PER_CHILD %children $children $status
1.12      www        57: 	    $pidfile $port %perlvar $lastlog);
1.2       www        58:  
                     59: # ------------------------------------------------------------ Service routines 
                     60: sub REAPER {                        # takes care of dead children 
                     61:                                     # and MAXIMA processes
                     62:     $SIG{CHLD} = \&REAPER;
                     63:     my $pid = wait;
1.35      albertel   64:     if (exists($children{$pid})) {
                     65: 	$children--;
                     66: 	delete($children{$pid});
                     67: 	if ($extra_children) {
                     68: 	    $extra_children--;
                     69: 	}
                     70:     }    
1.2       www        71: }
                     72:  
                     73: sub HUNTSMAN {                      # signal handler for SIGINT
                     74:     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
1.6       albertel   75:     kill('INT' => keys(%children));
1.2       www        76:     unlink($pidfile);
                     77:     unlink($port);
                     78:     &logthis('---- Shutdown ----');
                     79:     exit;                           # clean up with dignity
                     80: }
                     81: 
                     82: 
                     83:  
                     84: # --------------------------------------------------------------------- Logging
                     85:  
                     86: sub logthis {
1.4       albertel   87:     my ($message)=@_;
1.2       www        88:     my $execdir=$perlvar{'lonDaemons'};
                     89:     my $fh=IO::File->new(">>$execdir/logs/lonmaxima.log");
                     90:     my $now=time;
                     91:     my $local=localtime($now);
                     92:     $lastlog=$local.': '.$message;
                     93:     print $fh "$local ($$): $message\n";
                     94: }
                     95:  
                     96: # -------------------------------------------------------------- Status setting
                     97:  
                     98: sub status {
1.4       albertel   99:     my ($what)=@_;
1.2       www       100:     my $now=time;
                    101:     my $local=localtime($now);
                    102:     $status=$local.': '.$what;
                    103:     $0='lonmaxima: '.$what.' '.$local;
                    104: }
                    105:  
                    106: # -------------------------------------------------------- Escape Special Chars
                    107:  
                    108: sub escape {
1.4       albertel  109:     my ($str)=@_;
1.2       www       110:     $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
                    111:     return $str;
                    112: }
                    113:  
                    114: # ----------------------------------------------------- Un-Escape Special Chars
                    115:  
                    116: sub unescape {
1.4       albertel  117:     my ($str)=@_;
1.2       www       118:     $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
                    119:     return $str;
                    120: }
                    121:  
                    122: # ------------------------ grabs exception and records it to log before exiting
                    123: sub catchexception {
                    124:     my ($signal)=@_;
                    125:     $SIG{QUIT}='DEFAULT';
                    126:     $SIG{__DIE__}='DEFAULT';
                    127:     chomp($signal);
1.5       albertel  128:     &logthis("<font color=\"red\">CRITICAL: "
                    129: 	     ."ABNORMAL EXIT. Child $$ died through "
                    130: 	     ."\"$signal\"</font>");
1.2       www       131:     die("Signal abend");
                    132: }
1.5       albertel  133: 
1.16      www       134: 
1.35      albertel  135: sub child_announce_death {
                    136:     $SIG{USR1} = \&child_announce_death;
1.36      albertel  137:     if ($extra_children < $PREFORK*10) {
                    138: 	$extra_children++;
                    139:     }
1.35      albertel  140: }
1.16      www       141: 
1.2       www       142: # ---------------------------------------------------------------- Main program
                    143: # -------------------------------- Set signal handlers to record abnormal exits
                    144:  
                    145:  
                    146: $SIG{'QUIT'}=\&catchexception;
                    147: $SIG{__DIE__}=\&catchexception;
1.35      albertel  148: $SIG{USR1} = \&child_announce_death;
1.2       www       149:  
                    150: # ---------------------------------- Read loncapa_apache.conf and loncapa.conf
                    151: &status("Read loncapa.conf and loncapa_apache.conf");
1.3       albertel  152: %perlvar=%{&LONCAPA::Configuration::read_conf('loncapa.conf')};
1.2       www       153:  
                    154: # ----------------------------- Make sure this process is running from user=www
                    155: my $wwwid=getpwnam('www');
                    156: if ($wwwid!=$<) {
1.5       albertel  157:     my $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
                    158:     my $subj="LON: User ID mismatch";
                    159:     system("echo 'User ID mismatch.  lonmaxima must be run as user www.' |\
1.2       www       160:  mailto $emailto -s '$subj' > /dev/null");
1.5       albertel  161:     exit 1;
1.2       www       162: }
                    163:  
                    164: # --------------------------------------------- Check if other instance running
                    165:  
                    166: $pidfile="$perlvar{'lonDaemons'}/logs/lonmaxima.pid";
                    167:  
                    168: if (-e $pidfile) {
1.5       albertel  169:     my $lfh=IO::File->new("$pidfile");
                    170:     my $pide=<$lfh>;
                    171:     chomp($pide);
1.6       albertel  172:     if (kill(0 => $pide)) { die "already running"; }
1.2       www       173: }
1.5       albertel  174: 
1.2       www       175: # ------------------------------------------------------- Listen to UNIX socket
                    176: &status("Opening socket");
                    177:  
                    178: $port = "$perlvar{'lonSockDir'}/maximasock";
                    179:  
                    180: unlink($port);
                    181:  
                    182: 
1.6       albertel  183: my $server = IO::Socket::UNIX->new(Local  => $port,
                    184: 				   Type   => SOCK_STREAM,
                    185: 				   Listen => 10 );
                    186: if (!$server) {
                    187:     my $st=120+int(rand(240));
                    188: 
                    189:     &logthis("<font color=blue>WARNING: ".
                    190: 	     "Can't make server socket ($st secs):  .. exiting</font>");
                    191: 
                    192:     sleep($st);
                    193:     exit;
                    194: }
1.2       www       195:     
                    196:  
                    197: # ---------------------------------------------------- Fork once and dissociate
                    198:  
                    199: my $fpid=fork;
                    200: exit if $fpid;
1.6       albertel  201: die("Couldn't fork: $!") unless defined($fpid);
1.2       www       202:  
                    203: POSIX::setsid() or die "Can't start new session: $!";
                    204:  
                    205: # ------------------------------------------------------- Write our PID on disk
                    206:  
                    207: my $execdir=$perlvar{'lonDaemons'};
1.5       albertel  208: open(PIDSAVE,">$execdir/logs/lonmaxima.pid");
1.2       www       209: print PIDSAVE "$$\n";
                    210: close(PIDSAVE);
                    211: &logthis("<font color='red'>CRITICAL: ---------- Starting ----------</font>");
                    212: &status('Starting');
1.6       albertel  213:      
1.2       www       214: 
1.10      albertel  215: # Install signal handlers.
                    216: $SIG{CHLD} = \&REAPER;
                    217: $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
1.16      www       218:  
1.2       www       219: # Fork off our children.
                    220: for (1 .. $PREFORK) {
1.16      www       221:     &make_new_child($server);
1.2       www       222: }
                    223:  
                    224: # And maintain the population.
                    225: while (1) {
                    226:     &status('Parent process, sleeping');
                    227:     sleep;                          # wait for a signal (i.e., child's death)
1.35      albertel  228:     for (my $i = $children; $i < $PREFORK+$extra_children; $i++) {
1.2       www       229:         &status('Parent process, starting child');
1.16      www       230:         &make_new_child($server);           # top up the child pool
1.2       www       231:     }
                    232: }
                    233:                                                                                 
                    234: sub make_new_child {
1.16      www       235:     my ($server) = @_;
1.4       albertel  236: 
1.2       www       237:     # block signal for fork
1.4       albertel  238:     my $sigset = POSIX::SigSet->new(SIGINT);
1.2       www       239:     sigprocmask(SIG_BLOCK, $sigset)
1.6       albertel  240:         or die("Can't block SIGINT for fork: $!\n");
1.2       www       241:      
1.6       albertel  242:     die("fork: $!") unless defined(my $pid = fork);
1.2       www       243:      
                    244:     if ($pid) {
                    245:         # Parent records the child's birth and returns.
                    246:         sigprocmask(SIG_UNBLOCK, $sigset)
1.6       albertel  247:             or die("Can't unblock SIGINT for fork: $!\n");
1.16      www       248:         $children{$pid} = 1;
1.2       www       249:         $children++;
                    250:         return;
                    251:     } else {
                    252:         # Child can *not* return from this subroutine.
1.30      albertel  253:         
1.35      albertel  254: 	my $ppid = getppid();
1.2       www       255:      
                    256:         # unblock signals
                    257:         sigprocmask(SIG_UNBLOCK, $sigset)
1.6       albertel  258:             or die("Can't unblock SIGINT for fork: $!\n");
1.15      www       259: 
1.17      www       260:         &logthis('New process started');
                    261: 
1.16      www       262:         my $command=Expect->spawn('maxima');
1.30      albertel  263: 	# soft/hard_close can take awhile and we really
                    264:         # don't care we just want it gone
                    265: 	$SIG{INT} = sub {
                    266: 	    my $pid = $command->pid();
                    267: 	    kill('KILL'=>$pid);
                    268: 	    exit; 
                    269: 	};
                    270: 
                    271: 	$command->log_stdout(0);
1.27      albertel  272: 	#$command->log_file("$execdir/logs/lonmaxima.session.log");
1.16      www       273: 
1.13      www       274:         for (my $i=0; $i < $MAX_CLIENTS_PER_CHILD; $i++) {
1.26      raeburn   275:             &status('Accepting connections');
                    276:             my $client = $server->accept()     or last;
1.37    ! bisitz    277:             &sync($command);
1.29      www       278:             print $command ("display2d:false;kill(all);\n");
1.27      albertel  279: 	    &getmaximaoutput($command,2);
1.26      raeburn   280:             &sync($command);
                    281:             my $syntaxerr = 0;
                    282:             while (my $cmd=<$client>) {
                    283:                 &status('Processing command');
                    284:                 print $command &unescape($cmd);
                    285:                 my ($reply,$syntaxerr) = &getmaximaoutput($command,1);
                    286:                 print $client &escape($reply)."\n";
                    287:                 if ($syntaxerr) {
                    288:                     last;
                    289:                 } elsif ($reply=~/^Error\:/) {
                    290:                     &logthis('Died through '.$reply);
1.35      albertel  291: 		    kill('USR1' => $ppid);
1.26      raeburn   292:                     $client->close();
                    293:                     $command->hard_close();     
                    294:                     exit;
                    295:                 }
                    296: 	        &sync($command);
                    297:                 &status('Waiting for commands');
                    298:             }
1.13      www       299:         }
1.4       albertel  300: 
1.35      albertel  301: 	kill('USR1' => $ppid);
1.34      albertel  302: 	print $command ("quit();\n");
1.12      www       303:         # tidy up gracefully and finish
1.36      albertel  304: 	sleep(15);
1.16      www       305:         $command->soft_close();
1.1       www       306: 
1.2       www       307:         # this exit is VERY important, otherwise the child will become
                    308:         # a producer of more and more children, forking yourself into
                    309:         # process death.
                    310:         exit;
                    311:     }
                    312: }
1.4       albertel  313: 
1.18      www       314: {
                    315:     my $counter;
                    316:     sub sync {
                    317: 	my ($command)=@_;
                    318: 	$counter++;
                    319: 	my $expect=$counter.time;
                    320: 	print $command "$expect;\n";
                    321: 	while (1) {
1.25      raeburn   322: 	    my $output=&getmaximaoutput($command,1);
1.18      www       323: 	    if (($output=~/\Q$expect\E/) || ($output=~/^Error\:/)) {
                    324: 		return;
                    325: 	    }
                    326: 	}
                    327:     }
                    328: }
                    329: 
1.16      www       330: sub getmaximaoutput {
1.25      raeburn   331:     my ($command,$numcheck)=@_;
                    332:     my $regexp = '\(\%i\d+\)';
1.26      raeburn   333:     my $syntaxerr=0;
1.25      raeburn   334:     if ($numcheck) {
1.27      albertel  335:        	if ($numcheck eq 2) {
                    336: 	    # command was the killall so should get a full reset on
                    337: 	    # command numbers
                    338: 	    $regexp = '(\(\%i(1)\)|Incorrect syntax\:)';
                    339: 	} elsif ($command->match() =~ /\(\%i(\d+)\)/) {
1.25      raeburn   340:             my $nextmatch = $1+1;
1.26      raeburn   341:             $regexp = '(\(\%i'.$nextmatch.'\)|Incorrect syntax\:)';
                    342:         }
                    343:     }
                    344:     my $timeout = 20;
1.27      albertel  345:     my (undef,$error,$matched,$output) =
                    346: 	$command->expect($timeout, -re => $regexp);
                    347: 
                    348:     if ($numcheck && $matched eq 'Incorrect syntax:') {
                    349: 	$syntaxerr = 1;
                    350: 	if (wantarray) {
                    351: 	    return ($matched,$syntaxerr);
                    352: 	} else {
                    353: 	    return $matched;
                    354: 	}
1.25      raeburn   355:     }
1.17      www       356:     if ($error) {
1.27      albertel  357: 	return 'Error: '.$error;
1.17      www       358:     }
1.23      raeburn   359:     $output =~ s/\r+//g; # Remove Windows-style linebreaks
1.16      www       360:     my $foundoutput=0;
1.32      albertel  361:     my $found_label=0;
1.16      www       362:     my $realoutput='';
                    363:     foreach my $line (split(/\n/,$output)) {
                    364:        if ($line=~/\;/) { $foundoutput=1; next; }
                    365:        if (!$foundoutput) { next; }
1.23      raeburn   366:        if ($line=~/^Incorrect syntax:/) { $syntaxerr = 1; next; }
1.32      albertel  367:        if ($line=~ /^(\(\%o\d+\))(.+)$/){
1.31      bisitz    368:            my $label = $1;
                    369:            $line = $2;
                    370:            $label =~s/\S/ /g;
1.26      raeburn   371:            $line=$label.$line;
1.32      albertel  372: 	   $found_label=1;
                    373:        }
                    374:        if ($found_label) {
                    375: 	   $realoutput.=$line."\n";
1.16      www       376:        }
                    377:     }
1.23      raeburn   378:     if (wantarray) {
1.26      raeburn   379:         return ($realoutput,$syntaxerr);
1.23      raeburn   380:     } else {
                    381:         return $realoutput;
                    382:     }
1.15      www       383: }

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.