--- loncom/lonsql 2002/08/13 19:40:57 1.52 +++ loncom/lonsql 2006/02/10 09:50:50 1.71.2.1 @@ -3,7 +3,7 @@ # The LearningOnline Network # lonsql - LON TCP-MySQL-Server Daemon for handling database requests. # -# $Id: lonsql,v 1.52 2002/08/13 19:40:57 matthew Exp $ +# $Id: lonsql,v 1.71.2.1 2006/02/10 09:50:50 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -39,10 +39,59 @@ lonsql - LON TCP-MySQL-Server Daemon for This script should be run as user=www. Note that a lonsql.pid file contains the pid of the parent process. -=head1 DESCRIPTION +=head1 OVERVIEW -lonsql is many things to many people. To me, it is a source file in need -of documentation. +=head2 Purpose within LON-CAPA + +LON-CAPA is meant to distribute A LOT of educational content to A LOT +of people. It is ineffective to directly rely on contents within the +ext2 filesystem to be speedily scanned for on-the-fly searches of +content descriptions. (Simply put, it takes a cumbersome amount of +time to open, read, analyze, and close thousands of files.) + +The solution is to index various data fields that are descriptive of +the educational resources on a LON-CAPA server machine in a +database. Descriptive data fields are referred to as "metadata". The +question then arises as to how this metadata is handled in terms of +the rest of the LON-CAPA network without burdening client and daemon +processes. + +The obvious solution, using lonc to send a query to a lond process, +doesn't work so well in general as you can see in the following +example: + + lonc= loncapa client process A-lonc= a lonc process on Server A + lond= loncapa daemon process + + database command + A-lonc --------TCP/IP----------------> B-lond + +The problem emerges that A-lonc and B-lond are kept waiting for the +MySQL server to "do its stuff", or in other words, perform the +conceivably sophisticated, data-intensive, time-sucking database +transaction. By tying up a lonc and lond process, this significantly +cripples the capabilities of LON-CAPA servers. + +The solution is to offload the work onto another process, and use +lonc and lond just for requests and notifications of completed +processing: + + database command + + A-lonc ---------TCP/IP-----------------> B-lond =====> B-lonsql + <---------------------------------/ | + "ok, I'll get back to you..." | + | + / + A-lond <------------------------------- B-lonc <====== + "Guess what? I have the result!" + +Of course, depending on success or failure, the messages may vary, but +the principle remains the same where a separate pool of children +processes (lonsql's) handle the MySQL database manipulations. + +Thus, lonc and lond spend effectively no time waiting on results from +the database. =head1 Internals @@ -54,6 +103,7 @@ use strict; use lib '/home/httpd/lib/perl/'; use LONCAPA::Configuration; +use LONCAPA::lonmetadata(); use IO::Socket; use Symbol; @@ -65,6 +115,7 @@ use Fcntl; use Tie::RefHash; use DBI; use File::Find; +use localenroll; ######################################################## ######################################################## @@ -152,10 +203,26 @@ my $run =0; # running count # # Read loncapa_apache.conf and loncapa.conf # -my $perlvarref=LONCAPA::Configuration::read_conf('loncapa_apache.conf', - 'loncapa.conf'); +my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf'); my %perlvar=%{$perlvarref}; # +# Write the /home/www/.my.cnf file +my $conf_file = '/home/www/.my.cnf'; +if (! -e $conf_file) { + if (open MYCNF, ">$conf_file") { + print MYCNF <<"ENDMYCNF"; +[client] +user=www +password=$perlvar{'lonSqlAccess'} +ENDMYCNF + close MYCNF; + } else { + warn "Unable to write $conf_file, continuing"; + } +} + + +# # Make sure that database can be accessed # my $dbh; @@ -167,8 +234,14 @@ unless ($dbh = DBI->connect("DBI:mysql:l my $subj="LON: $perlvar{'lonHostID'} Cannot connect to database!"; system("echo 'Cannot connect to MySQL database!' |". " mailto $emailto -s '$subj' > /dev/null"); + + open(SMP,'>/home/httpd/html/lon-status/mysql.txt'); + print SMP 'time='.time.'&mysql=defunct'."\n"; + close(SMP); + exit 1; } else { + unlink('/home/httpd/html/lon-status/mysql.txt'); $dbh->disconnect; } @@ -186,20 +259,18 @@ if (-e $pidfile) { # # Read hosts file # -my %hostip; my $thisserver; my $PREFORK=4; # number of children to maintain, at least four spare open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file"; while (my $configline=) { - my ($id,$domain,$role,$name,$ip)=split(/:/,$configline); - chomp($ip); - $hostip{$ip}=$id; + my ($id,$domain,$role,$name)=split(/:/,$configline); + $name=~s/\s//g; $thisserver=$name if ($id eq $perlvar{'lonHostID'}); - $PREFORK++; + #$PREFORK++; } close(CONFIG); # -$PREFORK=int($PREFORK/4); +#$PREFORK=int($PREFORK/4); # # Create a socket to talk to lond @@ -228,7 +299,7 @@ my $execdir=$perlvar{'lonDaemons'}; open (PIDSAVE,">$execdir/logs/lonsql.pid"); print PIDSAVE "$$\n"; close(PIDSAVE); -&logthis("CRITICAL: ---------- Starting ----------"); +&logthis("CRITICAL: ---------- Starting ----------"); # # Ignore signals generated during initial startup @@ -299,7 +370,7 @@ sub make_new_child { $perlvar{'lonSqlAccess'}, { RaiseError =>0,PrintError=>0})) { sleep(10+int(rand(20))); - &logthis("WARNING: Couldn't connect to database". + &logthis("WARNING: Couldn't connect to database". ": $@"); # "($st secs): $@"); print "database handle error\n"; @@ -327,7 +398,7 @@ sub make_new_child { $queryid .= $run; print $client "$queryid\n"; # - &logthis("QUERY: $query - $arg1 - $arg2 - $arg3"); + # &logthis("QUERY: $query - $arg1 - $arg2 - $arg3"); sleep 1; # my $result=''; @@ -352,6 +423,49 @@ sub make_new_child { $result='no_such_file'; } # end of log query + } elsif (($query eq 'fetchenrollment') || + ($query eq 'institutionalphotos')) { + # retrieve institutional class lists + my $dom = &unescape($arg1); + my %affiliates = (); + my %replies = (); + my $locresult = ''; + my $querystr = &unescape($arg3); + foreach (split/%%/,$querystr) { + if (/^([^=]+)=([^=]+)$/) { + @{$affiliates{$1}} = split/,/,$2; + } + } + if ($query eq 'fetchenrollment') { + $locresult = &localenroll::fetch_enrollment($dom,\%affiliates,\%replies); + } elsif ($query eq 'institutionalphotos') { + my $crs = &unescape($arg2); + eval { + local($SIG{__DIE__})='DEFAULT'; + $locresult = &localenroll::institutional_photos($dom,$crs,\%affiliates,\%replies,'update'); + }; + if ($@) { + $locresult = 'error'; + } + } + $result = &escape($locresult.':'); + if ($locresult) { + $result .= &escape(join(':',map{$_.'='.$replies{$_}} keys %replies)); + } + } elsif ($query eq 'prepare activity log') { + my ($cid,$domain) = map {&unescape($_);} ($arg1,$arg2); + &logthis('preparing activity log tables for '.$cid); + my $command = + qq{$perlvar{'lonDaemons'}/parse_activity_log.pl -course=$cid -domain=$domain}; + system($command); + &logthis($command); + my $returnvalue = $?>>8; + if ($returnvalue) { + $result = 'error: parse_activity_log.pl returned '. + $returnvalue; + } else { + $result = 'success'; + } } else { # Do an sql query $result = &do_sql_query($query,$arg1,$arg2); @@ -359,15 +473,13 @@ sub make_new_child { # result does not need to be escaped because it has already been # escaped. #$result=&escape($result); - # reply with result, append \n unless already there - $result.="\n" unless ($result=~/\n$/); &reply("queryreply:$queryid:$result",$conserver); } # tidy up gracefully and finish # # close the database handle $dbh->disconnect - or &logthis("WARNING: Couldn't disconnect". + or &logthis("WARNING: Couldn't disconnect". " from database $DBI::errstr : $@"); # this exit is VERY important, otherwise the child will become # a producer of more and more children, forking yourself into @@ -408,6 +520,7 @@ sub process_file { sub do_sql_query { my ($query,$custom,$customshow) = @_; +# &logthis('doing query '.$query); $custom = &unescape($custom); $customshow = &unescape($customshow); # @@ -422,8 +535,9 @@ sub do_sql_query { #prepare and execute the query my $sth = $dbh->prepare($query); unless ($sth->execute()) { - &logthis("WARNING: ". - "Could not retrieve from database: $@"); + &logthis(''. + 'WARNING: Could not retrieve from database:'. + $sth->errstr().''); } else { my $aref=$sth->fetchall_arrayref; foreach my $row (@$aref) { @@ -855,7 +969,7 @@ sub HUNTSMAN { # si kill 'INT' => keys %children; my $execdir=$perlvar{'lonDaemons'}; unlink("$execdir/logs/lonsql.pid"); - &logthis("CRITICAL: Shutting down"); + &logthis("CRITICAL: Shutting down"); $unixsock = "mysqlsock"; my $port="$perlvar{'lonSockDir'}/$unixsock"; unlink($port); @@ -866,7 +980,7 @@ sub HUPSMAN { # sig local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children kill 'INT' => keys %children; close($server); # free up socket - &logthis("CRITICAL: Restarting"); + &logthis("CRITICAL: Restarting"); my $execdir=$perlvar{'lonDaemons'}; $unixsock = "mysqlsock"; my $port="$perlvar{'lonSockDir'}/$unixsock"; @@ -876,23 +990,12 @@ sub HUPSMAN { # sig sub DISCONNECT { $dbh->disconnect or - &logthis("WARNING: Couldn't disconnect from database ". + &logthis("WARNING: Couldn't disconnect from database ". " $DBI::errstr : $@"); exit; } - - - - - - - - - -# ----------------------------------- POD (plain old documentation, CPAN style) - =pod =back