File:  [LON-CAPA] / loncom / loncnew
Revision 1.4: download - view: text, annotated - select for diffs
Thu Apr 24 10:56:55 2003 UTC (21 years ago) by foxr
Branches: MAIN
CVS tags: HEAD
Added code to roll back connection counts.  Also added code to show
status of daemon on ps -axuww: show who 'I'm connected to and current connection
count

    1: #!/usr/bin/perl
    2: # The LearningOnline Network with CAPA
    3: # lonc maintains the connections to remote computers
    4: #
    5: # $Id: loncnew,v 1.4 2003/04/24 10:56:55 foxr 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: my $IdleSeconds     = 0;	# Number of seconds idle.
  104: 
  105: #
  106: 
  107: =pod
  108: 
  109: =head2 GetPeerName
  110: 
  111: Returns the name of the host that a socket object is connected to.
  112: 
  113: =cut
  114: 
  115: sub GetPeername {
  116:     my $connection = shift;
  117:     my $AdrFamily  = shift;
  118:     my $peer       = $connection->peername();
  119:     my $peerport;
  120:     my $peerip;
  121:     if($AdrFamily == AF_INET) {
  122: 	($peerport, $peerip) = sockaddr_in($peer);
  123: 	my $peername    = gethostbyaddr($iaddr, $AdrFamily);
  124: 	return $peername;
  125:     } elsif ($AdrFamily == AF_UNIX) {
  126: 	my $peerfile;
  127: 	($peerfile) = sockaddr_un($peer);
  128: 	return $peerfile;
  129:     }
  130: }
  131: #----------------------------- Timer management ------------------------
  132: =pod
  133: 
  134: =head2 Debug
  135: 
  136: Invoked to issue a debug message.
  137: 
  138: =cut
  139: 
  140: sub Debug {
  141:     my $level   = shift;
  142:     my $message = shift;
  143:     if ($level <= $DebugLevel) {
  144: 	print $message." host = ".$RemoteHost."\n";
  145:     }
  146: }
  147: 
  148: sub SocketDump {
  149:     my $level = shift;
  150:     my $socket= shift;
  151:     if($level <= $DebugLevel) {
  152: 	$socket->Dump();
  153:     }
  154: }
  155: 
  156: =pod
  157: 
  158: =head2 Tick
  159: 
  160: Invoked  each timer tick.
  161: 
  162: =cut
  163: 
  164: sub Tick {
  165:     my $client;
  166:     $0 = 'lonc: '.GetServerHost()." Connection count: ".$ConnectionCount;
  167:     Debug(6, "Tick");
  168:     Debug(6, "    Current connection count: ".$ConnectionCount);
  169:     foreach $client (keys %ActiveClients) {
  170: 	Debug(7, "    Have client:  with id: ".$ActiveClients{$client});
  171:     }
  172:     # Is it time to prune connection count:
  173: 
  174: 
  175:     if($IdleConnections->Count()  && 
  176:        ($WorkQueue->Count() == 0)) { # Idle connections and nothing to do?
  177: 	$IdleSeconds++;
  178: 	if($IdleSeconds > $IdleTimeout) { # Prune a connection...
  179: 	    $Socket = $IdleConnections->pop();
  180: 	    KillSocket($Socket, 0);
  181: 	}
  182:     } else {
  183: 	$IdleSeconds = 0;	# Reset idle count if not idle.
  184:     }
  185: }
  186: 
  187: =pod
  188: 
  189: =head2 SetupTimer
  190: 
  191: Sets up a 1 per sec recurring timer event.  The event handler is used to:
  192: 
  193: =item
  194: 
  195: Trigger timeouts on communications along active sockets.
  196: 
  197: =item
  198: 
  199: Trigger disconnections of idle sockets.
  200: 
  201: =cut
  202: 
  203: sub SetupTimer {
  204:     Debug(6, "SetupTimer");
  205:     Event->timer(interval => 1, debug => 1, cb => \&Tick );
  206: }
  207: 
  208: =pod
  209: 
  210: =head2 ServerToIdle
  211: 
  212: This function is called when a connection to the server is
  213: ready for more work.
  214: 
  215: If there is work in the Work queue the top element is dequeued
  216: and the connection will start to work on it.  If the work queue is
  217: empty, the connection is pushed on the idle connection stack where
  218: it will either get another work unit, or alternatively, if it sits there
  219: long enough, it will be shut down and released.
  220: 
  221: =cut
  222: 
  223: sub ServerToIdle {
  224:     my $Socket   = shift;	# Get the socket.
  225: 
  226:     &Debug(6, "Server to idle");
  227: 
  228:     #  If there's work to do, start the transaction:
  229: 
  230:     $reqdata = $WorkQueue->dequeue();
  231:     Debug(9, "Queue gave request data: ".$reqdata);
  232:     unless($reqdata eq undef)  {
  233: 	my $unixSocket = $ClientQueue->dequeue();
  234: 	&Debug(6, "Starting new work request");
  235: 	&Debug(7, "Request: ".$reqdata);
  236: 	
  237: 	&StartRequest($Socket, $unixSocket, $reqdata);
  238:     } else {
  239: 	
  240:     #  There's no work waiting, so push the server to idle list.
  241: 	&Debug(8, "No new work requests, server connection going idle");
  242: 	delete($ActiveTransactions{$Socket});
  243: 	$IdleConnections->push($Socket);
  244:     }
  245: }
  246: 
  247: =pod
  248: 
  249: =head2 ClientWritable
  250: 
  251: Event callback for when a client socket is writable.
  252: 
  253: This callback is established when a transaction reponse is
  254: avaiable from lond.  The response is forwarded to the unix socket
  255: as it becomes writable in this sub.
  256: 
  257: Parameters:
  258: 
  259: =item Event
  260: 
  261: The event that has been triggered. Event->w->data is
  262: the data and Event->w->fd is the socket to write.
  263: 
  264: =cut
  265: 
  266: sub ClientWritable {
  267:     my $Event    = shift;
  268:     my $Watcher  = $Event->w;
  269:     my $Data     = $Watcher->data;
  270:     my $Socket   = $Watcher->fd;
  271: 
  272:     # Try to send the data:
  273: 
  274:     &Debug(6, "ClientWritable writing".$Data);
  275:     &Debug(9, "Socket is: ".$Socket);
  276: 
  277:     my $result = $Socket->send($Data, 0);
  278: 
  279:     # $result undefined: the write failed.
  280:     # otherwise $result is the number of bytes written.
  281:     # Remove that preceding string from the data.
  282:     # If the resulting data is empty, destroy the watcher
  283:     # and set up a read event handler to accept the next
  284:     # request.
  285: 
  286:     &Debug(9,"Send result is ".$result." Defined: ".defined($result));
  287:     if(defined($result)) {
  288: 	&Debug(9, "send result was defined");
  289: 	if($result == length($Data)) { # Entire string sent.
  290: 	    &Debug(9, "ClientWritable data all written");
  291: 	    $Watcher->cancel();
  292: 	    #
  293: 	    #  Set up to read next request from socket:
  294: 	    
  295: 	    my $descr     = sprintf("Connection to lonc client %d",
  296: 				    $ActiveClients{$Socket});
  297: 	    Event->io(cb    => \&ClientRequest,
  298: 		      poll  => 'r',
  299: 		      desc  => $descr,
  300: 		      data  => "",
  301: 		      fd    => $Socket);
  302: 
  303: 	} else {		# Partial string sent.
  304: 	    $Watcher->data(substr($Data, $result));
  305: 	}
  306: 	
  307:     } else {			# Error of some sort...
  308: 
  309: 	# Some errnos are possible:
  310: 	my $errno = $!;
  311: 	if($errno == POSIX::EWOULDBLOCK   ||
  312: 	   $errno == POSIX::EAGAIN        ||
  313: 	   $errno == POSIX::EINTR) {
  314: 	    # No action taken?
  315: 	} else {		# Unanticipated errno.
  316: 	    &Debug(5,"ClientWritable error or peer shutdown".$RemoteHost);
  317: 	    $Watcher->cancel;	# Stop the watcher.
  318: 	    $Socket->shutdown(2); # Kill connection
  319: 	    $Socket->close();	# Close the socket.
  320: 	}
  321: 	
  322:     }
  323: }
  324: 
  325: =pod
  326: 
  327: =head2 CompleteTransaction
  328: 
  329: Called when the reply data has been received for a lond 
  330: transaction.   The reply data must now be sent to the
  331: ultimate client on the other end of the Unix socket.  This is
  332: done by setting up a writable event for the socket with the
  333: data the reply data.
  334: 
  335: Parameters:
  336: 
  337: =item Socket
  338: 
  339: Socket on which the lond transaction occured.  This is a
  340: LondConnection. The data received is in the TransactionReply member.
  341: 
  342: =item Client
  343: 
  344: Unix domain socket open on the ultimate client.
  345: 
  346: =cut
  347: 
  348: sub CompleteTransaction {
  349:     &Debug(6,"Complete transaction");
  350:     my $Socket = shift;
  351:     my $Client = shift;
  352: 
  353:     my $data   = $Socket->GetReply(); # Data to send.
  354: 
  355:     &Debug(8," Reply was: ".$data);
  356:     my $Serial         = $ActiveClients{$Client};
  357:     my $desc           = sprintf("Connection to lonc client %d",
  358: 				 $Serial);
  359:     Event->io(fd       => $Client,
  360: 	      poll     => "w",
  361: 	      desc     => $desc,
  362: 	      cb       => \&ClientWritable,
  363: 	      data     => $data);
  364: }
  365: =pod
  366: =head2 FailTransaction
  367: 
  368:   Finishes a transaction with failure because the associated lond socket
  369:   disconnected.  It is up to our client to retry if desired.  
  370: 
  371: Parameters:
  372: 
  373: =item client  
  374:  
  375:    The UNIX domain socket open on our client.
  376: 
  377: =cut
  378: 
  379: sub FailTransaction {
  380:     my $client = shift;
  381: 
  382:     &Debug(8, "Failing transaction due to disconnect");
  383:     my $Serial = $ActiveClients{$client};
  384:     my $desc   = sprintf("Connection to lonc client %d", $Serial);
  385:     my $data   = "error: Connection to lond lost\n";
  386: 
  387:     Event->io(fd     => $client,
  388: 	      poll   => "w",
  389: 	      desc   => $desc,
  390: 	      cb     => \&ClientWritable,
  391: 	      data   => $data);
  392: 
  393: }
  394: 
  395: =pod
  396: 
  397: =head2 KillSocket
  398:  
  399: Destroys a socket.  This function can be called either when a socket
  400: has died of 'natural' causes or because a socket needs to be pruned due to
  401: idleness.  If the socket has died naturally, if there are no longer any 
  402: live connections a new connection is created (in case there are transactions
  403: in the queue).  If the socket has been pruned, it is never re-created.
  404: 
  405: Parameters:
  406: 
  407: =item Socket
  408:  
  409:   The socket to kill off.
  410: 
  411: =item Restart
  412: 
  413: nonzero if we are allowed to create a new connection.
  414: 
  415: 
  416: =cut
  417: sub KillSocket {
  418:     my $Socket = shift;
  419:     my $Restart= shift;
  420: 
  421:     #  If the socket came from the active connection set, delete it.
  422:     # otherwise it came from the idle set and has already been destroyed:
  423:     
  424:     if(exists($ActiveTransactions{$Socket})) {
  425: 	delete ($ActiveTransactions{$Socket});
  426:     }
  427:     if(exists($ActiveConnections{$Socket})) {
  428: 	delete($ActiveConnections{$Socket});
  429:     }
  430:     $ConnectionCount--;
  431:     if( ($ConnectionCount = 0) && ($Restart)) {
  432: 	MakeLondConnection();
  433:     }
  434: 
  435: }
  436: 
  437: =pod
  438: 
  439: =head2 LondReadable
  440: 
  441: This function is called whenever a lond connection
  442: is readable.  The action is state dependent:
  443: 
  444: =head3 State=Initialized
  445: 
  446: We''re waiting for the challenge, this is a no-op until the
  447: state changes.
  448: 
  449: =head3 State=Challenged 
  450: 
  451: The challenge has arrived we need to transition to Writable.
  452: The connection must echo the challenge back.
  453: 
  454: =head3 State=ChallengeReplied
  455: 
  456: The challenge has been replied to.  The we are receiveing the 
  457: 'ok' from the partner.
  458: 
  459: =head3 State=RequestingKey
  460: 
  461: The ok has been received and we need to send the request for
  462: an encryption key.  Transition to writable for that.
  463: 
  464: =head3 State=ReceivingKey
  465: 
  466: The the key has been requested, now we are reading the new key.
  467: 
  468: =head3 State=Idle 
  469: 
  470: The encryption key has been negotiated or we have finished 
  471: reading data from the a transaction.   If the callback data has
  472: a client as well as the socket iformation, then we are 
  473: doing a transaction and the data received is relayed to the client
  474: before the socket is put on the idle list.
  475: 
  476: =head3 State=SendingRequest
  477: 
  478: I do not think this state can be received here, but if it is,
  479: the appropriate thing to do is to transition to writable, and send
  480: the request.
  481: 
  482: =head3 State=ReceivingReply
  483: 
  484: We finished sending the request to the server and now transition
  485: to readable to receive the reply. 
  486: 
  487: The parameter to this function are:
  488: 
  489: The event. Implicit in this is the watcher and its data.  The data 
  490: contains at least the lond connection object and, if a 
  491: transaction is in progress, the socket attached to the local client.
  492: 
  493: =cut
  494: 
  495: sub LondReadable {
  496:     my $Event      = shift;
  497:     my $Watcher    = $Event->w;
  498:     my $Socket     = $Watcher->data;
  499:     my $client     = undef;
  500: 
  501: 
  502:     my $State = $Socket->GetState(); # All action depends on the state.
  503: 
  504:     &Debug(6,"LondReadable called state = ".$State);
  505:     SocketDump(6, $Socket);
  506: 
  507:     if($Socket->Readable() != 0) {
  508: 	 # bad return from socket read. Currently this means that
  509: 	# The socket has become disconnected. We fail the transaction.
  510: 
  511: 	if(exists($ActiveTransactions{$Socket})) {
  512: 	    Debug(3,"Lond connection lost failing transaction");
  513: 	    FailTransaction($ActiveTransactions{$Socket});
  514: 	}
  515: 	$Watcher->cancel();
  516: 	KillSocket($Socket, 1);
  517: 	return;
  518:     }
  519:     SocketDump(6,$Socket);
  520: 
  521:     $State = $Socket->GetState(); # Update in case of transition.
  522:     &Debug(6, "After read, state is ".$State);
  523: 
  524:    if($State eq "Initialized") {
  525: 
  526: 
  527:     } elsif ($State eq "ChallengeReceived") {
  528: 	#  The challenge must be echoed back;  The state machine
  529: 	# in the connection takes care of setting that up.  Just
  530: 	# need to transition to writable:
  531: 
  532: 	$Watcher->poll("w");
  533: 	$Watcher->cb(\&LondWritable);
  534: 
  535:     } elsif ($State eq "ChallengeReplied") {
  536: 
  537: 
  538:     } elsif ($State eq "RequestingKey") {
  539: 	#  The ok was received.  Now we need to request the key
  540: 	#  That requires us to be writable:
  541: 
  542: 	$Watcher->poll("w");
  543: 	$Watcher->cb(\&LondWritable);
  544: 
  545:     } elsif ($State eq "ReceivingKey") {
  546: 
  547:     } elsif ($State eq "Idle") {
  548: 	# If necessary, complete a transaction and then go into the
  549: 	# idle queue.
  550: 	if(exists($ActiveTransactions{$Socket})) {
  551: 	    Debug(8,"Completing transaction!!");
  552: 	    CompleteTransaction($Socket, 
  553: 				$ActiveTransactions{$Socket});
  554: 	}
  555: 	$Watcher->cancel();
  556: 	ServerToIdle($Socket);	# Next work unit or idle.
  557: 
  558:     } elsif ($State eq "SendingRequest") {
  559: 	#  We need to be writable for this and probably don't belong
  560: 	#  here inthe first place.
  561: 
  562: 	Deubg(6, "SendingRequest state encountered in readable");
  563: 	$Watcher->poll("w");
  564: 	$Watcher->cb(\&LondWritable);
  565: 
  566:     } elsif ($State eq "ReceivingReply") {
  567: 
  568: 
  569:     } else {
  570: 	 # Invalid state.
  571: 	Debug(4, "Invalid state in LondReadable");
  572:     }
  573: }
  574: 
  575: =pod
  576: 
  577: =head2 LondWritable
  578: 
  579: This function is called whenever a lond connection
  580: becomes writable while there is a writeable monitoring
  581: event.  The action taken is very state dependent:
  582: 
  583: =head3 State = Connected 
  584: 
  585: The connection is in the process of sending the 'init' hailing to the
  586: lond on the remote end.  The connection object''s Writable member is
  587: called.  On error, ConnectionError is called to destroy the connection
  588: and remove it from the ActiveConnections hash
  589: 
  590: =head3 Initialized
  591: 
  592: 'init' has been sent, writability monitoring is removed and
  593: readability monitoring is started with LondReadable as the callback.
  594: 
  595: =head3 ChallengeReceived
  596: 
  597: The connection has received the who are you challenge from the remote
  598: system, and is in the process of sending the challenge
  599: response. Writable is called.
  600: 
  601: =head3 ChallengeReplied
  602: 
  603: The connection has replied to the initial challenge The we switch to
  604: monitoring readability looking for the server to reply with 'ok'.
  605: 
  606: =head3 RequestingKey
  607: 
  608: The connection is in the process of requesting its encryption key.
  609: Writable is called.
  610: 
  611: =head3 ReceivingKey
  612: 
  613: The connection has sent the request for a key.  Switch to readability
  614: monitoring to accept the key
  615: 
  616: =head3 SendingRequest
  617: 
  618: The connection is in the process of sending a request to the server.
  619: This request is part of a client transaction.  All the states until
  620: now represent the client setup protocol. Writable is called.
  621: 
  622: =head3 ReceivingReply
  623: 
  624: The connection has sent a request.  Now it must receive a reply.
  625: Readability monitoring is requested.
  626: 
  627: This function is an event handler and therefore receives as
  628: a parameter the event that has fired.  The data for the watcher
  629: of this event is a reference to a list of one or two elements,
  630: depending on state. The first (and possibly only) element is the
  631: socket.  The second (present only if a request is in progress)
  632: is the socket on which to return a reply to the caller.
  633: 
  634: =cut
  635: 
  636: sub LondWritable {
  637:     my $Event   = shift;
  638:     my $Watcher = $Event->w;
  639:     my @data    = $Watcher->data;
  640:     Debug(6,"LondWritable State = ".$State." data has ".@data." elts.\n");
  641: 
  642:     my $Socket  = $data[0];	# I know there's at least a socket.
  643: 
  644:     #  Figure out what to do depending on the state of the socket:
  645:     
  646: 
  647:     my $State   = $Socket->GetState();
  648: 
  649: 
  650:     SocketDump(6,$Socket);
  651: 
  652:     if      ($State eq "Connected")         {
  653: 
  654: 	if ($Socket->Writable() != 0) {
  655: 	    #  The write resulted in an error.
  656: 	    # We'll treat this as if the socket got disconnected:
  657: 	    if(exists($ActiveTransactions{$Socket})) {
  658: 		Debug(3, "Lond connection lost, failing transactions");
  659: 		FailTransaction($ActiveTransactions{$Socket});
  660: 	    }
  661: 	    $Watcher->cancel();
  662: 	    KillSocket($Socket, 1);
  663: 	    return;
  664: 	}
  665: 	#  "init" is being sent...
  666: 
  667: 	
  668:     } elsif ($State eq "Initialized")       {
  669: 
  670: 	# Now that init was sent, we switch 
  671: 	# to watching for readability:
  672: 
  673: 	$Watcher->poll("r");
  674: 	$Watcher->cb(\&LondReadable);
  675: 
  676:     } elsif ($State eq "ChallengeReceived") {
  677: 	# We received the challenge, now we 
  678: 	# are echoing it back. This is a no-op,
  679: 	# we're waiting for the state to change
  680: 	
  681: 	if($Socket->Writable() != 0) {
  682: 	    # Write of the next chunk resulted in an error.
  683: 	}
  684: 	
  685:     } elsif ($State eq "ChallengeReplied")  {
  686: 	# The echo was sent back, so we switch
  687: 	# to watching readability.
  688: 
  689: 	$Watcher->poll("r");
  690: 	$Watcher->cb(\&LondReadable);
  691: 
  692:     } elsif ($State eq "RequestingKey")     {
  693: 	# At this time we're requesting the key.
  694: 	# again, this is essentially a no-op.
  695: 	# we'll write the next chunk until the
  696: 	# state changes.
  697: 
  698: 	if($Socket->Writable() != 0) {
  699: 	    # Write resulted in an error.
  700: 	}
  701: 
  702:     } elsif ($State eq "ReceivingKey")      {
  703: 	# Now we need to wait for the key
  704: 	# to come back from the peer:
  705: 
  706: 	$Watcher->poll("r");
  707: 	$Watcher->cb(\&LondReadable);
  708: 
  709:     } elsif ($State eq "SendingRequest")    {
  710: 	# At this time we are sending a request to the
  711: 	# peer... write the next chunk:
  712: 
  713: 	if($Socket->Writable() != 0) {
  714: 	    # Write resulted in an error.
  715: 
  716: 	}
  717: 
  718:     } elsif ($State eq "ReceivingReply")    {
  719: 	# The send has completed.  Wait for the
  720: 	# data to come in for a reply.
  721: 	Debug(8,"Writable sent request/receiving reply");
  722: 	$Watcher->poll("r");
  723: 	$Watcher->cb(\&LondReadable);
  724: 
  725:     } else {
  726: 	#  Control only passes here on an error: 
  727: 	#  the socket state does not match any
  728: 	#  of the known states... so an error
  729: 	#  must be logged.
  730: 
  731: 	&Debug(4, "Invalid socket state ".$State."\n");
  732:     }
  733:     
  734: }
  735: 
  736: =pod
  737: 
  738: =head2 MakeLondConnection
  739: 
  740: Create a new lond connection object, and start it towards its initial
  741: idleness.  Once idle, it becomes elligible to receive transactions
  742: from the work queue.  If the work queue is not empty when the
  743: connection is completed and becomes idle, it will dequeue an entry and
  744: start off on it.
  745: 
  746: =cut
  747: 
  748: sub MakeLondConnection {     
  749:     Debug(4,"MakeLondConnection to ".GetServerHost()." on port "
  750: 	  .GetServerPort());
  751: 
  752:     my $Connection = LondConnection->new(&GetServerHost(),
  753: 					 &GetServerPort());
  754: 
  755:     if($Connection == undef) {	# Needs to be more robust later.
  756: 	die "Failed to make a connection!!".$!."\n";
  757: 	
  758:     } 
  759:     # The connection needs to have writability 
  760:     # monitored in order to send the init sequence
  761:     # that starts the whole authentication/key
  762:     # exchange underway.
  763:     #
  764:     my $Socket = $Connection->GetSocket();
  765:     if($Socket == undef) {
  766: 	die "did not get a socket from the connection";
  767:     } else {
  768: 	&Debug(9,"MakeLondConnection got socket: ".$Socket);
  769:     }
  770: 
  771:     
  772:     $event = Event->io(fd       => $Socket,
  773: 		       poll     => 'w',
  774: 		       cb       => \&LondWritable,
  775: 		       data     => ($Connection, undef),
  776: 		       desc => 'Connection to lond server');
  777:     $ActiveConnections{$Connection} = $event;
  778: 
  779:     $ConnectionCount++;
  780:    
  781:     
  782: }
  783: 
  784: =pod
  785: 
  786: =head2 StartRequest
  787: 
  788: Starts a lond request going on a specified lond connection.
  789: parameters are:
  790: 
  791: =item $Lond
  792: 
  793: Connection to the lond that will send the transaction and receive the
  794: reply.
  795: 
  796: =item $Client
  797: 
  798: Connection to the client that is making this request We got the
  799: request from this socket, and when the request has been relayed to
  800: lond and we get a reply back from lond it will get sent to this
  801: socket.
  802: 
  803: =item $Request
  804: 
  805: The text of the request to send.
  806: 
  807: =cut
  808: 
  809: sub StartRequest {
  810:     my $Lond     = shift;
  811:     my $Client   = shift;
  812:     my $Request  = shift;
  813:     
  814:     Debug(6, "StartRequest: ".$Request);
  815: 
  816:     my $Socket = $Lond->GetSocket();
  817:     
  818:     $ActiveTransactions{$Lond} = $Client; # Socket to relay to client.
  819: 
  820:     $Lond->InitiateTransaction($Request);
  821:     $event = Event->io(fd      => $Lond->GetSocket(),
  822: 		       poll    => "w",
  823: 		       cb      => \&LondWritable,
  824: 		       data    => $Lond,
  825: 		       desc    => "lond transaction connection");
  826:     $ActiveConnections{$Lond} = $event;
  827:     Debug(8," Start Request made watcher data with ".$event->data."\n");
  828: }
  829: 
  830: =pod
  831: 
  832: =head2 QueueTransaction
  833: 
  834: If there is an idle lond connection, it is put to work doing this
  835: transaction.  Otherwise, the transaction is placed in the work queue.
  836: If placed in the work queue and the maximum number of connections has
  837: not yet been created, a new connection will be started.  Our goal is
  838: to eventually have a sufficient number of connections that the work
  839: queue will typically be empty.  parameters are:
  840: 
  841: =item Socket
  842: 
  843: open on the lonc client.
  844: 
  845: =item Request
  846: 
  847: data to send to the lond.
  848: 
  849: =cut
  850: 
  851: sub QueueTransaction {
  852:     my $requestSocket = shift;
  853:     my $requestData   = shift;
  854: 
  855:     Debug(6,"QueueTransaction: ".$requestData);
  856: 
  857:     my $LondSocket    = $IdleConnections->pop();
  858:     if(!defined $LondSocket) {	# Need to queue request.
  859: 	Debug(8,"Must queue...");
  860: 	$ClientQueue->enqueue($requestSocket);
  861: 	$WorkQueue->enqueue($requestData);
  862: 	if($ConnectionCount < $MaxConnectionCount) {
  863: 	    Debug(4,"Starting additional lond connection");
  864: 	    MakeLondConnection();
  865: 	}
  866:     } else {			# Can start the request:
  867: 	Debug(8,"Can start...");
  868: 	StartRequest($LondSocket, $requestSocket, $requestData);
  869:     }
  870: }
  871: 
  872: #-------------------------- Lonc UNIX socket handling ---------------------
  873: 
  874: =pod
  875: 
  876: =head2 ClientRequest
  877: 
  878: Callback that is called when data can be read from the UNIX domain
  879: socket connecting us with an apache server process.
  880: 
  881: =cut
  882: 
  883: sub ClientRequest {
  884:     Debug(6, "ClientRequest");
  885:     my $event   = shift;
  886:     my $watcher = $event->w;
  887:     my $socket  = $watcher->fd;
  888:     my $data    = $watcher->data;
  889:     my $thisread;
  890: 
  891:     Debug(9, "  Watcher named: ".$watcher->desc);
  892: 
  893:     my $rv = $socket->recv($thisread, POSIX::BUFSIZ, 0);
  894:     Debug(8, "rcv:  data length = ".length($thisread)
  895: 	  ." read =".$thisread);
  896:     unless (defined $rv && length($thisread)) {
  897: 	 # Likely eof on socket.
  898: 	Debug(5,"Client Socket closed on lonc for ".$RemoteHost);
  899: 	close($socket);
  900: 	$watcher->cancel();
  901: 	delete($ActiveClients{$socket});
  902:     }
  903:     Debug(8,"Data: ".$data." this read: ".$thisread);
  904:     $data = $data.$thisread;	# Append new data.
  905:     $watcher->data($data);
  906:     if($data =~ /(.*\n)/) {	# Request entirely read.
  907: 	Debug(8, "Complete transaction received: ".$data);
  908: 	QueueTransaction($socket, $data);
  909: 	$watcher->cancel();	# Done looking for input data.
  910:     }
  911: 
  912: }
  913: 
  914: 
  915: =pod
  916: 
  917: =head2  NewClient
  918: 
  919: Callback that is called when a connection is received on the unix
  920: socket for a new client of lonc.  The callback is parameterized by the
  921: event.. which is a-priori assumed to be an io event, and therefore has
  922: an fd member that is the Listener socket.  We Accept the connection
  923: and register a new event on the readability of that socket:
  924: 
  925: =cut
  926: 
  927: sub NewClient {
  928:     Debug(6, "NewClient");
  929:     my $event      = shift;		# Get the event parameters.
  930:     my $watcher    = $event->w; 
  931:     my $socket     = $watcher->fd;	# Get the event' socket.
  932:     my $connection = $socket->accept();	# Accept the client connection.
  933:     Debug(8,"Connection request accepted from "
  934: 	  .GetPeername($connection, AF_UNIX));
  935: 
  936: 
  937:     my $description = sprintf("Connection to lonc client %d",
  938: 			      $ClientConnection);
  939:     Debug(9, "Creating event named: ".$description);
  940:     Event->io(cb      => \&ClientRequest,
  941: 	      poll    => 'r',
  942: 	      desc    => $description,
  943: 	      data    => "",
  944: 	      fd      => $connection);
  945:     $ActiveClients{$connection} = $ClientConnection;
  946:     $ClientConnection++;
  947: }
  948: 
  949: =pod
  950: 
  951: =head2 GetLoncSocketPath
  952: 
  953: Returns the name of the UNIX socket on which to listen for client
  954: connections.
  955: 
  956: =cut
  957: 
  958: sub GetLoncSocketPath {
  959:     return $UnixSocketDir."/".GetServerHost();
  960: }
  961: 
  962: =pod
  963: 
  964: =head2 GetServerHost
  965: 
  966: Returns the host whose lond we talk with.
  967: 
  968: =cut
  969: 
  970: sub GetServerHost {		# Stub - get this from config.
  971:     return $RemoteHost;		# Setup by the fork.
  972: }
  973: 
  974: =pod
  975: 
  976: =head2 GetServerPort
  977: 
  978: Returns the lond port number.
  979: 
  980: =cut
  981: 
  982: sub GetServerPort {		# Stub - get this from config.
  983:     return $perlvar{londPort};
  984: }
  985: 
  986: =pod
  987: 
  988: =head2 SetupLoncListener
  989: 
  990: Setup a lonc listener event.  The event is called when the socket
  991: becomes readable.. that corresponds to the receipt of a new
  992: connection.  The event handler established will accept the connection
  993: (creating a communcations channel), that int turn will establish
  994: another event handler to subess requests.
  995: 
  996: =cut
  997: 
  998: sub SetupLoncListener {
  999: 
 1000:     my $socket;
 1001:     my $SocketName = GetLoncSocketPath();
 1002:     unlink($SocketName);
 1003:     unless ($socket = IO::Socket::UNIX->new(Local  => $SocketName,
 1004: 					    Listen => 10, 
 1005: 					    Type   => SOCK_STREAM)) {
 1006: 	die "Failed to create a lonc listner socket";
 1007:     }
 1008:     Event->io(cb     => \&NewClient,
 1009: 	      poll   => 'r',
 1010: 	      desc   => 'Lonc listener Unix Socket',
 1011: 	      fd     => $socket);
 1012: }
 1013: 
 1014: =pod
 1015: 
 1016: =head2 ChildProcess
 1017: 
 1018: This sub implements a child process for a single lonc daemon.
 1019: 
 1020: =cut
 1021: 
 1022: sub ChildProcess {
 1023: 
 1024:     print "Loncnew\n";
 1025: 
 1026:     # For now turn off signals.
 1027:     
 1028:     $SIG{QUIT}  = IGNORE;
 1029:     $SIG{HUP}   = IGNORE;
 1030:     $SIG{USR1}  = IGNORE;
 1031:     $SIG{INT}   = IGNORE;
 1032:     $SIG{CHLD}  = IGNORE;
 1033:     $SIG{__DIE__}  = IGNORE;
 1034: 
 1035:     SetupTimer();
 1036:     
 1037:     SetupLoncListener();
 1038:     
 1039:     $Event::Debuglevel = $DebugLevel;
 1040:     
 1041:     Debug(9, "Making initial lond connection for ".$RemoteHost);
 1042: 
 1043: # Setup the initial server connection:
 1044:     
 1045:     &MakeLondConnection();
 1046:     
 1047:     Debug(9,"Entering event loop");
 1048:     my $ret = Event::loop();		#  Start the main event loop.
 1049:     
 1050:     
 1051:     die "Main event loop exited!!!";
 1052: }
 1053: 
 1054: #  Create a new child for host passed in:
 1055: 
 1056: sub CreateChild {
 1057:     my $host = shift;
 1058:     $RemoteHost = $host;
 1059:     Debug(3, "Forking off child for ".$RemoteHost);
 1060:     sleep(5);
 1061:     $pid          = fork;
 1062:     if($pid) {			# Parent
 1063: 	$ChildHash{$pid} = $RemoteHost;
 1064:     } else {			# child.
 1065: 	ChildProcess;
 1066:     }
 1067: 
 1068: }
 1069: #
 1070: #  Parent process logic pass 1:
 1071: #   For each entry in the hosts table, we will
 1072: #  fork off an instance of ChildProcess to service the transactions
 1073: #  to that host.  Each pid will be entered in a global hash
 1074: #  with the value of the key, the host.
 1075: #  The parent will then enter a loop to wait for process exits.
 1076: #  Each exit gets logged and the child gets restarted.
 1077: #
 1078: 
 1079: my $HostIterator = LondConnection::GetHostIterator;
 1080: while (! $HostIterator->end()) {
 1081: 
 1082:     $hostentryref = $HostIterator->get();
 1083:     CreateChild($hostentryref->[0]);
 1084:     $HostIterator->next();
 1085: }
 1086: 
 1087: # Maintain the population:
 1088: 
 1089: while(1) {
 1090:     $deadchild = wait();
 1091:     if(exists $ChildHash{$deadchild}) {	# need to restart.
 1092: 	$deadhost = $ChildHash{$deadchild};
 1093: 	delete($ChildHash{$deadchild});
 1094: 	Debug(4,"Lost child pid= ".$deadchild.
 1095: 	      "Connected to host ".$deadhost);
 1096: 	CreateChild($deadhost);
 1097:     }
 1098: }
 1099: 
 1100: =head1 Theory
 1101: 
 1102: The event class is used to build this as a single process with an
 1103: event driven model.  The following events are handled:
 1104: 
 1105: =item UNIX Socket connection Received
 1106: 
 1107: =item Request data arrives on UNIX data transfer socket.
 1108: 
 1109: =item lond connection becomes writable.
 1110: 
 1111: =item timer fires at 1 second intervals.
 1112: 
 1113: All sockets are run in non-blocking mode.  Timeouts managed by the timer
 1114: handler prevents hung connections.
 1115: 
 1116: Key data structures:
 1117: 
 1118: =item RequestQueue
 1119: 
 1120: A queue of requests received from UNIX sockets that are
 1121: waiting for a chance to be forwarded on a lond connection socket.
 1122: 
 1123: =item ActiveConnections
 1124: 
 1125: A hash of lond connections that have transactions in process that are
 1126: available to be timed out.
 1127: 
 1128: =item ActiveTransactions
 1129: 
 1130: A hash indexed by lond connections that contain the client reply
 1131: socket for each connection that has an active transaction on it.
 1132: 
 1133: =item IdleConnections
 1134: 
 1135: A hash of lond connections that have no work to do.  These connections
 1136: can be closed if they are idle for a long enough time.
 1137: 
 1138: =cut

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