File:  [LON-CAPA] / loncom / loncnew
Revision 1.2: download - view: text, annotated - select for diffs
Fri Apr 18 02:59:31 2003 UTC (21 years ago) by albertel
Branches: MAIN
CVS tags: HEAD
- add GNU GPL headers

    1: #!/usr/bin/perl
    2: # The LearningOnline Network with CAPA
    3: # lonc maintains the connections to remote computers
    4: #
    5: # $Id: loncnew,v 1.2 2003/04/18 02:59:31 albertel Exp $
    6: #
    7: # Copyright Michigan State University Board of Trustees
    8: #
    9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   10: #
   11: # LON-CAPA is free software; you can redistribute it and/or modify
   12: # it under the terms of the GNU General Public License as published by
   13: # the Free Software Foundation; either version 2 of the License, or
   14: # (at your option) any later version.
   15: #
   16: # LON-CAPA is distributed in the hope that it will be useful,
   17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   19: # GNU General Public License for more details.
   20: #
   21: # You should have received a copy of the GNU General Public License
   22: # along with LON-CAPA; if not, write to the Free Software
   23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   24: #
   25: # /home/httpd/html/adm/gpl.txt
   26: #
   27: # http://www.lon-capa.org/
   28: #
   29: #
   30: # new lonc handles n requestors spread out bver m connections to londs.
   31: # This module is based on the Event class.
   32: #   Development iterations:
   33: #    - Setup basic event loop.   (done)
   34: #    - Add timer dispatch.       (done)
   35: #    - Add ability to accept lonc UNIX domain sockets.  (done)
   36: #    - Add ability to create/negotiate lond connections (done).
   37: #    - Add general logic for dispatching requests and timeouts.
   38: #    - Add support for the lonc/lond requests.
   39: #    - Add logging/status monitoring.
   40: #    - Add Signal handling - HUP restarts. USR1 status report.
   41: #    - Add Configuration file I/O
   42: #    - Add Pending request processing on startup.
   43: #    - Add management/status request interface.
   44: 
   45: use lib "/home/httpd/lib/perl/";
   46: use lib "/home/foxr/newloncapa/types";
   47: use Event qw(:DEFAULT );
   48: use POSIX qw(:signal_h);
   49: use IO::Socket;
   50: use IO::Socket::INET;
   51: use IO::Socket::UNIX;
   52: use Socket;
   53: use Crypt::IDEA;
   54: use LONCAPA::Queue;
   55: use LONCAPA::Stack;
   56: use LONCAPA::LondConnection;
   57: use LONCAPA::Configuration;
   58: use LONCAPA::HashIterator;
   59: 
   60: print "Loncnew starting\n";
   61: 
   62: #
   63: #   Disable all signals we might receive from outside for now.
   64: #
   65: $SIG{QUIT}  = IGNORE;
   66: $SIG{HUP}   = IGNORE;
   67: $SIG{USR1}  = IGNORE;
   68: $SIG{INT}   = IGNORE;
   69: $SIG{CHLD}  = IGNORE;
   70: $SIG{__DIE__}  = IGNORE;
   71: 
   72: 
   73: # Read the httpd configuration file to get perl variables
   74: # normally set in apache modules:
   75: 
   76: my $perlvarref = LONCAPA::Configuration::read_conf('loncapa.conf');
   77: my %perlvar    = %{$perlvarref};
   78: 
   79: #
   80: #  parent and shared variables.
   81: 
   82: my %ChildHash;			# by pid -> host.
   83: 
   84: 
   85: my $MaxConnectionCount = 5;	# Will get from config later.
   86: my $ClientConnection = 0;	# Uniquifier for client events.
   87: 
   88: my $DebugLevel = 5;
   89: my $IdleTimeout= 3600;		# Wait an hour before pruning connections.
   90: 
   91: #
   92: #  The variables below are only used by the child processes.
   93: #
   94: my $RemoteHost;			# Name of host child is talking to.
   95: my $UnixSocketDir= "/home/httpd/sockets"; 
   96: my $IdleConnections = Stack->new(); # Set of idle connections
   97: my %ActiveConnections;		# Connections to the remote lond.
   98: my %ActiveTransactions;		# Transactions in flight.
   99: my %ActiveClients;		# Serial numbers of active clients by socket.
  100: my $WorkQueue       = Queue->new(); # Queue of pending transactions.
  101: my $ClientQueue     = Queue->new(); # Queue of clients causing xactinos.
  102: my $ConnectionCount = 0;
  103: 
  104: 
  105: #
  106: =pod
  107: =head 2 GetPeerName
  108:    Returns the name of the host that a socket object is connected
  109:    to.
  110: =cut
  111: 
  112: sub GetPeername {
  113:     my $connection = shift;
  114:     my $AdrFamily  = shift;
  115:     my $peer       = $connection->peername();
  116:     my $peerport;
  117:     my $peerip;
  118:     if($AdrFamily == AF_INET) {
  119: 	($peerport, $peerip) = sockaddr_in($peer);
  120: 	my $peername    = gethostbyaddr($iaddr, $AdrFamily);
  121: 	return $peername;
  122:     } elsif ($AdrFamily == AF_UNIX) {
  123: 	my $peerfile;
  124: 	($peerfile) = sockaddr_un($peer);
  125: 	return $peerfile;
  126:     }
  127: }
  128: #----------------------------- Timer management ------------------------
  129: =pod
  130: =head2 Debug
  131:    Invoked to issue a debug message.
  132: =cut
  133: sub Debug {
  134:     my $level   = shift;
  135:     my $message = shift;
  136:     if ($level <= $DebugLevel) {
  137: 	print $message." host = ".$RemoteHost."\n";
  138:     }
  139: }
  140: 
  141: sub SocketDump {
  142:     my $level = shift;
  143:     my $socket= shift;
  144:     if($level <= $DebugLevel) {
  145: 	$socket->Dump();
  146:     }
  147: }
  148: =pod
  149: =head2 Tick
  150:     Invoked  each timer tick.
  151: =cut
  152: 
  153: sub Tick {
  154:     my $client;
  155:     Debug(6, "Tick");
  156:     Debug(6, "    Current connection count: ".$ConnectionCount);
  157:     foreach $client (keys %ActiveClients) {
  158: 	Debug(7, "    Have client:  with id: ".$ActiveClients{$client});
  159:     }
  160: }
  161: 
  162: =pod
  163: =head2 SetupTimer
  164:    Sets up a 1 per sec recurring timer event.  The event handler is used to:
  165: 
  166: =item Trigger timeouts on communications along active sockets.
  167: =item Trigger disconnections of idle sockets.
  168: 
  169: 
  170: =cut
  171: 
  172: sub SetupTimer {
  173:     Debug(6, "SetupTimer");
  174:     Event->timer(interval => 1, debug => 1, cb => \&Tick );
  175: }
  176: =pod
  177: =head2 ServerToIdle
  178:    This function is called when a connection to the server is
  179: ready for more work. 
  180:    If there is work in the Work queue the top element is dequeued
  181: and the connection will start to work on it.  If the work queue is
  182: empty, the connection is pushed on the idle connection stack where
  183: it will either get another work unit, or alternatively, if it sits there
  184: long enough, it will be shut down and released.
  185: 
  186: 
  187: =cut
  188: sub ServerToIdle {
  189:     my $Socket   = shift;	# Get the socket.
  190: 
  191:     &Debug(6, "Server to idle");
  192: 
  193:     #  If there's work to do, start the transaction:
  194: 
  195:     $reqdata = $WorkQueue->dequeue();
  196:     Debug(9, "Queue gave request data: ".$reqdata);
  197:     unless($reqdata eq undef)  {
  198: 	my $unixSocket = $ClientQueue->dequeue();
  199: 	&Debug(6, "Starting new work request");
  200: 	&Debug(7, "Request: ".$reqdata);
  201: 	
  202: 	&StartRequest($Socket, $unixSocket, $reqdata);
  203:     } else {
  204: 	
  205:     #  There's no work waiting, so push the server to idle list.
  206: 	&Debug(8, "No new work requests, server connection going idle");
  207: 	delete($ActiveTransactions{$Socket});
  208: 	$IdleConnections->push($Socket);
  209:     }
  210: }
  211: =pod
  212: =head2 ClientWritable
  213:   Event callback for when a client socket is writable.
  214:   This callback is established when a transaction reponse is
  215:   avaiable from lond.  The response is forwarded to the unix socket
  216:   as it becomes writable in this sub.
  217: Parameters:
  218: 
  219: =item Event - The event that has been triggered. Event->w->data is
  220:               the data and Event->w->fd is the socket to write.
  221: 
  222: =cut
  223: sub ClientWritable {
  224:     my $Event    = shift;
  225:     my $Watcher  = $Event->w;
  226:     my $Data     = $Watcher->data;
  227:     my $Socket   = $Watcher->fd;
  228: 
  229:     # Try to send the data:
  230: 
  231:     &Debug(6, "ClientWritable writing".$Data);
  232:     &Debug(9, "Socket is: ".$Socket);
  233: 
  234:     my $result = $Socket->send($Data, 0);
  235: 
  236:     # $result undefined: the write failed.
  237:     # otherwise $result is the number of bytes written.
  238:     # Remove that preceding string from the data.
  239:     # If the resulting data is empty, destroy the watcher
  240:     # and set up a read event handler to accept the next
  241:     # request.
  242: 
  243:     &Debug(9,"Send result is ".$result." Defined: ".defined($result));
  244:     if(defined($result)) {
  245: 	&Debug(9, "send result was defined");
  246: 	if($result == length($Data)) { # Entire string sent.
  247: 	    &Debug(9, "ClientWritable data all written");
  248: 	    $Watcher->cancel();
  249: 	    #
  250: 	    #  Set up to read next request from socket:
  251: 	    
  252: 	    my $descr     = sprintf("Connection to lonc client %d",
  253: 				    $ActiveClients{$Socket});
  254: 	    Event->io(cb    => \&ClientRequest,
  255: 		      poll  => 'r',
  256: 		      desc  => $descr,
  257: 		      data  => "",
  258: 		      fd    => $Socket);
  259: 
  260: 	} else {		# Partial string sent.
  261: 	    $Watcher->data(substr($Data, $result));
  262: 	}
  263: 	
  264:     } else {			# Error of some sort...
  265: 
  266: 	# Some errnos are possible:
  267: 	my $errno = $!;
  268: 	if($errno == POSIX::EWOULDBLOCK   ||
  269: 	   $errno == POSIX::EAGAIN        ||
  270: 	   $errno == POSIX::EINTR) {
  271: 	    # No action taken?
  272: 	} else {		# Unanticipated errno.
  273: 	    &Debug(5,"ClientWritable error or peer shutdown".$RemoteHost);
  274: 	    $Watcher->cancel;	# Stop the watcher.
  275: 	    $Socket->shutdown(2); # Kill connection
  276: 	    $Socket->close();	# Close the socket.
  277: 	}
  278: 	
  279:     }
  280: }
  281: 
  282: =pod
  283: =head2 CompleteTransaction
  284:   Called when the reply data has been received for a lond 
  285: transaction.   The reply data must now be sent to the
  286: ultimate client on the other end of the Unix socket.  This is
  287: done by setting up a writable event for the socket with the
  288: data the reply data.
  289: Parameters:
  290: =item Socket - Socket on which the lond transaction occured.  This
  291:       is a LondConnection. The data received is in the 
  292: 	TransactionReply member.
  293: =item Client - Unix domain socket open on the ultimate client.
  294: 
  295: =cut
  296: sub CompleteTransaction {
  297:     &Debug(6,"Complete transaction");
  298:     my $Socket = shift;
  299:     my $Client = shift;
  300: 
  301:     my $data   = $Socket->GetReply(); # Data to send.
  302: 
  303:     &Debug(8," Reply was: ".$data);
  304:     my $Serial         = $ActiveClients{$Client};
  305:     my $desc           = sprintf("Connection to lonc client %d",
  306: 				 $Serial);
  307:     Event->io(fd       => $Client,
  308: 	      poll     => "w",
  309: 	      desc     => $desc,
  310: 	      cb       => \&ClientWritable,
  311: 	      data     => $data);
  312: }
  313: 
  314: 
  315: =pod
  316: =head2 LondReadable
  317: This function is called whenever a lond connection
  318: is readable.  The action is state dependent:
  319: 
  320: =head3 State = Initialized
  321:     We''re waiting for the challenge, this is a no-op until the
  322: state changes.
  323: =head3 State=Challenged 
  324:     The challenge has arrived we need to transition to Writable.
  325: The connection must echo the challenge back.
  326: =head3 State=ChallengeReplied
  327:     The challenge has been replied to.  The we are receiveing the 
  328: 'ok' from the partner.
  329: =head3 State=RequestingKey
  330:     The ok has been received and we need to send the request for
  331: an encryption key.  Transition to writable for that.
  332: =head3 State=ReceivingKey
  333:     The the key has been requested, now we are reading the new key.
  334: =head3 State=Idle 
  335:     The encryption key has been negotiated or we have finished 
  336: reading data from the a transaction.   If the callback data has
  337: a client as well as the socket iformation, then we are 
  338: doing a transaction and the data received is relayed to the client
  339: before the socket is put on the idle list.
  340: =head3 State=SendingRequest
  341:     I do not think this state can be received here, but if it is,
  342: the appropriate thing to do is to transition to writable, and send
  343: the request.
  344: =head3 State=ReceivingReply
  345:     We finished sending the request to the server and now transition
  346: to readable to receive the reply. 
  347: 
  348: The parameter to this function are:
  349: The event. Implicit in this is the watcher and its data.  The data 
  350: contains at least the lond connection object and, if a 
  351: transaction is in progress, the socket attached to the local client.
  352: 
  353: 
  354: =cut
  355: sub LondReadable {
  356:     my $Event      = shift;
  357:     my $Watcher    = $Event->w;
  358:     my $Socket     = $Watcher->data;
  359:     my $client     = undef;
  360: 
  361: 
  362:     my $State = $Socket->GetState(); # All action depends on the state.
  363: 
  364:     &Debug(6,"LondReadable called state = ".$State);
  365:     SocketDump(6, $Socket);
  366: 
  367:     if($Socket->Readable() != 0) {
  368: 	 # bad return from socket read.
  369:     }
  370:     SocketDump(6,$Socket);
  371: 
  372:     $State = $Socket->GetState(); # Update in case of transition.
  373:     &Debug(6, "After read, state is ".$State);
  374: 
  375:    if($State eq "Initialized") {
  376: 
  377: 
  378:     } elsif ($State eq "ChallengeReceived") {
  379: 	#  The challenge must be echoed back;  The state machine
  380: 	# in the connection takes care of setting that up.  Just
  381: 	# need to transition to writable:
  382: 
  383: 	$Watcher->poll("w");
  384: 	$Watcher->cb(\&LondWritable);
  385: 
  386:     } elsif ($State eq "ChallengeReplied") {
  387: 
  388: 
  389:     } elsif ($State eq "RequestingKey") {
  390: 	#  The ok was received.  Now we need to request the key
  391: 	#  That requires us to be writable:
  392: 
  393: 	$Watcher->poll("w");
  394: 	$Watcher->cb(\&LondWritable);
  395: 
  396:     } elsif ($State eq "ReceivingKey") {
  397: 
  398:     } elsif ($State eq "Idle") {
  399: 	# If necessary, complete a transaction and then go into the
  400: 	# idle queue.
  401: 	if(exists($ActiveTransactions{$Socket})) {
  402: 	    Debug(8,"Completing transaction!!");
  403: 	    CompleteTransaction($Socket, 
  404: 				$ActiveTransactions{$Socket});
  405: 	}
  406: 	$Watcher->cancel();
  407: 	ServerToIdle($Socket);	# Next work unit or idle.
  408: 
  409:     } elsif ($State eq "SendingRequest") {
  410: 	#  We need to be writable for this and probably don't belong
  411: 	#  here inthe first place.
  412: 
  413: 	Deubg(6, "SendingRequest state encountered in readable");
  414: 	$Watcher->poll("w");
  415: 	$Watcher->cb(\&LondWritable);
  416: 
  417:     } elsif ($State eq "ReceivingReply") {
  418: 
  419: 
  420:     } else {
  421: 	 # Invalid state.
  422: 	Debug(4, "Invalid state in LondReadable");
  423:     }
  424: }
  425: =pod
  426: =head2 LondWritable
  427: This function is called whenever a lond connection
  428: becomes writable while there is a writeable monitoring
  429: event.  The action taken is very state dependent:
  430: =head3 State = Connected 
  431:   The connection is in the process of sending the
  432:   'init' hailing to the lond on the remote end.
  433:    The connection object''s Writable member is called.
  434:    On error, ConnectionError is called to destroy
  435:    the connection and remove it from the ActiveConnections
  436:    hash
  437: =head3 Initialized
  438:    'init' has been sent, writability monitoring is 
  439:     removed and readability monitoring is started
  440:     with LondReadable as the callback.
  441: =head3 ChallengeReceived
  442:     The connection has received the who are you 
  443:    challenge from the remote system, and is in the
  444:     process of sending the challenge response. 
  445:     Writable is called.
  446: =head3 ChallengeReplied
  447:     The connection has replied to the initial challenge
  448:     The we switch to monitoring readability looking
  449:     for the server to reply with 'ok'.
  450: =head3 RequestingKey
  451:     The connection is in the process of requesting its
  452:     encryption key.  Writable is called.
  453: =head3 ReceivingKey
  454:     The connection has sent the request for a key.
  455:     Switch to readability monitoring to accept the key
  456: =head3 SendingRequest
  457:     The connection is in the process of sending a
  458:     request to the server.  This request is part of
  459:     a client transaction.  All the states until now
  460:     represent the client setup protocol. Writable
  461:     is called.
  462: =head3 ReceivingReply
  463:     The connection has sent a request.  Now it must
  464:     receive a reply.  Readability monitoring is
  465:     requested.
  466: 
  467:  This function is an event handler and therefore receives as
  468: a parameter the event that has fired.  The data for the watcher
  469: of this event is a reference to a list of one or two elements,
  470: depending on state. The first (and possibly only) element is the
  471: socket.  The second (present only if a request is in progress)
  472: is the socket on which to return a reply to the caller.
  473: 
  474: =cut
  475: sub LondWritable {
  476:     my $Event   = shift;
  477:     my $Watcher = $Event->w;
  478:     my @data    = $Watcher->data;
  479:     Debug(6,"LondWritable State = ".$State." data has ".@data." elts.\n");
  480: 
  481:     my $Socket  = $data[0];	# I know there's at least a socket.
  482: 
  483:     #  Figure out what to do depending on the state of the socket:
  484:     
  485: 
  486:     my $State   = $Socket->GetState();
  487: 
  488: 
  489:     SocketDump(6,$Socket);
  490: 
  491:     if      ($State eq "Connected")         {
  492: 	#  "init" is being sent...
  493: 
  494: 	if ($Socket->Writable() != 0) {
  495: 	    #  The write resulted in an error.
  496: 	}
  497: 	
  498:     } elsif ($State eq "Initialized")       {
  499: 
  500: 	# Now that init was sent, we switch 
  501: 	# to watching for readability:
  502: 
  503: 	$Watcher->poll("r");
  504: 	$Watcher->cb(\&LondReadable);
  505: 
  506:     } elsif ($State eq "ChallengeReceived") {
  507: 	# We received the challenge, now we 
  508: 	# are echoing it back. This is a no-op,
  509: 	# we're waiting for the state to change
  510: 	
  511: 	if($Socket->Writable() != 0) {
  512: 	    # Write of the next chunk resulted in an error.
  513: 	}
  514: 	
  515:     } elsif ($State eq "ChallengeReplied")  {
  516: 	# The echo was sent back, so we switch
  517: 	# to watching readability.
  518: 
  519: 	$Watcher->poll("r");
  520: 	$Watcher->cb(\&LondReadable);
  521: 
  522:     } elsif ($State eq "RequestingKey")     {
  523: 	# At this time we're requesting the key.
  524: 	# again, this is essentially a no-op.
  525: 	# we'll write the next chunk until the
  526: 	# state changes.
  527: 
  528: 	if($Socket->Writable() != 0) {
  529: 	    # Write resulted in an error.
  530: 	}
  531: 
  532:     } elsif ($State eq "ReceivingKey")      {
  533: 	# Now we need to wait for the key
  534: 	# to come back from the peer:
  535: 
  536: 	$Watcher->poll("r");
  537: 	$Watcher->cb(\&LondReadable);
  538: 
  539:     } elsif ($State eq "SendingRequest")    {
  540: 	# At this time we are sending a request to the
  541: 	# peer... write the next chunk:
  542: 
  543: 	if($Socket->Writable() != 0) {
  544: 	    # Write resulted in an error.
  545: 
  546: 	}
  547: 
  548:     } elsif ($State eq "ReceivingReply")    {
  549: 	# The send has completed.  Wait for the
  550: 	# data to come in for a reply.
  551: 	Debug(8,"Writable sent request/receiving reply");
  552: 	$Watcher->poll("r");
  553: 	$Watcher->cb(\&LondReadable);
  554: 
  555:     } else {
  556: 	#  Control only passes here on an error: 
  557: 	#  the socket state does not match any
  558: 	#  of the known states... so an error
  559: 	#  must be logged.
  560: 
  561: 	&Debug(4, "Invalid socket state ".$State."\n");
  562:     }
  563:     
  564: }
  565: 
  566: =pod
  567: =head2 MakeLondConnection
  568:     Create a new lond connection object, and start it towards
  569:     its initial idleness.  Once idle, it becomes elligible to
  570:     receive transactions from the work queue.  If the work queue
  571:     is not empty when the connection is completed and becomes idle,
  572:     it will dequeue an entry and start off on it.
  573: =cut
  574: sub MakeLondConnection {     
  575:     Debug(4,"MakeLondConnection to ".GetServerHost()." on port "
  576: 	  .GetServerPort());
  577: 
  578:     my $Connection = LondConnection->new(&GetServerHost(),
  579: 					 &GetServerPort());
  580: 
  581:     if($Connection == undef) {	# Needs to be more robust later.
  582: 	die "Failed to make a connection!!".$!."\n";
  583: 	
  584:     } 
  585:     # The connection needs to have writability 
  586:     # monitored in order to send the init sequence
  587:     # that starts the whole authentication/key
  588:     # exchange underway.
  589:     #
  590:     my $Socket = $Connection->GetSocket();
  591:     if($Socket == undef) {
  592: 	die "did not get a socket from the connection";
  593:     } else {
  594: 	&Debug(9,"MakeLondConnection got socket: ".$Socket);
  595:     }
  596: 
  597:     
  598:     $event = Event->io(fd       => $Socket,
  599: 		       poll     => 'w',
  600: 		       cb       => \&LondWritable,
  601: 		       data     => ($Connection, undef),
  602: 		       desc => 'Connection to lond server');
  603:     $ActiveConnections{$Lond} = $event;
  604: 
  605:     $ConnectionCount++;
  606:    
  607:     
  608: }
  609: =pod
  610: =head2 StartRequest
  611:    Starts a lond request going on a specified lond connection.
  612:     parameters are:
  613: =item $Lond - Connection to the lond that will send the transaction
  614:     and receive the reply.
  615: =item $Client - Connection to the client that is making this request
  616:     We got the request from this socket, and when the request has
  617:     been relayed to lond and we get a reply back from lond it will
  618:     get sent to this socket.
  619: =item $Request - The text of the request to send.
  620: =cut
  621: 
  622: sub StartRequest {
  623:     my $Lond     = shift;
  624:     my $Client   = shift;
  625:     my $Request  = shift;
  626:     
  627:     Debug(6, "StartRequest: ".$Request);
  628: 
  629:     my $Socket = $Lond->GetSocket();
  630:     
  631:     $ActiveTransactions{$Lond} = $Client; # Socket to relay to client.
  632: 
  633:     $Lond->InitiateTransaction($Request);
  634:     $event = Event->io(fd      => $Lond->GetSocket(),
  635: 		       poll    => "w",
  636: 		       cb      => \&LondWritable,
  637: 		       data    => $Lond,
  638: 		       desc    => "lond transaction connection");
  639:     $ActiveConnections{$Lond} = $event;
  640:     Debug(8," Start Request made watcher data with ".$event->data."\n");
  641: }
  642: 
  643: =pod
  644: =head2 QueueTransaction
  645:     - If there is an idle lond connection, it is put to 
  646:     work doing this transaction.  Otherwise, the transaction is
  647:     placed in the work queue.  If placed in the work queue and the
  648:     maximum number of connections has not yet been created, a new
  649:     connection will be started.  Our goal is to eventually have 
  650:     a sufficient number of connections that the work queue will
  651:     typically be empty.
  652:     parameters are:
  653: =item Socket open on the lonc client.
  654: =item Request data to send to the lond.
  655: 
  656: =cut
  657: sub QueueTransaction {
  658:     my $requestSocket = shift;
  659:     my $requestData   = shift;
  660: 
  661:     Debug(6,"QueueTransaction: ".$requestData);
  662: 
  663:     my $LondSocket    = $IdleConnections->pop();
  664:     if(!defined $LondSocket) {	# Need to queue request.
  665: 	Debug(8,"Must queue...");
  666: 	$ClientQueue->enqueue($requestSocket);
  667: 	$WorkQueue->enqueue($requestData);
  668: 	if($ConnectionCount < $MaxConnectionCount) {
  669: 	    Debug(4,"Starting additional lond connection");
  670: 	    MakeLondConnection();
  671: 	}
  672:     } else {			# Can start the request:
  673: 	Debug(8,"Can start...");
  674: 	StartRequest($LondSocket, $requestSocket, $requestData);
  675:     }
  676: }
  677: 
  678: #-------------------------- Lonc UNIX socket handling ---------------------
  679: =pod
  680: =head2 ClientRequest
  681:    Callback that is called when data can be read from the 
  682:    UNIX domain socket connecting us with an apache server process.
  683: 
  684: =cut
  685: 
  686: sub ClientRequest {
  687:     Debug(6, "ClientRequest");
  688:     my $event   = shift;
  689:     my $watcher = $event->w;
  690:     my $socket  = $watcher->fd;
  691:     my $data    = $watcher->data;
  692:     my $thisread;
  693: 
  694:     Debug(9, "  Watcher named: ".$watcher->desc);
  695: 
  696:     my $rv = $socket->recv($thisread, POSIX::BUFSIZ, 0);
  697:     Debug(8, "rcv:  data length = ".length($thisread)
  698: 	  ." read =".$thisread);
  699:     unless (defined $rv && length($thisread)) {
  700: 	 # Likely eof on socket.
  701: 	Debug(5,"Client Socket closed on lonc for ".$RemoteHost);
  702: 	close($socket);
  703: 	$watcher->cancel();
  704: 	delete($ActiveClients{$socket});
  705:     }
  706:     Debug(8,"Data: ".$data." this read: ".$thisread);
  707:     $data = $data.$thisread;	# Append new data.
  708:     $watcher->data($data);
  709:     if($data =~ /(.*\n)/) {	# Request entirely read.
  710: 	Debug(8, "Complete transaction received: ".$data);
  711: 	QueueTransaction($socket, $data);
  712: 	$watcher->cancel();	# Done looking for input data.
  713:     }
  714: 
  715: }
  716: 
  717: 
  718: =pod
  719: =head2  NewClient
  720:     Callback that is called when a connection is received on the
  721:     unix socket for a new client of lonc.  The callback is parameterized
  722:     by the event.. which is a-priori assumed to be an  io event, and therefore
  723:     has an fd member that is the Listener socket.   We Accept the connection
  724:     and register a new event on the readability of that socket:
  725: =cut
  726: sub NewClient {
  727:     Debug(6, "NewClient");
  728:     my $event      = shift;		# Get the event parameters.
  729:     my $watcher    = $event->w; 
  730:     my $socket     = $watcher->fd;	# Get the event' socket.
  731:     my $connection = $socket->accept();	# Accept the client connection.
  732:     Debug(8,"Connection request accepted from "
  733: 	  .GetPeername($connection, AF_UNIX));
  734: 
  735: 
  736:     my $description = sprintf("Connection to lonc client %d",
  737: 			      $ClientConnection);
  738:     Debug(9, "Creating event named: ".$description);
  739:     Event->io(cb      => \&ClientRequest,
  740: 	      poll    => 'r',
  741: 	      desc    => $description,
  742: 	      data    => "",
  743: 	      fd      => $connection);
  744:     $ActiveClients{$connection} = $ClientConnection;
  745:     $ClientConnection++;
  746: }
  747: =pod GetLoncSocketPath
  748:     Returns the name of the UNIX socket on which to listen for client
  749:     connections.
  750: 
  751: =cut
  752: sub GetLoncSocketPath {
  753:     return $UnixSocketDir."/".GetServerHost();
  754: }
  755: 
  756: =pod GetServerHost 
  757:    Returns the host whose lond we talk with.
  758: =cut
  759: sub GetServerHost {		# Stub - get this from config.
  760:     return $RemoteHost;		# Setup by the fork.
  761: }
  762: =pod GetServerPort
  763:    Returns the lond port number.
  764: =cut
  765: sub GetServerPort {		# Stub - get this from config.
  766:     return $perlvar{londPort};
  767: }
  768: =pod SetupLoncListener
  769:    Setup a lonc listener event.  The event is called when
  770:    the socket becomes readable.. that corresponds to the 
  771:    receipt of a new connection.  The event handler established
  772:    will accept the connection (creating a communcations channel), that
  773:    int turn will establish another event handler to subess requests.
  774: 
  775: =cut
  776: sub SetupLoncListener {
  777: 
  778:     my $socket;
  779:     my $SocketName = GetLoncSocketPath();
  780:     unlink($SocketName);
  781:     unless ($socket = IO::Socket::UNIX->new(Local  => $SocketName,
  782: 					    Listen => 10, 
  783: 					    Type   => SOCK_STREAM)) {
  784: 	die "Failed to create a lonc listner socket";
  785:     }
  786:     Event->io(cb     => \&NewClient,
  787: 	      poll   => 'r',
  788: 	      desc   => 'Lonc listener Unix Socket',
  789: 	      fd     => $socket);
  790: }
  791: 
  792: =pod
  793: =head2 ChildProcess
  794: 
  795: This sub implements a child process for a single lonc daemon.
  796: 
  797: =cut
  798: 
  799: sub ChildProcess {
  800: 
  801:     print "Loncnew\n";
  802: 
  803:     # For now turn off signals.
  804:     
  805:     $SIG{QUIT}  = IGNORE;
  806:     $SIG{HUP}   = IGNORE;
  807:     $SIG{USR1}  = IGNORE;
  808:     $SIG{INT}   = IGNORE;
  809:     $SIG{CHLD}  = IGNORE;
  810:     $SIG{__DIE__}  = IGNORE;
  811: 
  812:     SetupTimer();
  813:     
  814:     SetupLoncListener();
  815:     
  816:     $Event::Debuglevel = $DebugLevel;
  817:     
  818:     Debug(9, "Making initial lond connection for ".$RemoteHost);
  819: 
  820: # Setup the initial server connection:
  821:     
  822:     &MakeLondConnection();
  823:     
  824:     Debug(9,"Entering event loop");
  825:     my $ret = Event::loop();		#  Start the main event loop.
  826:     
  827:     
  828:     die "Main event loop exited!!!";
  829: }
  830: 
  831: #  Create a new child for host passed in:
  832: 
  833: sub CreateChild {
  834:     my $host = shift;
  835:     $RemoteHost = $host;
  836:     Debug(3, "Forking off child for ".$RemoteHost);
  837:     sleep(5);
  838:     $pid          = fork;
  839:     if($pid) {			# Parent
  840: 	$ChildHash{$pid} = $RemoteHost;
  841:     } else {			# child.
  842: 	ChildProcess;
  843:     }
  844: 
  845: }
  846: #
  847: #  Parent process logic pass 1:
  848: #   For each entry in the hosts table, we will
  849: #  fork off an instance of ChildProcess to service the transactions
  850: #  to that host.  Each pid will be entered in a global hash
  851: #  with the value of the key, the host.
  852: #  The parent will then enter a loop to wait for process exits.
  853: #  Each exit gets logged and the child gets restarted.
  854: #
  855: 
  856: my $HostIterator = LondConnection::GetHostIterator;
  857: while (! $HostIterator->end()) {
  858: 
  859:     $hostentryref = $HostIterator->get();
  860:     CreateChild($hostentryref->[0]);
  861:     $HostIterator->next();
  862: }
  863: 
  864: # Maintain the population:
  865: 
  866: while(1) {
  867:     $deadchild = wait();
  868:     if(exists $ChildHash{$deadchild}) {	# need to restart.
  869: 	$deadhost = $ChildHash{$deadchild};
  870: 	delete($ChildHash{$deadchild});
  871: 	Debug(4,"Lost child pid= ".$deadchild.
  872: 	      "Connected to host ".$deadhost);
  873: 	CreateChild($deadhost);
  874:     }
  875: }
  876: 
  877: =head1 Theory
  878:    The event class is used to build this as a single process with
  879:    an event driven model.  The following events are handled:
  880: 
  881: =item UNIX Socket connection Received
  882: 
  883: =item Request data arrives on UNIX data transfer socket.
  884: 
  885: =item lond connection becomes writable.
  886: 
  887: =item timer fires at 1 second intervals.
  888: 
  889: All sockets are run in non-blocking mode.  Timeouts managed by the timer
  890: handler prevents hung connections.
  891: 
  892: Key data structures:
  893: =item RequestQueue - A queue of requests received from UNIX sockets that are
  894:     waiting for a chance to be forwarded on a lond connection socket.
  895: 
  896: =item ActiveConnections - A hash of lond connections that have transactions
  897:     in process that are available to be timed out.
  898: 
  899: =item ActiveTransactions - A hash indexed by lond connections that
  900:     contain the client reply socket for each connection that has an active
  901:     transaction on it.
  902: 
  903: =item IdleConnections - A hash of lond connections that have no work
  904:     to do.  These connections can be closed if they are idle for a long
  905:     enough time.
  906: =cut

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>