Annotation of loncom/loncnew, revision 1.2

1.1       foxr        1: #!/usr/bin/perl
1.2     ! albertel    2: # The LearningOnline Network with CAPA
        !             3: # lonc maintains the connections to remote computers
        !             4: #
        !             5: # $Id: gplheader.pl,v 1.1 2001/11/29 18:19:27 www 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: #
1.1       foxr       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>