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