--- loncom/loncnew 2003/04/29 03:24:51 1.5 +++ loncom/loncnew 2003/06/03 01:59:39 1.7 @@ -2,7 +2,7 @@ # The LearningOnline Network with CAPA # lonc maintains the connections to remote computers # -# $Id: loncnew,v 1.5 2003/04/29 03:24:51 foxr Exp $ +# $Id: loncnew,v 1.7 2003/06/03 01:59:39 foxr Exp $ # # Copyright Michigan State University Board of Trustees # @@ -34,13 +34,22 @@ # - Add timer dispatch. (done) # - Add ability to accept lonc UNIX domain sockets. (done) # - Add ability to create/negotiate lond connections (done). -# - Add general logic for dispatching requests and timeouts. -# - Add support for the lonc/lond requests. +# - Add general logic for dispatching requests and timeouts. (done). +# - Add support for the lonc/lond requests. (done). # - Add logging/status monitoring. # - Add Signal handling - HUP restarts. USR1 status report. -# - Add Configuration file I/O +# - Add Configuration file I/O (done). # - Add Pending request processing on startup. # - Add management/status request interface. +# - Add deferred request capability. +# + +# Change log: +# $Log: loncnew,v $ +# Revision 1.7 2003/06/03 01:59:39 foxr +# complete coding to support deferred transactions. +# +# use lib "/home/httpd/lib/perl/"; use lib "/home/foxr/newloncapa/types"; @@ -49,11 +58,13 @@ use POSIX qw(:signal_h); use IO::Socket; use IO::Socket::INET; use IO::Socket::UNIX; +use IO::Handle; use Socket; use Crypt::IDEA; use LONCAPA::Queue; use LONCAPA::Stack; use LONCAPA::LondConnection; +use LONCAPA::LondTransaction; use LONCAPA::Configuration; use LONCAPA::HashIterator; @@ -95,14 +106,20 @@ my $RemoteHost; # Name of host child i my $UnixSocketDir= "/home/httpd/sockets"; my $IdleConnections = Stack->new(); # Set of idle connections my %ActiveConnections; # Connections to the remote lond. -my %ActiveTransactions; # Transactions in flight. +my %ActiveTransactions; # LondTransactions in flight. my %ActiveClients; # Serial numbers of active clients by socket. my $WorkQueue = Queue->new(); # Queue of pending transactions. -my $ClientQueue = Queue->new(); # Queue of clients causing xactinos. +# my $ClientQueue = Queue->new(); # Queue of clients causing xactinos. my $ConnectionCount = 0; my $IdleSeconds = 0; # Number of seconds idle. # +# This disconnected socket makes posible a bit more regular +# code when processing delayed requests: +# +my $NullSocket = IO::Socket->new(); + +# =pod @@ -190,7 +207,7 @@ sub Tick { $IdleSeconds++; if($IdleSeconds > $IdleTimeout) { # Prune a connection... $Socket = $IdleConnections->pop(); - KillSocket($Socket, 0); + KillSocket($Socket); } } else { $IdleSeconds = 0; # Reset idle count if not idle. @@ -250,24 +267,20 @@ long enough, it will be shut down and re sub ServerToIdle { my $Socket = shift; # Get the socket. + delete($ActiveTransactions{$Socket}); # Server has no transaction &Debug(6, "Server to idle"); # If there's work to do, start the transaction: - $reqdata = $WorkQueue->dequeue(); - Debug(9, "Queue gave request data: ".$reqdata); + $reqdata = $WorkQueue->dequeue(); # This is a LondTransaction unless($reqdata eq undef) { - my $unixSocket = $ClientQueue->dequeue(); - &Debug(6, "Starting new work request"); - &Debug(7, "Request: ".$reqdata); - - &StartRequest($Socket, $unixSocket, $reqdata); + Debug(9, "Queue gave request data: ".$reqdata->getRequest()); + &StartRequest($Socket, $reqdata); } else { # There's no work waiting, so push the server to idle list. &Debug(8, "No new work requests, server connection going idle"); - delete($ActiveTransactions{$Socket}); $IdleConnections->push($Socket); } } @@ -302,51 +315,55 @@ sub ClientWritable { &Debug(6, "ClientWritable writing".$Data); &Debug(9, "Socket is: ".$Socket); - my $result = $Socket->send($Data, 0); - - # $result undefined: the write failed. - # otherwise $result is the number of bytes written. - # Remove that preceding string from the data. - # If the resulting data is empty, destroy the watcher - # and set up a read event handler to accept the next - # request. - - &Debug(9,"Send result is ".$result." Defined: ".defined($result)); - if(defined($result)) { - &Debug(9, "send result was defined"); - if($result == length($Data)) { # Entire string sent. - &Debug(9, "ClientWritable data all written"); - $Watcher->cancel(); - # - # Set up to read next request from socket: - - my $descr = sprintf("Connection to lonc client %d", - $ActiveClients{$Socket}); - Event->io(cb => \&ClientRequest, - poll => 'r', - desc => $descr, - data => "", - fd => $Socket); - - } else { # Partial string sent. - $Watcher->data(substr($Data, $result)); - } + if($Socket->connected) { + my $result = $Socket->send($Data, 0); - } else { # Error of some sort... - - # Some errnos are possible: - my $errno = $!; - if($errno == POSIX::EWOULDBLOCK || - $errno == POSIX::EAGAIN || - $errno == POSIX::EINTR) { - # No action taken? - } else { # Unanticipated errno. - &Debug(5,"ClientWritable error or peer shutdown".$RemoteHost); - $Watcher->cancel; # Stop the watcher. - $Socket->shutdown(2); # Kill connection - $Socket->close(); # Close the socket. - } + # $result undefined: the write failed. + # otherwise $result is the number of bytes written. + # Remove that preceding string from the data. + # If the resulting data is empty, destroy the watcher + # and set up a read event handler to accept the next + # request. + &Debug(9,"Send result is ".$result." Defined: ".defined($result)); + if(defined($result)) { + &Debug(9, "send result was defined"); + if($result == length($Data)) { # Entire string sent. + &Debug(9, "ClientWritable data all written"); + $Watcher->cancel(); + # + # Set up to read next request from socket: + + my $descr = sprintf("Connection to lonc client %d", + $ActiveClients{$Socket}); + Event->io(cb => \&ClientRequest, + poll => 'r', + desc => $descr, + data => "", + fd => $Socket); + + } else { # Partial string sent. + $Watcher->data(substr($Data, $result)); + } + + } else { # Error of some sort... + + # Some errnos are possible: + my $errno = $!; + if($errno == POSIX::EWOULDBLOCK || + $errno == POSIX::EAGAIN || + $errno == POSIX::EINTR) { + # No action taken? + } else { # Unanticipated errno. + &Debug(5,"ClientWritable error or peer shutdown".$RemoteHost); + $Watcher->cancel; # Stop the watcher. + $Socket->shutdown(2); # Kill connection + $Socket->close(); # Close the socket. + } + + } + } else { + $Watcher->cancel(); # A delayed request...just cancel. } } @@ -367,22 +384,50 @@ Parameters: Socket on which the lond transaction occured. This is a LondConnection. The data received is in the TransactionReply member. -=item Client +=item Transaction -Unix domain socket open on the ultimate client. +The transaction that is being completed. =cut sub CompleteTransaction { &Debug(6,"Complete transaction"); my $Socket = shift; - my $Client = shift; + my $Transaction = shift; + + if (!$Transaction->isDeferred()) { # Normal transaction + my $data = $Socket->GetReply(); # Data to send. + StartClientReply($Transaction, $data); + } else { # Delete deferred transaction file. + unlink $Transaction->getFile(); + } +} +=pod +=head1 StartClientReply + + Initiates a reply to a client where the reply data is a parameter. + +=head2 parameters: + +=item Transaction - my $data = $Socket->GetReply(); # Data to send. + The transaction for which we are responding to the client. + +=item data + + The data to send to apached client. + +=cut +sub StartClientReply { + my $Transaction = shift; + my $data = shift; + + my $Client = $Transaction->getClient(); &Debug(8," Reply was: ".$data); my $Serial = $ActiveClients{$Client}; my $desc = sprintf("Connection to lonc client %d", + $Serial); Event->io(fd => $Client, poll => "w", @@ -394,30 +439,47 @@ sub CompleteTransaction { =head2 FailTransaction Finishes a transaction with failure because the associated lond socket - disconnected. It is up to our client to retry if desired. + disconnected. There are two possibilities: + - The transaction is deferred: in which case we just quietly + delete the transaction since there is no client connection. + - The transaction is 'live' in which case we initiate the sending + of "con_lost" to the client. + +Deleting the transaction means killing it from the +%ActiveTransactions hash. Parameters: =item client - The UNIX domain socket open on our client. - + The LondTransaction we are failing. + =cut sub FailTransaction { - my $client = shift; + my $transaction = shift; + my $Lond = $transaction->getServer(); + if (!$client->isDeferred()) { # If the transaction is deferred we'll get to it. + my $client = $transcation->getClient(); + StartClientReply($client, "con_lost"); + } +# not needed, done elsewhere if active. +# delete $ActiveTransactions{$Lond}; + +} - &Debug(8, "Failing transaction due to disconnect"); - my $Serial = $ActiveClients{$client}; - my $desc = sprintf("Connection to lonc client %d", $Serial); - my $data = "error: Connection to lond lost\n"; - - Event->io(fd => $client, - poll => "w", - desc => $desc, - cb => \&ClientWritable, - data => $data); +=pod +=head1 EmptyQueue + + Fails all items in the work queue with con_lost. + Note that each item in the work queue is a transaction. +=cut +sub EmptyQueue { + while($WorkQueue->Count()) { + my $request = $Workqueue->dequeue(); # This is a transaction + FailTransaction($request); + } } =pod @@ -444,10 +506,12 @@ nonzero if we are allowed to create a ne =cut sub KillSocket { my $Socket = shift; - my $Restart= shift; - # If the socket came from the active connection set, delete it. - # otherwise it came from the idle set and has already been destroyed: + # If the socket came from the active connection set, + # delete its transaction... note that FailTransaction should + # already have been called!!! + # otherwise it came from the idle set. + # if(exists($ActiveTransactions{$Socket})) { delete ($ActiveTransactions{$Socket}); @@ -456,10 +520,13 @@ sub KillSocket { delete($ActiveConnections{$Socket}); } $ConnectionCount--; - if( ($ConnectionCount = 0) && ($Restart)) { - MakeLondConnection(); - } + # If the connection count has gone to zero and there is work in the + # work queue, the work all gets failed with con_lost. + # + if($ConnectionCount == 0) { + EmptyQueue; + } } =pod @@ -541,7 +608,7 @@ sub LondReadable { FailTransaction($ActiveTransactions{$Socket}); } $Watcher->cancel(); - KillSocket($Socket, 1); + KillSocket($Socket); return; } SocketDump(6,$Socket); @@ -582,7 +649,7 @@ sub LondReadable { } $Watcher->cancel(); ServerToIdle($Socket); # Next work unit or idle. - + } elsif ($State eq "SendingRequest") { # We need to be writable for this and probably don't belong # here inthe first place. @@ -667,7 +734,7 @@ sub LondWritable { my @data = $Watcher->data; Debug(6,"LondWritable State = ".$State." data has ".@data." elts.\n"); - my $Socket = $data[0]; # I know there's at least a socket. + my $Socket = $data; # I know there's at least a socket. # Figure out what to do depending on the state of the socket: @@ -684,7 +751,7 @@ sub LondWritable { # We'll treat this as if the socket got disconnected: $Watcher->cancel(); - KillSocket($Socket, 1); + KillSocket($Socket); return; } # "init" is being sent... @@ -706,7 +773,7 @@ sub LondWritable { if($Socket->Writable() != 0) { $Watcher->cancel(); - KillSocket($Socket, 1); + KillSocket($Socket); return; } @@ -727,7 +794,7 @@ sub LondWritable { # Write resulted in an error. $Watcher->cancel(); - KillSocket($Socket, 1); + KillSocket($Socket); return; } @@ -749,7 +816,7 @@ sub LondWritable { FailTransaction($ActiveTransactions{$Socket}); } $Watcher->cancel(); - KillSocket($Socket, 1); + KillSocket($Socket); return; } @@ -771,6 +838,27 @@ sub LondWritable { } } +=pod + +=cut +sub QueueDelayed { + my $path = "$perlvar{'lonSockDir'}/delayed"; + opendir(DIRHANDLE, $path); + @alldelayed = grep /\.$RemoteHost$/, readdir DIRHANDLE; + closedir(DIRHANDLE); + my $dfname; + my $reqfile + foreach $reqfile (sort @alldelayed) { + $reqfile = $path/$reqfile; + my $Handle = IO::File->new($reqfile); + my $cmd = <$Handle>; + chomp($cmd); + my $Transaction = LondTransaction->new($cmd); + $Transaction->SetDeferred($reqfile); + QueueTransaction($Transaction); + } + +} =pod @@ -810,11 +898,14 @@ sub MakeLondConnection { $event = Event->io(fd => $Socket, poll => 'w', cb => \&LondWritable, - data => ($Connection, undef), + data => \$Connection, desc => 'Connection to lond server'); $ActiveConnections{$Connection} = $event; $ConnectionCount++; + if($ConnectionCount == 1) { # First Connection: + QueueDelayed; + } } } @@ -846,16 +937,16 @@ The text of the request to send. sub StartRequest { my $Lond = shift; - my $Client = shift; - my $Request = shift; + my $Request = shift; # This is a LondTransaction. - Debug(6, "StartRequest: ".$Request); + Debug(6, "StartRequest: ".$Request->getRequest()); my $Socket = $Lond->GetSocket(); - $ActiveTransactions{$Lond} = $Client; # Socket to relay to client. + $Request->Activate($Lond); + $ActiveTransactions{$Lond} = $Request; - $Lond->InitiateTransaction($Request); + $Lond->InitiateTransaction($Request->getRequest()); $event = Event->io(fd => $Lond->GetSocket(), poll => "w", cb => \&LondWritable, @@ -887,15 +978,15 @@ data to send to the lond. =cut sub QueueTransaction { - my $requestSocket = shift; - my $requestData = shift; - Debug(6,"QueueTransaction: ".$requestData); + my $requestData = shift; # This is a LondTransaction. + my $cmd = $requestData->getRequest(); + + Debug(6,"QueueTransaction: ".$cmd); my $LondSocket = $IdleConnections->pop(); if(!defined $LondSocket) { # Need to queue request. Debug(8,"Must queue..."); - $ClientQueue->enqueue($requestSocket); $WorkQueue->enqueue($requestData); if($ConnectionCount < $MaxConnectionCount) { Debug(4,"Starting additional lond connection"); @@ -903,7 +994,7 @@ sub QueueTransaction { } } else { # Can start the request: Debug(8,"Can start..."); - StartRequest($LondSocket, $requestSocket, $requestData); + StartRequest($LondSocket, $requestData); } } @@ -943,7 +1034,9 @@ sub ClientRequest { $watcher->data($data); if($data =~ /(.*\n)/) { # Request entirely read. Debug(8, "Complete transaction received: ".$data); - QueueTransaction($socket, $data); + my $Transaction = new LondTransaction->new($data); + $Transaction->SetClient($socket); + QueueTransaction($Transaction); $watcher->cancel(); # Done looking for input data. } @@ -1005,7 +1098,7 @@ Returns the host whose lond we talk with =cut -sub GetServerHost { # Stub - get this from config. +sub GetServerHost { return $RemoteHost; # Setup by the fork. } @@ -1017,7 +1110,7 @@ Returns the lond port number. =cut -sub GetServerPort { # Stub - get this from config. +sub GetServerPort { return $perlvar{londPort}; } @@ -1038,7 +1131,7 @@ sub SetupLoncListener { my $socket; my $SocketName = GetLoncSocketPath(); unlink($SocketName); - unless ($socket = IO::Socket::UNIX->new(Local => $SocketName, + unless ($socket =IO::Socket::UNIX->new(Local => $SocketName, Listen => 10, Type => SOCK_STREAM)) { die "Failed to create a lonc listner socket"; @@ -1125,12 +1218,26 @@ sub CreateChild { # + + ShowStatus("Parent writing pid file:"); $execdir = $perlvar{'lonDaemons'}; open (PIDSAVE, ">$execdir/logs/lonc.pid"); print PIDSAVE "$$\n"; close(PIDSAVE); +ShowStatus("Forming new session"); +my $childpid = fork; +if ($childpid != 0) { + sleep 4; # Give child a chacne to break to + exit 0; # a new sesion. +} + +if (POSIX::setsid() < 0) { + print "Could not create new session\n"; + exit -1; +} + ShowStatus("Forking node servers"); my $HostIterator = LondConnection::GetHostIterator;