GVLocateImpl.mesa (Cedar) - Location of servers
Copyright © 1985 by Xerox Corporation. All rights reserved.
Andrew Birrell May 13, 1983 1:32 pm
Mike Schroeder March 21, 1981 10:59 AM
Hal Murray, June 3, 1986 3:46:17 pm PDT
DIRECTORY
GVBasics USING [Connect, RName],
GVLocate USING [FoundServerInfo, FoundState],
GVNames USING [GetConnect, GetMembers, MemberInfo, NameType, RListHandle],
GVProtocol USING [GetSocket, IsLocal],
Process USING [MsecToTicks, SetTimeout ],
Pup USING [Address, allHosts, allNets, nullAddress, Socket],
PupBuffer USING [Buffer],
PupHop USING [GetHop, Hop],
PupName USING [Error, HisAddresses, NameLookup],
PupSocket USING [AllocBuffer, CreateEphemeral, Destroy, Get, FreeBuffer, Kick, Send, SetNoErrors, SetUserHWords, Socket, waitForever],
Rope USING [Cat, Equal, Fetch, Length, Substr];
GVLocateImpl: CEDAR MONITOR
IMPORTS GVNames, GVProtocol, Process, PupHop, PupName, PupSocket, Rope
EXPORTS GVLocate = BEGIN
AddressInfo: TYPE = REF AddressInfoObject;
AddressInfoObject: TYPE = RECORD[
preSorted: BOOLEAN,
found: CARDINAL,
addresses: SEQUENCE possible: CARDINAL OF AddressRec];
AddressRec: TYPE = RECORD[
addr: Pup.Address,
hop: PupHop.Hop,
reply: BOOLEAN];
ReplyInfo: TYPE = RECORD[ found: BOOLEAN, where: Pup.Address ];
Prod: PROC [socket: PupSocket.Socket, addrInfo: AddressInfo, accept: PROC [Pup.Address] RETURNS[BOOLEAN] ] RETURNS [info: ReplyInfo ] = {
This is complicated! The parameters are a table of candidate addresses, and a socket. The prodder process sends out "echoMe" pups to the addresses; the slurper process accepts "iAmEcho" replies, and marks the table. The original process reads the marks from the table, and calls the client's "accept" procedure to see if this address is ok. This satisfies various constraints about not hogging pup buffers. The "echoMe" packets are sent out to closer addresses first; they're not sent after the client has accepted an address. Everywhere, the Monitor is not locked while doing long waits.
replyRecvd: BOOLEANFALSE;-- "reply-waiting" flag
cond: CONDITION; -- notified when reply comes in
pleaseDie: BOOLEANFALSE; -- for killing auxiliary processes
DeathWish: ENTRY PROC RETURNS [BOOLEAN] = { RETURN[pleaseDie] };
KillSiblings: ENTRY PROC = { pleaseDie ← TRUE; PupSocket.Kick[socket] };
NoteReply: ENTRY PROC [i: CARDINAL, addr: Pup.Address] = {
caller promises that "i" is in range
IF addrInfo.addresses[i].addr.host = 0 THEN {
addrInfo.addresses[i].addr.host ← addr.host;
addrInfo.addresses[i].addr.net ← addr.net };
addrInfo.addresses[i].reply ← TRUE;
IF ~replyRecvd THEN NOTIFY cond;
replyRecvd ← TRUE;
};
TestReply: ENTRY PROC [i: CARDINAL] RETURNS [yes: BOOLEAN] = {
yes ← addrInfo.addresses[i].reply; addrInfo.addresses[i].reply ← FALSE };
More: ENTRY PROC RETURNS[yes: BOOLEAN] = CHECKED {
IF ~replyRecvd THEN WAIT cond;
yes ← replyRecvd; replyRecvd ← FALSE;
};
SendProd: PROCEDURE = {
sent: CARDINAL ← 0;
lastHop: CARDINAL = 4; --sorting limit--
FOR wantedHop: CARDINAL IN [0..lastHop) DO
lastPass: BOOLEAN = (wantedHop=lastHop-1);
FOR i: CARDINAL IN [0..addrInfo.found) DO
IF DeathWish[] OR sent >= addrInfo.found THEN GOTO GetUsOutOfHere;
IF addrInfo.preSorted
OR (lastPass AND addrInfo.addresses[i].hop>=wantedHop)
OR addrInfo.addresses[i].hop = wantedHop THEN {
b: PupBuffer.Buffer = PupSocket.AllocBuffer[socket];
b.type ← echoMe;
b.dest ← addrInfo.addresses[i].addr;
force socket number, because database doesn't have GV test-mode socket numbers
b.dest.socket ← GVProtocol.GetSocket[RSPoll];
b.hWord[0] ← i;
PupSocket.SetUserHWords[b, 1];
PupSocket.Send[socket, b, b.dest];
sent ← sent + 1; };
REPEAT
GetUsOutOfHere => EXIT -- from outer loop!
ENDLOOP;
ENDLOOP;
};
Slurp: PROCEDURE = {
UNTIL DeathWish[] DO
b: PupBuffer.Buffer = PupSocket.Get[socket];
IF b # NIL THEN {
SELECT b.type FROM
iAmEcho =>
IF b.hWord[0] < addrInfo.found THEN NoteReply[b.hWord[0], b.source];
ENDCASE => NULL;
PupSocket.FreeBuffer[b]; };
ENDLOOP;
};
prodder: PROCESS;
slurper: PROCESS;
TRUSTED {
prodder ← FORK SendProd[];
slurper ← FORK Slurp[];
Process.SetTimeout[@cond, Process.MsecToTicks[1500]]; };
info.found ← FALSE;
UNTIL info.found DO
IF ~More[] THEN EXIT-- time-out on "cond" --;
FOR i: CARDINAL IN [0..addrInfo.found) UNTIL info.found DO
IF TestReply[i] THEN {
info.where ← addrInfo.addresses[i].addr;
IF accept[info.where] THEN info.found ← TRUE;
};
ENDLOOP;
ENDLOOP;
KillSiblings[];
TRUSTED {
JOIN prodder;
JOIN slurper; };
};
GetGVRegServer: PROC RETURNS[ info: AddressInfo ] = {
addressesInGVRS: CARDINAL = 10; -- we take the 10 closest
socket: Pup.Socket = GVProtocol.GetSocket[RSEnquiry];
addresses: LIST OF Pup.Address;
Top-level contacting R-Servers. Any method is valid! Try local broadcast, and try Name Lookup Server.
info ← NEW[AddressInfoObject[addressesInGVRS]];
info.preSorted ← TRUE;
info.found ← 0;
info.addresses[0] ← [
addr: [net: Pup.allNets, host: Pup.allHosts, socket: socket],
hop: PupHop.GetHop[Pup.allNets],
reply: FALSE ];
info.found ← 1;
addresses ← PupName.HisAddresses["GrapevineRServer", socket ! PupName.Error => CONTINUE ];
UNTIL addresses = NIL DO
info.addresses[info.found] ← [
addr: addresses.first,
hop: PupHop.GetHop[addresses.first.net],
reply: FALSE ];
info.found ← info.found + 1;
IF info.found = addressesInGVRS THEN EXIT;
addresses ← addresses.rest;
ENDLOOP;
};
GetGroupInfo: PROC [who: GVBasics.RName] RETURNS[ state: GVLocate.FoundState, info: AddressInfo ← NIL, local: GVBasics.RName ← NIL] = {
mInfo: GVNames.MemberInfo = GVNames.GetMembers[who];
SELECT mInfo.type FROM
notFound, individual => state ← notFound;
allDown => state ← allDown;
group => {
m: group GVNames.MemberInfo = NARROW[mInfo, group GVNames.MemberInfo];
state ← found;
info ← NEW[AddressInfoObject[m.count]];
info.preSorted ← FALSE;
info.found ← 0;
FOR member: GVNames.RListHandle ← m.members, member.rest UNTIL member = NIL DO
cInfo: GVNames.NameType;
connect: GVBasics.Connect;
[cInfo, connect] ← GVNames.GetConnect[member.first];
SELECT cInfo FROM
individual =>
BEGIN
addr: Pup.Address =
PupName.NameLookup[connect, GVProtocol.GetSocket[RSEnquiry] !
PupName.Error => GOTO Cant];
info.addresses[info.found] ← [
addr: addr,
hop: PupHop.GetHop[addr.net],
reply: FALSE ];
info.found ← info.found + 1;
IF GVProtocol.IsLocal[addr] THEN local ← member.first;
EXITS Cant => NULL;
END;
ENDCASE => NULL -- ignore others --;
ENDLOOP; };
ENDCASE => ERROR;
};
FindNearestServer: PUBLIC PROC [
list: GVBasics.RName,
accept: PROC [Pup.Address] RETURNS [BOOL] ]
RETURNS [info: GVLocate.FoundServerInfo] = {
addrInfo: AddressInfo;
state: GVLocate.FoundState;
IF list.Equal["gv.gv", FALSE] THEN { state ← found; addrInfo ← GetGVRegServer[]; }
ELSE [state, addrInfo, ] ← GetGroupInfo[list];
SELECT state FROM
notFound => RETURN[[notFound[]]];
allDown => RETURN[[allDown[]]];
found => {
socket: PupSocket.Socket = PupSocket.CreateEphemeral[
remote: Pup.nullAddress, getTimeout: PupSocket.waitForever ];
PupSocket.SetNoErrors[socket];
FOR i: NAT IN [0..4) DO -- re-tries for lost packets
reply: ReplyInfo = Prod[socket, addrInfo, accept];
IF reply.found THEN {
PupSocket.Destroy[socket];
RETURN[[found[reply.where]]]; };
REPEAT FINISHED => {
PupSocket.Destroy[socket];
RETURN[[allDown[]]]; };
ENDLOOP; };
ENDCASE => ERROR;
};
FindLocalServer: PUBLIC PROC [list: GVBasics.RName]
RETURNS [state: GVLocate.FoundState, local: GVBasics.RName] = {
[state, , local] ← GetGroupInfo[list];
IF local = NIL THEN state ← notFound;
};
FindRegServer: PUBLIC PROC [
who: GVBasics.RName, accept: PROC [Pup.Address] RETURNS [BOOL] ]
RETURNS [foundInfo: GVLocate.FoundServerInfo] = {
find a registration server for given R-Name
sep: CHARACTER = '.; -- SN sep NA
length: INT = who.Length[];
FOR i: INT DECREASING IN [0..length) DO
IF who.Fetch[i] = sep THEN
RETURN[FindNearestServer[Rope.Cat[who.Substr[start: i+1, len: length-i-1], ".gv"], accept]];
REPEAT FINISHED => RETURN[[notFound[]]];
ENDLOOP;
};
END.