XNSCHPrivateImpl.mesa
Copyright Ó 1988, 1989, 1991, 1992 by Xerox Corporation. All rights reserved.
Demers, August 3, 1988 9:44:08 am PDT
Wes Irish, July 8, 1988 11:48:07 am PDT
Willie-Sue, February 13, 1989 3:29:56 pm PST
Tim Diebert: November 16, 1989 9:21:08 am PST
Willie-s, February 14, 1992 10:54 am PST
DIRECTORY
BasicTime USING [earliestGMT, GMT, Now, Period, Update],
CHACLOpsP127V1 USING [ArgumentError, AuthenticationError, CallError, PropertyError, UpdateError, WrongServer],
CHEntriesP0V0 USING [addressList, members],
CHOpsP2V3 USING [ArgumentError, AuthenticationError, Authenticator, CallError, ListDomainsServed, PropertyError, RetrieveItem, RetrieveMembers, UpdateError, WrongServer],
CrRPC USING [BulkDataCheckAbortProc, BulkDataValueProc, BulkDataXferProc, CreateClientHandle, DestroyClientHandle, Error, GetRope, Handle, ReadBulkDataStream],
Process USING [Detach, PauseMsec],
Random USING [ChooseInt],
RefTab USING [Create, EachPairAction, EqualProc, Fetch, HashProc, Insert, Pairs, Ref, Val],
Rope USING [Cat, Equal, Index, IsEmpty, ROPE, Substr],
SymTab USING [Create, EachPairAction, Fetch, Pairs, Ref, Store],
XNS USING [Address, Host, unknownAddress, unknownSocket],
XNSAuth USING [AuthenticationError, CallError, Conversation, GetCredentials, GetNextVerifier, GetNullIdentity, Identity, Initiate, SetRecipientHostNumber, Terminate],
XNSCH USING [AddressesFromItem, BestAddressInList, Element, Error, ErrorCode, Item, Name, Pattern, Properties, PropertyID, Which],
XNSCHConcreteTypes USING [ConversationObject],
XNSCHPrivate USING [GetElement, RemoteProc],
XNSCredentials USING [GetIdentity],
XNSRouter USING [GetHops, unreachable],
XNSServerLocation USING [EachAddressProc, LocateServers, StopBroadcast],
XNSWKS USING [clearinghouse]
;
XNSCHPrivateImpl:
CEDAR
MONITOR
IMPORTS BasicTime, CHACLOpsP127V1, CHOpsP2V3, CrRPC, Process, Random, RefTab, Rope, SymTab, XNSAuth, XNSCH, XNSCHPrivate, XNSCredentials, XNSRouter, XNSServerLocation
EXPORTS XNSCH, XNSCHPrivate
~ {
OPEN CHEntries: CHEntriesP0V0, CHOps: CHOpsP2V3, CHACLOps: CHACLOpsP127V1;
chPgmNum: CARD32 ~ 2;
chVersionNum: CARD16 ~ 3;
ROPE: TYPE ~ Rope.ROPE;
Name: TYPE ~ XNSCH.Name;
Pattern: TYPE ~ XNSCH.Pattern;
Element: TYPE ~ XNSCH.Element;
PropertyID: TYPE ~ XNSCH.PropertyID;
Properties: TYPE ~ XNSCH.Properties;
Item: TYPE ~ XNSCH.Item;
WrongServerError: PUBLIC ERROR [hint: Name] ¬ CHOps.WrongServer;
shiftInName: ROPE ~ "CHServers";
chServiceName: Name ~
[organization~shiftInName, domain~shiftInName, object~"Clearinghouse Service"];
Name / Address Cache
Cache of distinguished name / address pairs for recently looked-up names.
Note the same name could end up in the cache more than once; this is benign.
NACacheEntry: TYPE ~ REF NACacheEntryObject;
NACacheEntryObject:
TYPE ~
RECORD [
next: NACacheEntry,
name, distingName: Name,
address: XNS.Address];
naCacheMaxSize: INT ¬ 8;
naCacheSize: INT ¬ 0;
naCache: NACacheEntry ¬ NIL; -- REF to tail of circular list of NACacheEntryObjects
NACacheInsert:
PUBLIC ENTRY
PROC [name: Name, distingName: Name, address:
XNS.Address] ~ {
IF naCacheSize < naCacheMaxSize
THEN {
new: NACacheEntry ¬ NEW[NACacheEntryObject ¬ [address~XNS.unknownAddress]];
IF naCache =
NIL
THEN { new.next ¬ naCache ¬ new }
ELSE { new.next ¬ naCache.next; naCache.next ¬ new };
naCacheSize ¬ naCacheSize.SUCC;
};
naCache ¬ naCache.next;
naCache.name ¬ name;
naCache.distingName ¬ distingName;
naCache.address ¬ address;
};
SameName:
PROC [a, b: Name]
RETURNS [same:
BOOL] ~ {
same ¬ Rope.Equal[s1~a.object, s2~b.object, case~FALSE]
AND Rope.Equal[s1~a.domain, s2~b.domain, case~FALSE]
AND Rope.Equal[s1~a.organization, s2~b.organization, case~FALSE];
};
NACacheLookup:
PUBLIC ENTRY
PROC [name: Name]
RETURNS [found:
BOOL, distingName: Name, address:
XNS.Address] ~ {
p: NACacheEntry ¬ naCache;
THROUGH [0..naCacheSize)
DO
IF SameName[p.name, name]
OR SameName[p.distingName, name]
THEN RETURN [TRUE, p.distingName, p.address];
p ¬ p.next;
ENDLOOP;
RETURN [FALSE, [NIL,NIL,NIL], XNS.unknownAddress];
};
NACacheRollback:
ENTRY
PROC ~ {
p: NACacheEntry ¬ naCache;
naCache ¬ NIL;
naCacheSize ¬ 0;
WHILE p #
NIL
DO
nextp: NACacheEntry ~ p.next;
p.next ¬ NIL;
p ¬ nextp;
ENDLOOP;
};
minutesBetweenNACacheSweeps: INT ¬ 10;
minutesUntilNACacheSweep: INT ¬ minutesBetweenNACacheSweeps;
NACacheSweep:
ENTRY
PROC ~ {
Periodically delete the oldest cache entry.
IF (minutesUntilNACacheSweep ¬ minutesUntilNACacheSweep.PRED) > 0 THEN RETURN;
minutesUntilNACacheSweep ¬ minutesBetweenNACacheSweeps;
SELECT naCacheSize
FROM
0 => NULL;
1 => { naCache.next ¬ NIL; naCache ¬ NIL; naCacheSize ¬ 0 };
> 1 => { naCache.next ¬ naCache.next.next; naCacheSize ¬ naCacheSize.PRED };
ENDCASE => ERROR;
};
Conversations
Conversation: TYPE ~ REF ConversationObject;
ConversationObject:
PUBLIC
TYPE ~ XNSCHConcreteTypes.ConversationObject;
InitiateConversation:
PUBLIC
PROC [identity: XNSAuth.Identity, server: XNS.Address]
RETURNS [c: Conversation] ~ {
This never raises an error, but if identity is invalid it will substitute NullIdentity, so the resulting conversation will be able to do simple queries but not updates.
c ¬ NEW[ ConversationObject ¬ [] ];
Get conversation ...
IF identity = NIL THEN identity ¬ XNSCredentials.GetIdentity[];
IF identity #
NIL
THEN {
c.conversation ¬ XNSAuth.Initiate[identity, chServiceName
! XNSAuth.AuthenticationError, XNSAuth.CallError => CONTINUE];
};
IF c.conversation =
NIL
THEN {
identity ¬ XNSAuth.GetNullIdentity[];
c.conversation ¬ XNSAuth.Initiate[identity, chServiceName]; -- no ERROR
};
Get Courier handle for server, if specified ...
IF server #
XNS.unknownAddress
THEN {
c.handle ¬ CrRPC.CreateClientHandle[$CMUX, NEW[XNS.Address ¬ server]];
c.host ¬ server.host;
};
};
TerminateConversation:
PUBLIC
PROC [c: Conversation] ~ {
IF c.handle # NIL THEN CrRPC.DestroyClientHandle[c.handle];
XNSAuth.Terminate[c.conversation] };
GetAuthenticator:
PUBLIC
PROC [c: Conversation, host:
XNS.Host]
RETURNS [CHOps.Authenticator] ~ {
XNSAuth.SetRecipientHostNumber[c.conversation, host];
RETURN [ [XNSAuth.GetCredentials[c.conversation],
XNSAuth.GetNextVerifier[c.conversation]] ] };
IsGeneric:
PUBLIC
PROC [c: Conversation]
RETURNS [
BOOL] ~ {
RETURN [c.handle = NIL] };
Server Objects
CachedServer: TYPE ~ REF CachedServerObject;
CachedServerObject:
TYPE ~
RECORD [
address: REF XNS.Address, -- readonly, REF to conform to CrRPC.RefAddress
knowDomainsServed: BOOL ¬ FALSE, -- hint, no ML
inactiveUntil: BasicTime.GMT ¬ BasicTime.earliestGMT -- ML
];
numHeaders: CARDINAL ~ 101;
serversByAddress: RefTab.Ref ~ RefTab.Create[
mod~numHeaders, equal~EqualAddressesIgnoringSocket, hash~HashAddress];
HashAddress: RefTab.HashProc ~ {
host: XNS.Host ¬ NARROW[key, REF XNS.Address].host;
acc: CARDINAL ¬ ((((host.a*5+host.b)*5+host.c)*5+host.d)*5+host.e)*5+host.f;
RETURN [acc] };
EqualAddressesIgnoringSocket: RefTab.EqualProc ~ {
ra1: REF XNS.Address ¬ NARROW[key1];
ra2: REF XNS.Address ¬ NARROW[key2];
RETURN [(ra1.net = ra2.net) AND (ra1.host = ra2.host)] };
GetServerByAddress:
PROC [ra:
REF XNS.Address, makeActive:
BOOL]
RETURNS [s: CachedServer] ~ {
Net and host are significant, socket is set to XNS.unknownSocket
ENABLE UNWIND => NULL;
val: RefTab.Val;
found: BOOL;
[found, val] ¬ RefTab.Fetch[x~serversByAddress, key~ra];
IF
NOT found
THEN {
val ¬ NEW[CachedServerObject ¬ [address~ra]];
[] ¬ RefTab.Insert[x~serversByAddress, key~ra, val~val] };
s ¬ NARROW[val];
IF makeActive THEN MarkServerUsable[s];
};
secondsBusy: INT ¬ 10;
secondsDown: INT ¬ 600;
secondsDead: INT ¬ 900;
ServerIsUsableInternal:
INTERNAL
PROC [s: CachedServer]
RETURNS [usable:
BOOL] ~ {
usable ¬ (BasicTime.Period[from: s.inactiveUntil, to: BasicTime.Now[]] >= 0) };
MarkServerUsable:
ENTRY PROC [s: CachedServer] ~ {
ENABLE UNWIND => NULL;
s.inactiveUntil ¬ BasicTime.earliestGMT };
MarkServerBusy:
ENTRY PROC [s: CachedServer] ~ {
ENABLE UNWIND => NULL;
s.inactiveUntil ¬ BasicTime.Update[BasicTime.Now[], secondsBusy] };
MarkServerDown:
ENTRY PROC [s: CachedServer] ~ {
ENABLE UNWIND => NULL;
s.inactiveUntil ¬ BasicTime.Update[BasicTime.Now[], secondsDown];
s.knowDomainsServed ¬ FALSE };
MarkServerDead:
ENTRY PROC [s: CachedServer] ~ {
ENABLE UNWIND => NULL;
s.inactiveUntil ¬ BasicTime.Update[BasicTime.Now[], secondsDead];
s.knowDomainsServed ¬ FALSE };
Lists of Servers
CachedServerListHead: TYPE ~ REF CachedServerList;
CachedServerList: TYPE ~ REF CachedServerListElement;
CachedServerListElement:
TYPE ~
RECORD [
next: CachedServerList, -- ML
server: CachedServer -- readonly
];
AddServerToList:
ENTRY
PROC [listHead: CachedServerListHead,
server: CachedServer]
RETURNS [new:
BOOL] ~ {
ENABLE UNWIND => NULL;
new ¬ AddServerToListInternal[listHead, server] };
AddServerToListInternal:
INTERNAL
PROC [listHead: CachedServerListHead,
server: CachedServer]
RETURNS [new:
BOOL] ~ {
FOR p: CachedServerList ¬ listHead, p.next
WHILE p #
NIL
DO
IF p.server = server THEN RETURN [FALSE];
ENDLOOP;
listHead ¬ NEW[CachedServerListElement ¬ [next~listHead, server~server]];
RETURN [TRUE] };
DeleteServerFromListInternal:
INTERNAL
PROC [listHead: CachedServerListHead,
server: CachedServer] ~ {
p, prev: CachedServerList ¬ NIL;
IF listHead = NIL THEN RETURN;
p ¬ listHead;
WHILE (p # NIL) AND (p.server # server) DO prev ¬ p; p ¬ p.next ENDLOOP;
IF p = NIL THEN RETURN;
IF prev = NIL THEN listHead ¬ p ELSE prev.next ¬ p;
};
ServerFilterProc: TYPE ~ PROC [CachedServer] RETURNS [ok: BOOL];
GetBestServerFromList:
ENTRY PROC [listHead: CachedServerListHead,
filter: ServerFilterProc ¬
NIL]
RETURNS [bestServer: CachedServer ¬
NIL] ~ {
ENABLE UNWIND => NULL;
bestHops: CARDINAL ¬ LAST[CARDINAL];
serversAtBestHops: CARDINAL ¬ 0;
IF listHead = NIL THEN RETURN [NIL];
FOR p: CachedServerList ¬ listHead, p.next
WHILE p #
NIL
DO
server: CachedServer ~ p.server;
hops: CARDINAL;
IF NOT ServerIsUsableInternal[server] THEN LOOP;
hops ¬ XNSRouter.GetHops[server.address.net];
IF hops >= bestHops THEN LOOP;
IF hops >= XNSRouter.unreachable THEN LOOP; -- removed because XNSRouter.GetHops can lie about unreachability.
IF filter # NIL THEN IF NOT filter[server].ok THEN LOOP;
IF hops=bestHops
THEN {
serversAtBestHops ¬ serversAtBestHops.SUCC;
IF Random.ChooseInt[
NIL, 1, serversAtBestHops] = 1
THEN {
Choose randomly among the servers at the best distance
bestServer ¬ server;
};
}
ELSE {
bestServer ¬ server;
bestHops ¬ hops;
serversAtBestHops ¬ 1;
};
ENDLOOP;
};
Cache of Servers by Domain
domainServers: SymTab.Ref ~ SymTab.Create[mod~numHeaders, case~FALSE];
GetServersForDomain:
PROC [domain:
ROPE]
RETURNS [CachedServerListHead] ~ {
RETURN [NARROW[SymTab.Fetch[domainServers, domain].val]] };
AddServerForDomain:
ENTRY
PROC [domain:
ROPE, server: CachedServer] ~ {
ENABLE UNWIND => NULL;
listHead: CachedServerListHead;
listHead ¬ NARROW[SymTab.Fetch[domainServers, domain].val];
IF listHead =
NIL
THEN {
listHead ¬ NEW[CachedServerList ¬ NIL];
[] ¬ SymTab.Store[domainServers, domain, listHead] };
[] ¬ AddServerToListInternal[listHead, server] };
DeleteServerForDomain:
ENTRY
PROC [domain:
ROPE, server: CachedServer] ~ {
ENABLE UNWIND => NULL;
listHead: CachedServerListHead ~ NARROW[SymTab.Fetch[domainServers, domain].val];
DeleteServerFromListInternal[listHead, server] };
ListDomainsIKnow:
ENTRY
PROC
RETURNS [domains:
LIST
OF
ROPE] ~ {
For debugging.
EachPair:
INTERNAL SymTab.EachPairAction
-- [key, val] RETURNS[BOOL] -- ~ {
domains ¬ CONS[key, domains]; RETURN[FALSE] };
[] ¬ SymTab.Pairs[domainServers, EachPair];
};
LearnDomainsServed:
PROC [h: CrRPC.Handle, theServer: CachedServer] ~ {
c: Conversation;
ReadStreamOfDomainNames: CrRPC.BulkDataXferProc
[h: Handle, s: IO.STREAM, checkAbort: BulkDataCheckAbortProc] RETURNS [abort: BOOL];
~ {
EachDomainNameInStream: CrRPC.BulkDataValueProc
[s: IO.STREAM] RETURNS [abort: BOOL ← FALSE]
~ {
domPart, orgPart: ROPE;
orgPart ¬ CrRPC.GetRope[s];
domPart ¬ CrRPC.GetRope[s];
AddServerForDomain[
DomainKeyFromComponents[organization~orgPart, domain~domPart],
theServer];
The following is present because by convention every CH that serves a domain D:O also serves the "glue" domain O:CHServers, but won't include O:CHServers in the enumeration! I consider it BOGUS, but ... (ajd)
AddServerForDomain[
OrganizationKeyFromComponents[orgPart],
theServer];
};
RETURN [CrRPC.ReadBulkDataStream[h, s, checkAbort, EachDomainNameInStream]];
};
IF theServer.knowDomainsServed THEN RETURN;
c ¬ InitiateConversation[NIL, XNS.unknownAddress];
CHOps.ListDomainsServed[h, ReadStreamOfDomainNames,
GetAuthenticator[c, theServer.address.host]];
theServer.knowDomainsServed ¬ TRUE;
TerminateConversation[c];
};
DomainKeyFromComponents:
PROC [organization:
ROPE, domain:
ROPE]
RETURNS [key:
ROPE] ~
INLINE {
key ¬ Rope.Cat[domain, ":", organization] };
OrganizationKeyFromComponents:
PROC [organization:
ROPE]
RETURNS [key:
ROPE] ~
INLINE {
key ¬ Rope.Cat[organization, ":", shiftInName] };
OrganizationKeyFromDomainKey:
PROC [domainKey:
ROPE]
RETURNS [organizationKey:
ROPE] ~ {
e.g. "PARC:Xerox" -> "Xerox:CHServers"
pos: INT ~ Rope.Index[s1~domainKey, s2~":"] + 1;
organizationKey ¬ OrganizationKeyFromComponents[Rope.Substr[domainKey, pos]] };
Cache of Nearby Servers
nearbyServers: CachedServerListHead ~ NEW[CachedServerList ¬ NIL];
defaultMaxHops: CARDINAL ¬ 3;
desperationContactMaxHops: CARDINAL ¬ 5;
desperationBroadcastMaxHops: CARDINAL ¬ 4;
desperationBroadcastTryLimit: CARDINAL ¬ 2;
desperationBroadcastPauseLimit: CARDINAL ¬ 2;
desperationBroadcastPauseMsec:
CARD ¬ 8000;
These are necessary for machines with only 3Mb Ethernets, because it can take so long for the routing and translation caches to fill. They should go away after improvements are made in the underlying transport.
BroadcastForNearbyServers:
PROC [
maxHops:
CARDINAL, nWanted:
CARDINAL, tryLimit:
CARDINAL] ~ {
nGot: CARDINAL ¬ 0;
maxRetries: NAT ~ 3;
EachAddress: XNSServerLocation.EachAddressProc
-- [addr: XNS.Address] -- ~ {
server: CachedServer;
ra: REF XNS.Address ¬ NEW[XNS.Address ¬ [net~addr.net, host~addr.host, socket~XNS.unknownSocket]];
server ¬ GetServerByAddress[ra~ra, makeActive~TRUE];
IF AddServerToList[nearbyServers, server].new THEN nGot ¬ nGot.SUCC;
IF nGot = nWanted THEN ERROR XNSServerLocation.StopBroadcast[];
};
FOR i:
CARDINAL ¬ 0, i+1
DO
XNSServerLocation.LocateServers[socket~XNSWKS.clearinghouse, remotePgm~chPgmNum, remotePgmVersion~chVersionNum, eachAddress~EachAddress, maxHops~maxHops, tryLimit~tryLimit];
IF (nGot > 0) OR (i >= desperationBroadcastPauseLimit) THEN EXIT;
Process.PauseMsec[desperationBroadcastPauseMsec];
ENDLOOP;
};
NoticeKnownNearbyServers:
PROC[
maxHops:
CARDINAL, nWanted:
CARDINAL ¬
CARDINAL.
LAST] ~ {
nGot: CARDINAL ¬ 0;
EachServer: RefTab.EachPairAction
[key: Key, val: Val] RETURNS [quit: BOOL]
~ {
s: CachedServer ¬ NARROW[val];
IF XNSRouter.GetHops[s.address.net] > maxHops THEN RETURN [quit~FALSE];
IF AddServerToList[nearbyServers, s].new THEN nGot ¬ nGot.SUCC;
RETURN [quit~(nGot=nWanted)] };
[] ¬ RefTab.Pairs[x~serversByAddress, action~EachServer];
};
GetBestKnownServer:
PROC [maxHops:
CARDINAL, filter: ServerFilterProc]
RETURNS [bestServer: CachedServer ¬
NIL] ~ {
EachServer: RefTab.EachPairAction
[key: Key, val: Val] RETURNS [quit: BOOL]
~ {
s: CachedServer ¬ NARROW[val];
hops: CARDINAL;
IF BasicTime.Period[from: BasicTime.Now[], to: s.inactiveUntil] > 0 THEN RETURN;
hops ¬ XNSRouter.GetHops[s.address.net];
IF hops > maxHops THEN RETURN;
IF (filter # NIL) AND (NOT filter[s]) THEN RETURN;
maxHops ¬ hops;
bestServer ¬ s };
[] ¬ RefTab.Pairs[x~serversByAddress, action~EachServer];
};
GetSomeServer:
PROC [filter: ServerFilterProc ¬
NIL, okToBroadcast:
BOOL]
RETURNS [bestServer: CachedServer, didBroadcast:
BOOL ¬
FALSE] ~ {
bestServer ¬ GetBestServerFromList[nearbyServers, filter];
IF bestServer =
NIL
THEN {
NoticeKnownNearbyServers[maxHops~defaultMaxHops];
bestServer ¬ GetBestServerFromList[nearbyServers, filter] };
IF bestServer =
NIL
THEN {
bestServer ¬ GetBestKnownServer[desperationContactMaxHops, filter] };
IF (bestServer =
NIL)
AND okToBroadcast
THEN {
BroadcastForNearbyServers[maxHops~desperationBroadcastMaxHops, nWanted~1, tryLimit~desperationBroadcastTryLimit];
didBroadcast ¬ TRUE;
bestServer ¬ GetBestServerFromList[nearbyServers, filter] };
};
Hint Processing
maxHints: CARDINAL ~ 15;
ProcessHint:
PROC [h: CrRPC.Handle, theServer: CachedServer, hint: Name]
RETURNS [servers: CachedServerListHead] ~ {
c: Conversation;
nRead: CARDINAL;
hints: ARRAY [1..maxHints] OF ROPE ¬ ALL[NIL];
ReadStreamOfNames: CrRPC.BulkDataXferProc
[h: Handle, s: IO.STREAM, checkAbort: BulkDataCheckAbortProc] RETURNS [abort: BOOL];
~ {
EachNameInStream: CrRPC.BulkDataValueProc
[s: IO.STREAM] RETURNS [abort: BOOL ← FALSE]
~ {
serverName: Name ~ XNSCHPrivate.GetElement[s];
nRead ¬ nRead + 1;
IF nRead <= maxHints THEN { hints[nRead] ¬ serverName.object; RETURN };
IF Random.ChooseInt[NIL, 1, nRead] > maxHints THEN RETURN;
hints[Random.ChooseInt[NIL, 1, maxHints]] ¬ serverName.object;
};
RETURN [CrRPC.ReadBulkDataStream[h, s, checkAbort, EachNameInStream]];
};
c ¬ InitiateConversation[NIL, XNS.unknownAddress];
nRead ¬ 0;
[] ¬ CHOps.RetrieveMembers[h, hint, CHEntries.members, ReadStreamOfNames, GetAuthenticator[c, theServer.address.host]
! CHOps.ArgumentError, CHOps.PropertyError, CHOps.UpdateError => CONTINUE];
servers ¬ NEW[CachedServerList ¬ NIL];
FOR i:
CARDINAL
IN [1 ..
MIN[nRead, maxHints]]
DO
{
ENABLE CHOps.ArgumentError, CHOps.PropertyError, CHOps.UpdateError =>
CONTINUE;
item: Item;
addresses: LIST OF XNS.Address;
[value~item] ¬ CHOps.RetrieveItem[h,
[organization~shiftInName, domain~shiftInName, object~hints[i]], CHEntries.addressList, GetAuthenticator[c, theServer.address.host]];
addresses ¬ XNSCH.AddressesFromItem[item];
IF addresses #
NIL
THEN {
addr: XNS.Address;
ra: REF XNS.Address;
server: CachedServer;
addr ¬ XNSCH.BestAddressInList[addresses];
ra ¬ NEW[XNS.Address ¬ [net~addr.net, host~addr.host, socket~XNS.unknownSocket]];
server ¬ GetServerByAddress[ra~ra, makeActive~FALSE];
[] ¬ AddServerToList[servers, server] };
};
ENDLOOP;
TerminateConversation[c];
};
Invoking a remote procedure
maxTries: CARDINAL ~ 15;
triesToUse: CARDINAL ¬ 7; -- Bounds Fault if this exceeds maxTries
CallRemote:
PUBLIC
PROC [c: Conversation, proc: XNSCHPrivate.RemoteProc,
domain:
ROPE ¬
NIL, idempotent:
BOOL ¬
TRUE] ~ {
State: TYPE ~ { dom, hint, org, nearby, none };
state: State ¬ none;
organization: ROPE ¬ NIL;
theServer: CachedServer ¬ NIL;
theHandle: CrRPC.Handle ¬ NIL;
gotHint: BOOL;
theHint: Name;
hintServers: CachedServerListHead ¬ NIL;
specific: BOOL ~ NOT IsGeneric[c];
inClientProc: BOOL ¬ FALSE;
didBroadcast: BOOL ¬ FALSE;
nAlreadyUsed: CARDINAL ¬ 0;
alreadyUsedCache: ARRAY [0..maxTries) OF CachedServer;
NoteServerAlreadyUsed:
PROC [server: CachedServer] ~ {
Precondition: NOT ServerAlreadyUsed[server]
alreadyUsedCache[nAlreadyUsed] ¬ server;
nAlreadyUsed ¬ nAlreadyUsed + 1;
};
ServerMayBeUsed:
PROC [server: CachedServer]
RETURNS [
BOOL] ~ {
FOR i:
CARDINAL
IN [0..nAlreadyUsed)
DO
IF alreadyUsedCache[i] = server THEN RETURN [FALSE];
ENDLOOP;
RETURN [TRUE] };
CleanUp:
PROC ~ {
IF theHandle # NIL THEN CrRPC.DestroyClientHandle[theHandle];
theHandle ¬ NIL };
GetServerCandidate:
PROC ~ {
state ¬ none;
IF theHandle # NIL THEN { CrRPC.DestroyClientHandle[theHandle]; theHandle ¬ NIL };
theServer ¬ NIL;
IF
NOT Rope.IsEmpty[domain]
THEN {
state ¬ dom;
theServer ¬ GetBestServerFromList[GetServersForDomain[domain], ServerMayBeUsed];
};
IF theServer =
NIL
THEN {
state ¬ hint;
theServer ¬ GetBestServerFromList[hintServers, ServerMayBeUsed];
};
IF (theServer =
NIL)
AND (
NOT Rope.IsEmpty[domain])
THEN {
state ¬ org;
IF organization = NIL THEN organization ¬ OrganizationKeyFromDomainKey[domain];
theServer ¬ GetBestServerFromList[GetServersForDomain[organization], ServerMayBeUsed];
};
IF theServer =
NIL
THEN {
state ¬ nearby;
[theServer, didBroadcast] ¬ GetSomeServer[ServerMayBeUsed, (NOT didBroadcast)] };
IF theServer =
NIL
THEN {
state ¬ none;
RETURN };
theHandle ¬ CrRPC.CreateClientHandle[$CMUX, theServer.address];
};
Body of CallRemote ...
FOR tryNo:
CARDINAL
IN [1..triesToUse]
DO
ENABLE {
UNWIND => CleanUp[];
CHOps.AuthenticationError, CHACLOps.AuthenticationError => {
ERROR XNSCH.Error[credentialsInvalid, first]
};
CHOps.CallError, CHACLOps.CallError => {
SELECT problem
FROM
accessRightsInsufficient => {
ERROR XNSCH.Error[credentialsTooWeak, first] };
tooBusy => {
IF specific THEN ERROR XNSCH.Error[serverTooBusy, first];
MarkServerBusy[theServer];
LOOP };
serverDown => {
IF specific THEN ERROR XNSCH.Error[allDown, first];
If this server serves the right domain and can't answer, assume no other server can answer either. This is overly pessimistic. Doing better would be at least a little bit hard.
EXIT };
useCourier, other,
ENDCASE =>
-- can't happen -- {
IF specific THEN ERROR XNSCH.Error[unknown, first];
MarkServerDead[theServer];
LOOP };
};
CHOps.ArgumentError, CHACLOps.ArgumentError => {
code:
XNSCH.ErrorCode ~ (
SELECT problem
FROM
illegalProperty => illegalPropertyID,
illegalOrganizationName => illegalOrganizationName,
illegalDomainName => illegalDomainName,
illegalObjectName => illegalObjectName,
noSuchOrganization => noSuchOrganization,
noSuchDomain => noSuchDomain,
noSuchObject => noSuchObject
ENDCASE => unknown);
ERROR XNSCH.Error[code, IF which = first THEN first ELSE second] };
CHOps.PropertyError, CHACLOps.PropertyError => {
code:
XNSCH.ErrorCode ~ (
SELECT problem
FROM
missing => propertyIDNotFound,
wrongType => wrongPropertyType
ENDCASE => unknown);
ERROR XNSCH.Error[code, first] };
CHOps.UpdateError, CHACLOps.UpdateError => {
code:
XNSCH.ErrorCode ~ (
SELECT problem
FROM
noChange => noChange,
outOfDate => outOfDate,
objectOverflow => overflowOfName,
databaseOverflow => overflowOfDataBase
ENDCASE => unknown);
ERROR XNSCH.Error[code, IF which = first THEN first ELSE second] };
CrRPC.Error => {
SELECT errorReason
FROM
courierVersionMismatch => {
IF specific THEN ERROR XNSCH.Error[serviceNotExported, first];
MarkServerDead[theServer];
LOOP };
rejectedNoSuchProgram, rejectedNoSuchVersion, rejectedNoSuchProcedure, rejectedUnspecified => {
IF specific THEN ERROR XNSCH.Error[serviceNotExported, first];
MarkServerDown[theServer];
LOOP };
rejectedInvalidArgument => {
IF specific THEN ERROR XNSCH.Error[protocolError, first];
MarkServerDown[theServer];
LOOP };
remoteError, argsError, resultsError, bulkDataError, protocolError, remoteClose, communicationFailure, cantConnectToRemote =>
-- can't retry, progress made -- {
IF specific
THEN
ERROR
XNSCH.Error[
(
SELECT errorReason
FROM
communicationFailure => communicationFailure,
cantConnectToRemote => communicationFailure,
ENDCASE => protocolError),
first];
MarkServerDown[theServer];
IF (NOT idempotent) AND (inClientProc) THEN
ERROR XNSCH.Error[wasUpNowDown, first];
LOOP };
unknown, unknownClass, notImplemented, unknownOperation, notServerHandle, notClientHandle, addressInappropriateForClass
-- can't happen -- =>
ERROR;
ENDCASE => ERROR;
};
CHOps.WrongServer => {
IF specific THEN REJECT;
MarkServerDead[theServer];
LOOP };
CHACLOps.WrongServer => {
IF specific THEN ERROR CHOps.WrongServer[hint];
MarkServerDead[theServer];
LOOP };
};
IF specific
THEN {
inClientProc ¬ TRUE;
proc[c.handle, c.host];
inClientProc ¬ FALSE;
RETURN };
GetServerCandidate[]; -- sets [theServer, theHandle, state]
IF state = none THEN EXIT;
IF
NOT theServer.knowDomainsServed
THEN
LearnDomainsServed[theHandle, theServer];
gotHint ¬ FALSE;
inClientProc ¬ TRUE;
proc[theHandle, theServer.address.host
! CHOps.WrongServer, CHACLOps.WrongServer => {
theHint ¬ hint;
gotHint ¬ TRUE;
CONTINUE } ];
inClientProc ¬ FALSE;
IF gotHint
THEN {
DeleteServerForDomain[domain, theServer]; -- correct the cache
IF tryNo >= triesToUse THEN EXIT;
hintServers ¬ ProcessHint[theHandle, theServer, theHint];
NoteServerAlreadyUsed[theServer];
LOOP };
NoteServerAlreadyUsed[theServer];
AddServerForDomain[domain, theServer];
CleanUp[];
RETURN;
ENDLOOP;
CleanUp[];
ERROR XNSCH.Error[allDown, first];
};
Mainline Code
nNearbyServersToStart: CARDINAL ¬ 2;
Daemon:
PROC ~ {
DO
Process.PauseMsec[60000];
NACacheSweep[];
ENDLOOP;
};
Rollback: Booting.RollbackProc ~ {
TRUSTED { Process.Detach[FORK BroadcastForNearbyServers[maxHops~defaultMaxHops, nWanted~nNearbyServersToStart, tryLimit~0]] };
NACacheRollback[];
};
TRUSTED { Process.Detach[FORK Daemon[]] };
TRUSTED { Process.Detach[FORK BroadcastForNearbyServers[maxHops~defaultMaxHops, nWanted~nNearbyServersToStart, tryLimit~0]] };
Booting.RegisterProcs[r: Rollback];
}.