--- loncom/loncnew 2003/05/05 23:40:27 1.6 +++ loncom/loncnew 2003/06/11 02:04:35 1.8 @@ -2,7 +2,7 @@ # The LearningOnline Network with CAPA # lonc maintains the connections to remote computers # -# $Id: loncnew,v 1.6 2003/05/05 23:40:27 foxr Exp $ +# $Id: loncnew,v 1.8 2003/06/11 02:04:35 foxr Exp $ # # Copyright Michigan State University Board of Trustees # @@ -34,13 +34,26 @@ # - 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 Pending request processing on startup. +# - Add Configuration file I/O (done). # - Add management/status request interface. +# - Add deferred request capability. (done) +# + +# Change log: +# $Log: loncnew,v $ +# Revision 1.8 2003/06/11 02:04:35 foxr +# Support delayed transactions... this is done uniformly by encapsulating +# transactions in an object ... a LondTransaction that is implemented by +# LondTransaction.pm +# +# 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"; @@ -55,6 +68,7 @@ use Crypt::IDEA; use LONCAPA::Queue; use LONCAPA::Stack; use LONCAPA::LondConnection; +use LONCAPA::LondTransaction; use LONCAPA::Configuration; use LONCAPA::HashIterator; @@ -86,7 +100,7 @@ my %ChildHash; # by pid -> host. my $MaxConnectionCount = 5; # Will get from config later. my $ClientConnection = 0; # Uniquifier for client events. -my $DebugLevel = 5; +my $DebugLevel = 2; my $IdleTimeout= 3600; # Wait an hour before pruning connections. # @@ -96,10 +110,10 @@ 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. @@ -257,24 +271,21 @@ 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); } } @@ -378,30 +389,49 @@ 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; - my $data = $Socket->GetReply(); # Data to send. - StartClientReply($Client, $data); + if (!$Transaction->isDeferred()) { # Normal transaction + my $data = $Socket->GetReply(); # Data to send. + StartClientReply($Transaction, $data); + } else { # Delete deferred transaction file. + &Debug(4, "Deferred transaction complete: ".$Transaction->getFile(). + " request: ".$Transaction->getRequest(). + " answer: ".$Socket->GetReply()); + unlink $Transaction->getFile(); + } } =pod =head1 StartClientReply Initiates a reply to a client where the reply data is a parameter. +=head2 parameters: + +=item Transaction + + The transaction for which we are responding to the client. + +=item data + + The data to send to apached client. + =cut sub StartClientReply { - my $Client = shift; + 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", @@ -417,32 +447,46 @@ sub StartClientReply { =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; - - StartClientReply($client, "con_lost"); + 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}; } =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(); # Just to help it become empty. - my $client = $ClientQueue->dequeue(); # Need to con_lost this guy. - FailTransaction($client); + my $request = $Workqueue->dequeue(); # This is a transaction + FailTransaction($request); } } @@ -471,8 +515,11 @@ nonzero if we are allowed to create a ne sub KillSocket { my $Socket = 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}); @@ -549,15 +596,17 @@ transaction is in progress, the socket a =cut sub LondReadable { + my $Event = shift; my $Watcher = $Event->w; my $Socket = $Watcher->data; my $client = undef; + &Debug(6,"LondReadable called state = ".$State); + my $State = $Socket->GetState(); # All action depends on the state. - &Debug(6,"LondReadable called state = ".$State); SocketDump(6, $Socket); if($Socket->Readable() != 0) { @@ -585,8 +634,8 @@ sub LondReadable { # in the connection takes care of setting that up. Just # need to transition to writable: - $Watcher->poll("w"); $Watcher->cb(\&LondWritable); + $Watcher->poll("w"); } elsif ($State eq "ChallengeReplied") { @@ -595,20 +644,20 @@ sub LondReadable { # The ok was received. Now we need to request the key # That requires us to be writable: - $Watcher->poll("w"); $Watcher->cb(\&LondWritable); + $Watcher->poll("w"); } elsif ($State eq "ReceivingKey") { } elsif ($State eq "Idle") { # If necessary, complete a transaction and then go into the # idle queue. + $Watcher->cancel(); if(exists($ActiveTransactions{$Socket})) { Debug(8,"Completing transaction!!"); CompleteTransaction($Socket, $ActiveTransactions{$Socket}); } - $Watcher->cancel(); ServerToIdle($Socket); # Next work unit or idle. } elsif ($State eq "SendingRequest") { @@ -692,15 +741,15 @@ is the socket on which to return a reply sub LondWritable { my $Event = shift; my $Watcher = $Event->w; - my @data = $Watcher->data; - Debug(6,"LondWritable State = ".$State." data has ".@data." elts.\n"); + my $Socket = $Watcher->data; + my $State = $Socket->GetState(); - my $Socket = $data[0]; # I know there's at least a socket. + Debug(6,"LondWritable State = ".$State."\n"); + # Figure out what to do depending on the state of the socket: - my $State = $Socket->GetState(); SocketDump(6,$Socket); @@ -723,8 +772,8 @@ sub LondWritable { # Now that init was sent, we switch # to watching for readability: - $Watcher->poll("r"); $Watcher->cb(\&LondReadable); + $Watcher->poll("r"); } elsif ($State eq "ChallengeReceived") { # We received the challenge, now we @@ -742,8 +791,8 @@ sub LondWritable { # The echo was sent back, so we switch # to watching readability. - $Watcher->poll("r"); $Watcher->cb(\&LondReadable); + $Watcher->poll("r"); } elsif ($State eq "RequestingKey") { # At this time we're requesting the key. @@ -763,8 +812,8 @@ sub LondWritable { # Now we need to wait for the key # to come back from the peer: - $Watcher->poll("r"); $Watcher->cb(\&LondReadable); + $Watcher->poll("r"); } elsif ($State eq "SendingRequest") { # At this time we are sending a request to the @@ -786,8 +835,8 @@ sub LondWritable { # The send has completed. Wait for the # data to come in for a reply. Debug(8,"Writable sent request/receiving reply"); - $Watcher->poll("r"); $Watcher->cb(\&LondReadable); + $Watcher->poll("r"); } else { # Control only passes here on an error: @@ -803,18 +852,28 @@ sub LondWritable { =cut sub QueueDelayed { + Debug(3,"QueueDelayed called"); + my $path = "$perlvar{'lonSockDir'}/delayed"; + + Debug(4, "Delayed path: ".$path); opendir(DIRHANDLE, $path); + @alldelayed = grep /\.$RemoteHost$/, readdir DIRHANDLE; + Debug(4, "Got ".$alldelayed." delayed files"); closedir(DIRHANDLE); my $dfname; - my $reqfile - foreach $reqfile (sort @alldelayed) { - $reqfile = $path/$reqfile; + my $reqfile; + foreach $dfname (sort @alldelayed) { + $reqfile = "$path/$dfname"; + Debug(4, "queueing ".$reqfile); my $Handle = IO::File->new($reqfile); my $cmd = <$Handle>; - chomp($cmd); - QueueTransaction($NullSocket, $cmd); + chomp $cmd; # There may or may not be a newline... + $cmd = $cmd."\ny"; # now for sure there's exactly one newline. + my $Transaction = LondTransaction->new($cmd); + $Transaction->SetDeferred($reqfile); + QueueTransaction($Transaction); } } @@ -857,11 +916,12 @@ 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++; + Debug(4, "Connection count = ".$ConnectionCount); if($ConnectionCount == 1) { # First Connection: QueueDelayed; } @@ -896,17 +956,17 @@ 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); - $event = Event->io(fd => $Lond->GetSocket(), + $Lond->InitiateTransaction($Request->getRequest()); + $event = Event->io(fd => $Socket, poll => "w", cb => \&LondWritable, data => $Lond, @@ -937,15 +997,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"); @@ -953,7 +1013,7 @@ sub QueueTransaction { } } else { # Can start the request: Debug(8,"Can start..."); - StartRequest($LondSocket, $requestSocket, $requestData); + StartRequest($LondSocket, $requestData); } } @@ -993,7 +1053,9 @@ sub ClientRequest { $watcher->data($data); if($data =~ /(.*\n)/) { # Request entirely read. Debug(8, "Complete transaction received: ".$data); - QueueTransaction($socket, $data); + my $Transaction = LondTransaction->new($data); + $Transaction->SetClient($socket); + QueueTransaction($Transaction); $watcher->cancel(); # Done looking for input data. } @@ -1055,7 +1117,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. } @@ -1067,7 +1129,7 @@ Returns the lond port number. =cut -sub GetServerPort { # Stub - get this from config. +sub GetServerPort { return $perlvar{londPort}; } @@ -1088,7 +1150,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"; @@ -1177,11 +1239,7 @@ 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; @@ -1189,6 +1247,15 @@ if ($childpid != 0) { sleep 4; # Give child a chacne to break to exit 0; # a new sesion. } +# +# Write my pid into the pid file so I can be located +# + +ShowStatus("Parent writing pid file:"); +$execdir = $perlvar{'lonDaemons'}; +open (PIDSAVE, ">$execdir/logs/lonc.pid"); +print PIDSAVE "$$\n"; +close(PIDSAVE); if (POSIX::setsid() < 0) { print "Could not create new session\n";