DIRECTORY
BasicTime USING [earliestGMT, GMT, latestGMT, Now, Period, Update],
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, EnableAborts, SecondsToTicks, SetTimeout],
Random USING [ChooseInt],
Rope USING [Cat, Index, IsEmpty, ROPE, Substr],
SymTab USING [Create, EachPairAction, Fetch, Pairs, Ref, Store],
XNS USING [Address, Host, unknownAddress, unknownHost, unknownSocket],
XNSAuth USING [Conversation, GetCredentials, GetNextVerifier, GetNullIdentity, Identity, Initiate, SetRecipientHostNumber, Terminate],
XNSCH USING [AddressesFromItem, BestAddressInList, Element, Error, ErrorCode, Item, Name, Pattern, Properties, PropertyID, Which],
XNSCHPrivate USING [GetElement, RemoteProc],
XNSRouter USING [GetHops, unreachable],
XNSServerLocation USING [EachAddressProc, LocateServers, StopBroadcast],
XNSWKS USING [clearinghouse]
;
XNSCHPrivateImpl:
CEDAR
MONITOR
IMPORTS BasicTime, CHOpsP2V3, CrRPC, Process, Random, Rope, SymTab, XNSAuth, XNSCH, XNSCHPrivate, XNSRouter, XNSServerLocation
EXPORTS XNSCH, XNSCHPrivate
~ {
OPEN CHEntries: CHEntriesP0V0, CHOps: CHOpsP2V3;
chPgmNum: CARD ~ 2;
chVersionNum: CARDINAL ~ 3;
CARD16: TYPE ~ CARDINAL; -- temporary until Mimosa ????
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"];
Conversations
Conversation: TYPE ~ REF ConversationObject;
ConversationObject:
PUBLIC
TYPE ~
RECORD [
conversation: XNSAuth.Conversation,
host: XNS.Host ← XNS.unknownHost,
handle: CrRPC.Handle ← NIL
];
InitiateConversation:
PUBLIC
PROC [identity: XNSAuth.Identity, server:
XNS.Address]
RETURNS [c: Conversation] ~ {
c ← NEW[ ConversationObject ← [] ];
IF identity = NIL THEN identity ← XNSAuth.GetNullIdentity[];
c.conversation ← XNSAuth.Initiate[identity, chServiceName];
IF server #
XNS.unknownAddress
THEN {
c.handle ← CrRPC.CreateClientHandle[$SPP, 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 [
next: CachedServer ← NIL, -- successor in serversByAddress. ML
address: REF XNS.Address, -- readonly, REF to conform to CrRPC.RefAddress
knowDomainsServed: BOOL ← FALSE, -- hint, no ML
referenced: BasicTime.GMT, -- ML
inactiveUntil: BasicTime.GMT -- ML
];
cachedServerTimeout: INT ← 8*LONG[3600]; -- seconds
infiniteTimeout: INT ← 07FFFFFFFH;
numHeaders: CARDINAL ~ 101;
HeaderIndex: TYPE ~ [0..numHeaders);
Headers: TYPE ~ REF HeadersObject;
HeadersObject: TYPE ~ ARRAY HeaderIndex OF CachedServer;
serversByAddress: Headers ~ NEW[HeadersObject ← ALL[NIL]];
HashHost:
PROC [host:
XNS.Host]
RETURNS [HeaderIndex] ~ {
acc: CARD ← ((((host.a*5+host.b)*5+host.c)*5+host.d)*5+host.e)*5+host.f;
RETURN [acc MOD numHeaders] };
GetServerByAddress:
ENTRY
PROC [addr:
XNS.Address, makeActive:
BOOL]
RETURNS [s: CachedServer] ~ {
Net and host are significant, socket is set to XNS.unknownSocket
ENABLE UNWIND => NULL;
index: HeaderIndex ~ HashHost[addr.host];
addr.socket ← XNS.unknownSocket;
FOR s ← serversByAddress^[index], s.next
WHILE s #
NIL
DO
IF s.address^ = addr
THEN {
IF makeActive THEN s.inactiveUntil ← BasicTime.earliestGMT;
RETURN };
ENDLOOP;
s ← serversByAddress^[index] ← NEW[CachedServerObject ←
[next~serversByAddress^[index], address~NEW[XNS.Address ← addr], referenced~BasicTime.Now[], inactiveUntil~BasicTime.earliestGMT]];
};
ApplyToAllServers:
INTERNAL
PROC [proc:
PROC[CachedServer]] ~ {
FOR index: HeaderIndex
IN [HeaderIndex.
FIRST .. HeaderIndex.
LAST]
DO
FOR s: CachedServer ← serversByAddress^[index], s.next
WHILE s #
NIL
DO
proc[s];
ENDLOOP;
ENDLOOP;
};
secondsBusy: INT ← 10;
secondsDown: INT ← 600;
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 };
MarkServerNonexistent:
ENTRY PROC [s: CachedServer] ~ {
ENABLE UNWIND => NULL;
s.inactiveUntil ← BasicTime.latestGMT;
s.knowDomainsServed ← FALSE };
ServerIsDead:
INTERNAL
PROC [s: CachedServer, timeout:
INT]
RETURNS [
BOOL] ~ {
IF s.inactiveUntil = BasicTime.latestGMT THEN RETURN [TRUE];
IF BasicTime.Period[from~s.referenced, to~BasicTime.Now[]] > timeout THEN RETURN [TRUE];
RETURN [FALSE] };
SweepAllServers:
ENTRY
PROC ~ {
p, prev: CachedServer;
FOR i: HeaderIndex
IN [HeaderIndex.
FIRST .. HeaderIndex.
LAST]
DO
p ← serversByAddress^[i]; prev ← NIL;
WHILE p #
NIL
DO
IF ServerIsDead[p, infiniteTimeout]
-- infinite => everybody believes it's dead
THEN {
p ← p.next; IF prev = NIL THEN serversByAddress^[i] ← p ELSE prev.next ← p }
ELSE {
prev ← p; p ← p.next };
ENDLOOP;
ENDLOOP;
};
Lists of Servers
CachedServerListHead: TYPE ~ REF CachedServerList;
CachedServerList: TYPE ~ REF CachedServerListElement;
CachedServerListElement:
TYPE ~
RECORD [
next: CachedServerList, -- ML
server: CachedServer -- readonly
];
ListIsEmpty: PROC [listHead: CachedServerListHead] RETURNS [isEmpty: BOOL] ~ {
RETURN [listHead^ = NIL] };
ListContains: ENTRY PROC [listHead: CachedServerListHead, server: CachedServer]
RETURNS [isOnList: BOOL] ~ {
IF listHead = NIL THEN RETURN [FALSE];
FOR s: CachedServerList ← listHead^, s.next DO
IF s = NIL THEN RETURN [FALSE];
IF s.server = server THEN RETURN [TRUE]
ENDLOOP;
};
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] };
AddNewServerToList: ENTRY PROC [listHead: CachedServerListHead, server: CachedServer] ~ {
Precondition: server is not already on list.
ENABLE UNWIND => NULL;
listHead^ ← NEW[CachedServerListElement ← [next~listHead^, server~server]];
};
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];
IF listHead = NIL THEN RETURN [NIL];
FOR p: CachedServerList ← listHead^, p.next
WHILE p #
NIL
DO
server: CachedServer ~ p.server;
hops: CARDINAL;
IF BasicTime.Period[from: BasicTime.Now[], to: server.inactiveUntil] > 0 THEN LOOP;
hops ← XNSRouter.GetHops[server.address.net];
IF hops >= bestHops THEN LOOP;
IF hops >= XNSRouter.unreachable THEN LOOP;
IF filter # NIL THEN IF NOT filter[server].ok THEN LOOP;
bestServer ← server;
bestHops ← hops;
ENDLOOP;
IF bestServer # NIL THEN bestServer.referenced ← BasicTime.Now[];
};
SweepList:
INTERNAL
PROC [listHead: CachedServerListHead, timeout:
INT] ~ {
Called by Daemon.
p, prev: CachedServerList ← NIL;
IF listHead = NIL THEN RETURN;
p ← listHead^;
WHILE p #
NIL
DO
IF ServerIsDead[p.server, timeout]
THEN { p ← p.next; IF prev = NIL THEN listHead^ ← p ELSE prev.next ← p }
ELSE { prev ← p; p ← p.next };
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] };
SweepDomainCache:
ENTRY
PROC ~ {
Called by Daemon.
EachPair:
INTERNAL SymTab.EachPairAction
-- [key, val] RETURNS[BOOL] -- ~ {
IF val # NIL THEN SweepList[NARROW[val], cachedServerTimeout];
RETURN[FALSE] };
[] ← SymTab.Pairs[domainServers, EachPair];
};
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, key: ROPE;
orgPart ← CrRPC.GetRope[s];
domPart ← CrRPC.GetRope[s];
key ← Rope.Cat[orgPart, ":", domPart];
AddServerForDomain[key, theServer];
AddServerForDomain[OrganizationFromDomain[key], theServer]; -- <- BOGUS ???
};
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];
};
OrganizationFromDomain:
PROC [domain:
ROPE]
RETURNS [organization:
ROPE] ~ {
e.g. "Xerox:PARC" -> "CHServers:Xerox"
len: INT ~ Rope.Index[s1~domain, s2~":"];
organization ← Rope.Cat[shiftInName, ":", Rope.Substr[domain, 0, len]] };
Cache of Nearby Servers
nearbyServers: CachedServerListHead ~ NEW[CachedServerList ← NIL];
defaultMaxHops: CARDINAL ← 3;
desperationContactMaxHops: CARDINAL ← 6;
desperationBroadcastMaxHops: CARDINAL ← 5;
desperationBroadcastTryLimit: CARDINAL ← 2;
sweepBroadcastTryLimit: CARDINAL ← 1;
maxHopsUsedForNearby: CARDINAL ← 0;
RememberHopsUsedForNearby:
ENTRY
PROC [hops:
CARDINAL] ~
INLINE {
maxHopsUsedForNearby ← MAX[maxHopsUsedForNearby, hops] };
GetHopsUsedForNearby:
ENTRY
PROC
RETURNS [hops:
CARDINAL] ~
INLINE {
hops ← maxHopsUsedForNearby; maxHopsUsedForNearby ← 0 };
BroadcastForNearbyServers:
PROC [
maxHops:
CARDINAL, nWanted:
CARDINAL ←
CARDINAL.
LAST, tryLimit:
CARDINAL ← 0] ~ {
nGot: CARDINAL ← 0;
EachAddress: XNSServerLocation.EachAddressProc
-- [addr: XNS.Address] -- ~ {
server: CachedServer;
server ← GetServerByAddress[addr~addr, makeActive~TRUE];
IF AddServerToList[nearbyServers, server].new THEN nGot ← nGot.SUCC;
IF nGot = nWanted THEN ERROR XNSServerLocation.StopBroadcast[] };
XNSServerLocation.LocateServers[socket~XNSWKS.clearinghouse, remotePgm~chPgmNum, remotePgmVersion~chVersionNum, eachAddress~EachAddress, maxHops~maxHops, tryLimit~tryLimit];
};
StopSearch: ERROR ~ CODE;
NoticeKnownNearbyServers:
ENTRY
PROC[
maxHops:
CARDINAL, nWanted:
CARDINAL ←
CARDINAL.
LAST] ~ {
nGot: CARDINAL ← 0;
EachServer:
INTERNAL
PROC [s: CachedServer] ~ {
IF XNSRouter.GetHops[s.address.net] > maxHops THEN RETURN;
IF AddServerToListInternal[nearbyServers, s].new THEN nGot ← nGot.SUCC;
IF nGot = nWanted THEN ERROR StopSearch[] };
ApplyToAllServers[proc~EachServer ! StopSearch => CONTINUE];
};
GetBestKnownServer:
ENTRY
PROC [maxHops:
CARDINAL, filter: ServerFilterProc]
RETURNS [bestServer: CachedServer ←
NIL] ~ {
EachServer:
INTERNAL
PROC [s: CachedServer] ~ {
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 };
ApplyToAllServers[proc~EachServer];
};
GetBestServer:
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] };
IF bestServer #
NIL
THEN RememberHopsUsedForNearby[XNSRouter.GetHops[bestServer.address.net]]
ELSE RememberHopsUsedForNearby[CARDINAL.LAST];
};
SweepNearbyServers:
PROC ~ {
Called by Daemon.
maxHopsUsed: CARDINAL;
EntrySweepNearbyServers[];
NoticeKnownNearbyServers[maxHops~defaultMaxHops];
IF (maxHopsUsed ← GetHopsUsedForNearby[]) > 0
THEN
TRUSTED {
Process.Detach[FORK BroadcastForNearbyServers[maxHops~MIN[maxHopsUsed, defaultMaxHops], tryLimit~sweepBroadcastTryLimit] ];
};
};
EntrySweepNearbyServers:
ENTRY
PROC ~ {
SweepList[nearbyServers, infiniteTimeout];
};
Hint Processing
maxHints: CARDINAL ~ 25;
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 {
server: CachedServer ~ GetServerByAddress[
addr~XNSCH.BestAddressInList[addresses], 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;
};
ServerUsable:
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;
theServer ← NIL;
IF theHandle # NIL THEN { CrRPC.DestroyClientHandle[theHandle]; theHandle ← NIL };
IF
NOT Rope.IsEmpty[domain]
THEN {
state ← dom;
theServer ← GetBestServerFromList[GetServersForDomain[domain], ServerUsable];
IF theServer =
NIL
THEN {
state ← hint;
theServer ← GetBestServerFromList[hintServers, ServerUsable] };
IF theServer =
NIL
THEN {
state ← org;
IF organization = NIL THEN organization ← OrganizationFromDomain[domain];
theServer ← GetBestServerFromList[GetServersForDomain[organization], ServerUsable];
};
};
IF theServer =
NIL
THEN {
state ← nearby;
[theServer, didBroadcast] ← GetBestServer[ServerUsable, (NOT didBroadcast)] };
IF theServer =
NIL
THEN {
state ← none;
RETURN };
theHandle ← CrRPC.CreateClientHandle[$SPP, theServer.address];
};
Body of CallRemote ...
FOR tryNo:
CARDINAL
IN [1..triesToUse]
DO
ENABLE {
UNWIND => CleanUp[];
CHOps.AuthenticationError => {
ERROR XNSCH.Error[credentialsInvalid, first]
};
CHOps.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];
MarkServerNonexistent[theServer];
LOOP };
};
CHOps.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 => {
code:
XNSCH.ErrorCode ~ (
SELECT problem
FROM
missing => propertyIDNotFound,
wrongType => wrongPropertyType
ENDCASE => unknown);
ERROR XNSCH.Error[code, first] };
CHOps.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];
MarkServerNonexistent[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;
MarkServerNonexistent[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 => { 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];
};
Daemon
daemonWakeup: CONDITION;
secondsBetweenSweeps: CARDINAL ← 900;
Daemon:
PROC ~ {
DO
WaitDaemonWakeup[];
SweepDomainCache[];
SweepNearbyServers[];
SweepAllServers[];
ENDLOOP;
};
WaitDaemonWakeup:
ENTRY
PROC ~ {
TRUSTED {
Process.EnableAborts[@daemonWakeup];
Process.SetTimeout[@daemonWakeup, Process.SecondsToTicks[secondsBetweenSweeps]] };
WAIT daemonWakeup;
};
Mainline Code
nNearbyServersToStart: CARDINAL ← 2;
TRUSTED { Process.Detach[FORK Daemon[]] };
TRUSTED { Process.Detach[FORK BroadcastForNearbyServers[maxHops~defaultMaxHops, nWanted~nNearbyServersToStart]] };
}.