SunYPAgentImpl.mesa
Copyright Ó 1989, 1991, 1992 by Xerox Corporation. All rights reserved.
Demers, November 28, 1988 10:47:03 am PST
Alan Ishigo November 29, 1988 2:24:19 pm PST
Willie-sue, January 18, 1993 3:27 pm PST
TODO: the null server stuff is baroque.
Chauser, January 3, 1992 11:07 am PST
DIRECTORY
Arpa USING [Address, MyAddress, nullAddress],
ArpaUDP USING [nullPort, Port],
Ascii USING [CR, FF, LF, SP, TAB],
Basics USING [HFromCard16, IsBound, LowHalf],
BasicTime USING [earliestGMT, GMT, Now, Period, TimeNotKnown, Update],
Convert USING [RopeFromArpaAddress],
Process USING [Detach, PauseMsec],
RefText USING [Append, New, ObtainScratch, ReleaseScratch],
Rope USING [FromRefText, Length, ROPE, Substr, ToRefText],
SunPMap USING [udpPort],
SunRPC USING [Destroy, Error, Handle],
SunRPCOnUDP USING [Create, SetRemote],
SunRPCAuth USING [Conversation, Error, Initiate, Terminate],
SunYP USING [EachMapNameProc, ResponseKeyVal, ResponseVal, Status],
SunYPAgent USING [TextSeq, TextSeqObject],
SunYPAgentPrivate USING [Object, ServerObject],
SunYPClient USING [First, Maplist, Match, Next, Null],
SunYPFind USING [Error, GetDefaultDomain, GetServerAddressAndPortForDomain],
SymTab USING [Create, Fetch, Ref, Store, Update, UpdateAction, Val],
ThisMachineRegistry USING [ThisMachineProcsRec, ThisMachineRef, RegisterThisMachineProcs]
;
SunYPAgentImpl: CEDAR MONITOR
LOCKS lock USING lock: Lock
IMPORTS Arpa, Basics, BasicTime, Convert, Process, RefText, Rope, SunRPC, SunRPCOnUDP, SunRPCAuth, SunYPClient, SunYPFind, SymTab, ThisMachineRegistry
EXPORTS SunYPAgent
~ {
Parameters
msecBetweenSweeps: CARD ¬ 11111;
initialServerTTL: CARDINAL ¬ 300;
initialCachedValueTTL: CARDINAL ¬ 300;
maxCachedValues: CARDINAL ¬ 31;
timeoutBase: CARD ¬ 500;
timeoutMsecPerHop: CARD ¬ 250;
pauseMsecPerHop: CARD ¬ 100;
maxTriesForSpecifiedServer: CARDINAL ¬ 8;
pauseMsecBetweenRetriesForSpecifiedServer: CARD ¬ 1000;
pMapPort: ArpaUDP.Port ¬ Basics.HFromCard16[Basics.LowHalf[SunPMap.udpPort]];
maxArgLength: CARDINAL ¬ 100;
hostsMapName: ROPE ¬ "hosts.byaddr";
hostNamePos: CARDINAL = 1;
Types
ROPE: TYPE ~ Rope.ROPE;
Lock: TYPE ~ REF LockObject;
LockObject: TYPE ~ MONITORED RECORD [];
Error: PUBLIC ERROR [code: ATOM] ~ CODE;
Server Cache
Server: TYPE ~ REF ServerObject;
ServerObject: TYPE ~ SunYPAgentPrivate.ServerObject;
serverTab: SymTab.Ref ¬ SymTab.Create[];
serverLock: Lock ~ NEW[LockObject];
serverList: Server ¬ NIL;
nullServerLock: Lock ~ NEW[LockObject];
nullServerList: Server ¬ NIL;
GetServerByAddressAndPort: ENTRY PROC [address: Arpa.Address, port: ArpaUDP.Port, lock: Lock ¬ serverLock] RETURNS [server: Server] ~ {
FOR server ¬ serverList, server.next
WHILE (server # NIL) AND ((server.address # address) OR (server.port # port))
DO NULL ENDLOOP;
IF server = NIL THEN {
server ¬ NEW[ServerObject ¬ [serverList, address, port, initialServerTTL, FALSE]];
serverList ¬ server;
};
};
SweepServers: ENTRY PROC [seconds: CARD, lock: Lock ¬ serverLock] ~ {
FOR server: Server ¬ serverList, server.next WHILE server # NIL DO
server.ttl ¬ (IF server.ttl > seconds THEN server.ttl - seconds ELSE 0);
ENDLOOP;
};
IsNullServer: PROC [server: Server] RETURNS [BOOL] ~ INLINE {
RETURN [server.address = Arpa.nullAddress] };
GetNullServer: ENTRY PROC [lock: Lock ¬ nullServerLock] RETURNS [server: Server] ~ {
server ¬ NEW[ServerObject ¬ [nullServerList, Arpa.nullAddress, ArpaUDP.nullPort, initialServerTTL, FALSE]];
nullServerList ¬ server;
};
SweepNullServers: ENTRY PROC [seconds: CARD, lock: Lock ¬ nullServerLock] ~ {
prev, server: Server;
prev ¬ NIL; server ¬ nullServerList;
WHILE server # NIL DO
IF server.ttl > seconds
THEN {
server.ttl ¬ server.ttl - seconds;
prev ¬ server;
}
ELSE {
server.ttl ¬ 0;
IF prev = NIL THEN nullServerList ¬ server.next ELSE prev.next ¬ server.next;
};
server ¬ server.next;
ENDLOOP;
};
GetServerForDomain: PROC [domain: ROPE] RETURNS [server: Server] ~ {
This never raises Error, may store a down server in serverTab.
server ¬ NARROW[SymTab.Fetch[serverTab, domain].val];
SELECT TRUE FROM
(server = NIL) => NULL;
(IsNullServer[server]) => IF server.ttl = 0 THEN server ¬ NIL;
(server.down) => server ¬ NIL;
(server.ttl = 0) AND (NOT ServerResponding[server]) => server ¬ NIL;
NOTE: ServerResponding[server] will set server.down appropriately.
ENDCASE;
IF server # NIL THEN RETURN;
-- IF server = NIL THEN -- {
address: Arpa.Address ¬ Arpa.nullAddress;
port: ArpaUDP.Port ¬ ArpaUDP.nullPort;
[address, port] ¬ SunYPFind.GetServerAddressAndPortForDomain[domain
! SunYPFind.Error => CONTINUE];
IF address # Arpa.nullAddress THEN server ¬ GetServerByAddressAndPort[address, port];
};
IF server = NIL THEN {
server ¬ GetNullServer[];
};
[] ¬ SymTab.Store[serverTab, domain, server];
};
GetServerByAddress: PROC [address: Arpa.Address, domain: ROPE]
RETURNS [server: Server] ~ {
Raises Error if the specified server can't be contacted or doesn't serve the specified domain.
errorCode: ATOM ¬ NIL;
port: ArpaUDP.Port;
[, port] ¬ SunYPFind.GetServerAddressAndPortForDomain[domain, address
! SunYPFind.Error => { errorCode ¬ code; CONTINUE } ];
IF errorCode = NIL
THEN {
server ¬ GetServerByAddressAndPort[address, port];
server.down ¬ FALSE;
server.ttl ¬ initialServerTTL;
[] ¬ SymTab.Store[serverTab, domain, server];
}
ELSE {
ERROR Error[errorCode];
};
};
ServerResponding: PROC [server: Server] RETURNS [responding: BOOL ¬ FALSE] ~ {
h: SunRPC.Handle;
c: SunRPCAuth.Conversation;
IF NOT IsNullServer[server] THEN {
ENABLE SunRPC.Error, SunRPCAuth.Error => CONTINUE;
h ¬ SunRPCOnUDP.Create[remoteAddress~server.address, remotePort~server.port];
c ¬ SunRPCAuth.Initiate[];
server.ttl ¬ initialServerTTL;
SunYPClient.Null[h, c];
responding ¬ TRUE;
};
server.down ¬ NOT responding;
IF h # NIL THEN SunRPC.Destroy[h]; h ¬ NIL;
IF c # NIL THEN SunRPCAuth.Terminate[c]; c ¬ NIL;
};
Handles
Handle: TYPE ~ REF Object;
Object: PUBLIC TYPE ~ SunYPAgentPrivate.Object;
ObtainHandle: PUBLIC PROC [domainName: ROPE, conversation: SunRPCAuth.Conversation, serverAddress: Arpa.Address] RETURNS [h: Handle] ~ {
server: Server ¬ NIL;
rpcHandle: SunRPC.Handle;
serverSpecified: BOOL ¬ (serverAddress # Arpa.nullAddress);
IF domainName = NIL THEN domainName ¬ SunYPFind.GetDefaultDomain[];
IF serverSpecified
THEN {
FOR try: CARDINAL ¬ 0, try.SUCC DO
server ¬ GetServerByAddress[serverAddress, domainName
! Error => IF try >= maxTriesForSpecifiedServer THEN REJECT ELSE CONTINUE];
IF server # NIL THEN EXIT;
Process.PauseMsec[pauseMsecBetweenRetriesForSpecifiedServer];
ENDLOOP;
}
ELSE {
server ¬ GetServerForDomain[domainName];
IF IsNullServer[server] THEN ERROR Error[$domainNotFound];
};
IF conversation = NIL THEN conversation ¬ SunRPCAuth.Initiate[];
rpcHandle ¬ SunRPCOnUDP.Create[remoteAddress~server.address, remotePort~server.port];
h ¬ NEW[Object ¬ [domain~domainName, server~server, rpcHandle~rpcHandle, conversation~conversation, serverSpecified~serverSpecified]];
};
RefreshHandle: PUBLIC PROC [h: Handle, conversation: SunRPCAuth.Conversation, serverAddress: Arpa.Address] ~ {
server: Server;
IF serverAddress = Arpa.nullAddress
THEN {
server ¬ GetServerForDomain[h.domain];
IF IsNullServer[server] THEN ERROR Error[$domainNotFound];
}
ELSE {
server ¬ GetServerByAddress[serverAddress, h.domain];
};
SunRPCAuth.Terminate[h.conversation];
h.conversation ¬ IF conversation # NIL THEN conversation ELSE SunRPCAuth.Initiate[];
IF server # h.server THEN {
h.server ¬ server;
h.rpcHandle ¬ SunRPCOnUDP.SetRemote[h.rpcHandle, server.address, server.port];
};
};
ReleaseHandle: PUBLIC PROC [h: Handle] ~ {
IF h.rpcHandle # NIL THEN { SunRPC.Destroy[h.rpcHandle]; h.rpcHandle ¬ NIL };
h.server ¬ NIL;
IF h.conversation # NIL THEN { SunRPCAuth.Terminate[h.conversation]; h.conversation ¬ NIL };
};
FixupForDownServer: PROC [h: Handle] RETURNS [fixed: BOOL ¬ FALSE] ~ {
ENABLE Error => CONTINUE;
oldServer: Server;
IF h.serverSpecified THEN RETURN;
oldServer ¬ h.server;
h.server ¬ GetServerForDomain[h.domain];
IF IsNullServer[h.server] THEN RETURN;
IF h.server.address = oldServer.address THEN RETURN;
IF h.server.down THEN RETURN;
fixed ¬ TRUE;
};
Match Response Cache
CachedValue: TYPE ~ REF CachedValueObject;
CachedValueObject: TYPE ~ RECORD [
next, prev: CachedValue,
ttl: CARDINAL ¬ initialCachedValueTTL,
busy: CARDINAL ¬ 0,
value: REF TEXT,
key: ROPE,
table: SymTab.Ref
];
cachedValueLock: Lock ~ NEW[LockObject];
cachedValueHead, cachedValueTail: CachedValue;
numCachedValues: CARD ¬ 0;
cachedValueAvailable: CONDITION;
tabByDomain: SymTab.Ref ~ SymTab.Create[];
RemoveCachedValueFromTable: PROC [cV: CachedValue] ~ {
value: SymTab.Val ~ cV;
DeleteValue: SymTab.UpdateAction -- [found, val] RETURNS [op, new] -- ~ {
IF found AND val = value
THEN RETURN [delete, NIL]
ELSE RETURN [none, NIL];
};
IF (cV.table = NIL) OR (cV.key = NIL) THEN ERROR;
SymTab.Update[cV.table, cV.key, DeleteValue];
};
RemoveCachedValueFromList: INTERNAL PROC [cV: CachedValue] ~ {
IF cV = cachedValueTail
THEN cachedValueTail ¬ cV.prev
ELSE cV.next.prev ¬ cV.prev;
IF cV = cachedValueHead
THEN cachedValueHead ¬ cV.next
ELSE cV.prev.next ¬ cV.next;
};
LookupCachedValue: ENTRY PROC [domainName, mapName, key: ROPE, lock: Lock ¬ cachedValueLock] RETURNS [cV: CachedValue] ~ {
tabByMap, tabByKey: SymTab.Ref;
GetTabByMap: SymTab.UpdateAction -- [found, val] RETURNS [op, new] -- ~ {
IF found
THEN { tabByMap ¬ NARROW[val]; RETURN [none, NIL] }
ELSE { tabByMap ¬ SymTab.Create[]; RETURN [store, tabByMap] };
};
GetTabByKey: SymTab.UpdateAction -- [found, val] RETURNS [op, new] -- ~ {
IF found
THEN { tabByKey ¬ NARROW[val]; RETURN [none, NIL] }
ELSE { tabByKey ¬ SymTab.Create[]; RETURN [store, tabByKey] };
};
SymTab.Update[tabByDomain, domainName, GetTabByMap];
SymTab.Update[tabByMap, mapName, GetTabByKey];
cV ¬ NARROW[SymTab.Fetch[tabByKey, key].val];
SELECT TRUE FROM
(cV = NIL) => {
DO
SELECT TRUE FROM
(numCachedValues < maxCachedValues) => {
cV ¬ NEW[CachedValueObject];
numCachedValues ¬ numCachedValues + 1;
EXIT;
};
(cachedValueHead = NIL) => {
WAIT cachedValueAvailable;
LOOP;
};
ENDCASE => {
cV ¬ cachedValueTail;
RemoveCachedValueFromTable[cV];
RemoveCachedValueFromList[cV];
EXIT;
};
ENDLOOP;
cV.ttl ¬ initialCachedValueTTL;
cV.key ¬ key;
cV.value ¬ NIL;
cV.table ¬ tabByKey;
};
(cV.busy = 0) => {
RemoveCachedValueFromList[cV];
};
ENDCASE;
cV.busy ¬ cV.busy + 1;
};
CacheValue: ENTRY PROC [cV: CachedValue, newValue: REF TEXT ¬ NIL, lock: Lock ¬ cachedValueLock] ~ {
IF newValue # NIL THEN {
IF cV.busy # 1 THEN ERROR;
cV.value ¬ newValue;
[] ¬ SymTab.Store[cV.table, cV.key, cV];
};
IF (cV.busy ¬ cV.busy - 1) = 0 THEN {
IF cachedValueHead = NIL
THEN {
cV.next ¬ cV.prev ¬ NIL;
cachedValueHead ¬ cachedValueTail ¬ cV;
NOTIFY cachedValueAvailable;
}
ELSE {
cV.next ¬ cachedValueHead;
cV.prev ¬ NIL;
cachedValueHead.prev ¬ cV;
cachedValueHead ¬ cV;
};
};
};
FreeCachedValue: INTERNAL PROC [cV: CachedValue, lock: Lock] ~ {
cV.key ¬ NIL;
cV.value ¬ NIL;
cV.table ¬ NIL;
cV.next ¬ cV.prev ¬ NIL;
numCachedValues ¬ numCachedValues - 1;
NOTIFY cachedValueAvailable;
};
ReleaseCacheEntry: ENTRY PROC [cV: CachedValue, lock: Lock ¬ cachedValueLock] ~ {
IF cV.busy # 1 THEN ERROR;
FreeCachedValue[cV, lock];
};
SweepCachedValues: ENTRY PROC [seconds: CARD, lock: Lock ¬ cachedValueLock] ~ {
p, next: CachedValue;
FOR p ¬ cachedValueHead, next WHILE p # NIL DO
next ¬ p.next;
IF p.ttl > seconds
THEN {
p.ttl ¬ p.ttl - seconds;
}
ELSE {
IF p.busy # 0 THEN ERROR;
RemoveCachedValueFromTable[p];
RemoveCachedValueFromList[p];
FreeCachedValue[p, lock];
};
ENDLOOP;
};
Public Procedures
MapErrorCode: PROC [code: ATOM, h: Handle] RETURNS [mappedCode: ATOM] ~ {
h.server.down ¬ TRUE;
h.server.ttl ¬ initialServerTTL;
mappedCode ¬ code;
};
MapErrorStatus: PROC [status: SunYP.Status, h: Handle] RETURNS [mappedCode: ATOM] ~ {
h.server.down ¬ FALSE;
h.server.ttl ¬ initialServerTTL;
mappedCode ¬ (SELECT status FROM
true => NIL,
nomore => $noMoreEntries,
nomap => $mapNotFound,
nodomain => $domainNotFound,
nokey => $keyNotFound,
ENDCASE => $protocol);
};
Match: PUBLIC PROC [h: Handle, map: ROPE, key: ROPE] RETURNS [val: REF TEXT] ~ {
cV: CachedValue;
errorCode: ATOM;
response: SunYP.ResponseVal;
cV ¬ LookupCachedValue[h.domain, map, key];
IF cV.value # NIL THEN {
val ¬ cV.value;
CacheValue[cV];
RETURN;
};
{
ENABLE SunRPC.Error => {
errorCode ¬ MapErrorCode[code, h];
IF FixupForDownServer[h] THEN RETRY ELSE CONTINUE;
};
response ¬ SunYPClient.Match[h.rpcHandle, h.conversation, [domain~h.domain, map~map, key~Rope.ToRefText[key]]];
errorCode ¬ MapErrorStatus[response.status, h];
};
IF errorCode = NIL
THEN {
val ¬ response.val;
CacheValue[cV, val];
RETURN;
}
ELSE {
ReleaseCacheEntry[cV];
ERROR Error[errorCode];
};
};
First: PUBLIC PROC [h: Handle, map: ROPE] RETURNS [key: ROPE, val: REF TEXT] ~ {
response: SunYP.ResponseKeyVal;
errorCode: ATOM ¬ NIL;
{
ENABLE SunRPC.Error => {
errorCode ¬ MapErrorCode[code, h];
IF FixupForDownServer[h] THEN RETRY ELSE CONTINUE;
};
response ¬ SunYPClient.First[h.rpcHandle, h.conversation, [domain~h.domain, map~map]];
errorCode ¬ MapErrorStatus[response.status, h];
};
IF errorCode # NIL THEN ERROR Error[errorCode];
RETURN [Rope.FromRefText[response.key], response.val];
};
Next: PUBLIC PROC [h: Handle, map: ROPE, keyBefore: ROPE]
RETURNS [key: ROPE, val: REF TEXT] ~ {
response: SunYP.ResponseKeyVal;
errorCode: ATOM ¬ NIL;
{
ENABLE SunRPC.Error => {
errorCode ¬ MapErrorCode[code, h];
IF FixupForDownServer[h] THEN RETRY ELSE CONTINUE;
};
response ¬ SunYPClient.Next[h.rpcHandle, h.conversation, [domain~h.domain, map~map, key~Rope.ToRefText[keyBefore]]];
errorCode ¬ MapErrorStatus[response.status, h];
};
IF errorCode # NIL THEN ERROR Error[errorCode];
RETURN [Rope.FromRefText[response.key], response.val];
};
MapList: PUBLIC PROC [h: Handle, eachMap: SunYP.EachMapNameProc] ~ {
status: SunYP.Status;
errorCode: ATOM ¬ NIL;
gotOne: BOOL ¬ FALSE;
eachMapOuter: SunYP.EachMapNameProc -- [map] -- ~ {
gotOne ¬ TRUE;
RETURN [eachMap[map]];
};
{
ENABLE SunRPC.Error => {
errorCode ¬ MapErrorCode[code, h];
IF (NOT gotOne) AND FixupForDownServer[h] THEN RETRY ELSE CONTINUE;
};
status ¬ SunYPClient.Maplist[h.rpcHandle, h.conversation, h.domain, eachMapOuter];
errorCode ¬ MapErrorStatus[status, h];
};
IF errorCode # NIL THEN ERROR Error[errorCode];
};
Utilities
ExpandTextSeq: PROC [s: SunYPAgent.TextSeq] RETURNS [new: SunYPAgent.TextSeq] ~ {
new ¬ NEW[SunYPAgent.TextSeqObject[2*s.maxLength]];
new.length ¬ s.length;
FOR i: CARDINAL IN [0 .. s.length) DO
new.refText[i] ¬ s.refText[i];
ENDLOOP;
};
IsWhiteSpace: PROC [c: CHAR] RETURNS [isWhite: BOOL] ~ {
OPEN Ascii;
isWhite ¬ (SELECT c FROM
TAB, LF, FF, CR, SP => TRUE,
ENDCASE => FALSE);
};
TokenizeUsingSeparator: PUBLIC PROC [in: REF READONLY TEXT, sepChar: CHAR, trimLeadingSpace, trimTrailingSpace: BOOL] RETURNS [out: SunYPAgent.TextSeq]
~ {
i, iTo, len: CARDINAL;
scratch: REF TEXT ¬ RefText.ObtainScratch[in.length];
CountSeparators: PROC [txt: REF READONLY TEXT, sepChar: CHAR] RETURNS [ct: CARDINAL ¬ 0] = {
FOR k: CARDINAL IN [0..txt.length) DO
IF txt[k] = sepChar THEN ct ¬ ct + 1;
ENDLOOP;
};
len ¬ CountSeparators[in, sepChar] + 1;
out ¬ NEW[SunYPAgent.TextSeqObject[len]];
out.length ¬ 0;
i ¬ 0;
DO
IF i >= in.length THEN EXIT;
IF trimLeadingSpace AND IsWhiteSpace[in[i]] THEN { i ¬ i + 1; LOOP };
iTo ¬ 0;
DO
IF (i >= in.length) OR (in[i] = sepChar) THEN EXIT;
scratch[iTo] ¬ in[i];
i ¬ i + 1;
iTo ¬ iTo + 1;
ENDLOOP;
IF trimTrailingSpace THEN
WHILE (iTo > 0) AND IsWhiteSpace[scratch[iTo-1]] DO iTo ¬ iTo - 1 ENDLOOP;
scratch.length ¬ iTo;
IF out.length >= out.maxLength THEN out ¬ ExpandTextSeq[out];
out.refText[out.length] ¬ RefText.Append[to~RefText.New[scratch.length], from~scratch];
out.length ¬ out.length + 1;
i ¬ i + 1;
ENDLOOP;
Make sure each element of the seq is not NIL ... must have at least have a zero length rope
scratch.length ¬ 0;
FOR k: CARDINAL DECREASING IN [0..len) DO
IF out.refText[k] = NIL THEN {
out.refText[k] ¬ RefText.Append[to~RefText.New[scratch.length], from~scratch];
} ELSE EXIT;
ENDLOOP;
RefText.ReleaseScratch[scratch];
out.length ¬ len;
};
Tokenize: PUBLIC PROC [in: REF READONLY TEXT]
RETURNS [out: SunYPAgent.TextSeq]
~ {
i, iTo: CARDINAL;
scratch: REF TEXT ¬ RefText.ObtainScratch[in.length];
out ¬ NEW[SunYPAgent.TextSeqObject[8]];
out.length ¬ 0;
i ¬ 0;
DO
IF i >= in.length THEN EXIT;
iTo ¬ 0;
DO
IF i >= in.length THEN EXIT;
IF IsWhiteSpace[in[i]] THEN EXIT;
scratch[iTo] ¬ in[i];
i ¬ i + 1;
iTo ¬ iTo + 1;
ENDLOOP;
scratch.length ¬ iTo;
IF out.length >= out.maxLength THEN out ¬ ExpandTextSeq[out];
out.refText[out.length] ¬ RefText.Append[to~RefText.New[scratch.length], from~scratch];
out.length ¬ out.length + 1;
i ¬ i + 1;
WHILE (i < in.length) AND (IsWhiteSpace[in[i]]) DO i ¬ i + 1 ENDLOOP;
ENDLOOP;
RefText.ReleaseScratch[scratch];
};
Sweep Daemon and Rollback
lastSweepTime: BasicTime.GMT ¬ BasicTime.earliestGMT;
Daemon: PROC ~ {
DO
thisSweepTime: BasicTime.GMT;
secondsSinceLastSweep: INT;
secs: CARD; -- signed/unsigned ambiguity ... bletch!
thisSweepTime ¬ BasicTime.Now[
! BasicTime.TimeNotKnown => {
thisSweepTime ¬ BasicTime.Update[lastSweepTime, (msecBetweenSweeps+999)/1000];
CONTINUE;
}
];
secondsSinceLastSweep ¬ BasicTime.Period[from~lastSweepTime, to~thisSweepTime];
IF secondsSinceLastSweep < 0 THEN ERROR;
secs ¬ secondsSinceLastSweep;
SweepCachedValues[secs];
SweepServers[secs];
SweepNullServers[secs];
Process.PauseMsec[msecBetweenSweeps];
ENDLOOP;
};
Rollback: Booting.RollbackProc ~ {
SweepCachedValues[CARD.LAST];
SweepServers[CARD.LAST];
SweepNullServers[CARD.LAST];
};
Proc passed to ThisMachineRegistry to register the IP name proc with the ThisMachine interface
IPNameProc: PROC RETURNS [name: ROPE] = {
ENABLE Error => GOTO NoYP;
h: Handle ¬ NIL;
addressRope, unbracketedAddressRope: ROPE;
hostsEntry: REF TEXT;
seq: SunYPAgent.TextSeq;
h ¬ ObtainHandle[NIL, NIL, Arpa.nullAddress];
addressRope ¬ Convert.RopeFromArpaAddress[Arpa.MyAddress[]];
unbracketedAddressRope ¬ Rope.Substr[addressRope, 1, Rope.Length[addressRope]-2];
hostsEntry ¬ Match[h, hostsMapName, unbracketedAddressRope];
seq ¬ Tokenize[hostsEntry];
IF seq.length <= hostNamePos THEN GOTO NoYP;
name ¬ Rope.FromRefText[seq.refText[hostNamePos]];
IF h # NIL THEN ReleaseHandle[h];
EXITS
NoYP => NULL;
};

Proc will be passed to ThisMachineRegistry to register with the ThisMachine interface.
IPAddressProc: PROC RETURNS [ROPE] = {
RETURN[Convert.RopeFromArpaAddress[Arpa.MyAddress[]]];
};
Init: PROC = {
thisMachineProcs: ThisMachineRegistry.ThisMachineRef ¬
NEW[ThisMachineRegistry.ThisMachineProcsRec ¬ [
which: $ip,
Name: IPNameProc,
Address: IPAddressProc,
ProcessorID: NIL
]];
IF Basics.IsBound[LOOPHOLE[ThisMachineRegistry.RegisterThisMachineProcs]] THEN
ThisMachineRegistry.RegisterThisMachineProcs[thisMachineProcs];
};
Mainline code
TRUSTED { Process.Detach[ FORK Daemon[] ] };
Init[];
Booting.RegisterProcs[r: Rollback];
}...