-- Copyright (C) 1983, 1984, 1985 by Xerox Corporation. All rights reserved.
-- TimeServerClockImpl.mesa, KAM, 8-May-85 13:36:41, If passed a broadcast address, ResetClock will use the best value from that network. SurveyorProcess now can be "safely" aborted, so that it will terminate only if started is FALSE. I left the fix of 18-Nov-83 in, since it really was only an optimization.
-- TimeServerClockImpl.mesa, HGM, 15-Jan-84 1:39:02, 1 sec of Hysteresis in AdjustClockEntry
-- TimeServerClockImpl.mesa, HGM, 4-Dec-83 20:15:24, Bypass LOOP in SystemImpl.AdjustClock
-- TimeServerClockImpl.mesa, HGM, 19-Nov-83 12:32:04, Clone of TimeServerLog
-- TimeServerClockImpl.mesa, HGM, 19-Nov-83 11:00:06, =/# mixup calling LogClockChange
-- TimeServerClockImpl.mesa, HGM, 18-Nov-83 14:31:36, Patch out Abort in SetParameters
-- TimeServerClockImpl.mesa, KAM, 23-Sep-83 12:25:45
DIRECTORY
Buffer USING [NSBuffer],
Inline USING [DIVMOD, LowHalf],
NSConstants USING [timeServerSocket],
PacketExchange USING [ExchangeClientType],
Process USING [
Abort, Pause, priorityBackground, SecondsToTicks, SetPriority, Ticks],
ProcessorFace USING [SetGreenwichMeanTime],
Router USING [
endEnumeration, EnumerateRoutingTable, FillRoutingTable, FindMyHostID,
GetDelayToNet, infinity, NoTableEntryForNet, startEnumeration],
Socket USING [
AssignNetworkAddress, BroadcastPacketToAllConnectedNets, ChannelHandle,
Create, Delete, GetPacket, GetSendBuffer, PutPacket, ReturnBuffer,
SetPacketWords, SetWaitTime, TimeOut],
SpecialSystem USING [AdjustClock, clockSlipping, SetBackTooFar, TimeLastSet],
System USING [
AdjustGreenwichMeanTime, broadcastHostNumber, GetClockPulses,
GetGreenwichMeanTime, gmtEpoch, GreenwichMeanTime, HostNumber, Microseconds,
NetworkAddress, NetworkNumber, nullNetworkAddress, nullNetworkNumber,
Pulses, PulsesToMicroseconds, SecondsSinceEpoch],
TimeServerClock USING [Error, Rate, undefined],
TimeServerFormat USING [LongToWire, TSPacket, Version, WireToGMT, WireToLong],
TimeServerLog USING [LogClockChange, LogInconsistancy];
TimeServerClockImpl: MONITOR
IMPORTS
Inline, Process, ProcessorFace, Router, Socket, SpecialSystem, System,
TimeServerFormat, TimeServerLog
EXPORTS TimeServerClock =
BEGIN
-- Initial values to these variables are assigned by the procedure Init
tsError: TimeServerClock.Error;
tsDrift, tsWobble: TimeServerClock.Rate;
tsResetPeriod: CARDINAL;
clockInvalid: BOOLEAN ← FALSE; -- set if SpecialSystem.SetBackTooFar is raised
ignoringServer: BOOLEAN ← FALSE;
ignoringServerAddress: System.HostNumber;
tsSourceLastReset: System.NetworkAddress ← System.nullNetworkAddress;
tsLastChange: LONG INTEGER ← 0;
tsVersion: LONG CARDINAL ← 0;
started: BOOLEAN ← FALSE;
surveyorProcess: PROCESS;
lastID: LONG CARDINAL ← 1;
oneSecond: Process.Ticks = Process.SecondsToTicks[1];
triesPerNetwork: CARDINAL = 3;
resetTries: CARDINAL = 10;
Inconsistent: ERROR = CODE;
Invalid: PUBLIC SIGNAL = CODE;
-- public procedures
AdjustClock: PUBLIC PROCEDURE [
amount: LONG INTEGER, error: TimeServerClock.Error, newVersion: BOOLEAN] =
BEGIN
UpVersion: ENTRY PROCEDURE [update: BOOLEAN] RETURNS [LONG CARDINAL] = {
RETURN[IF update THEN SUCC[tsVersion] ELSE tsVersion]};
AdjustClockEntry[
amount, error, UpVersion[newVersion], 0, System.nullNetworkAddress, TRUE];
END;
GetParameters: PUBLIC ENTRY PROCEDURE RETURNS [
drift, wobble: TimeServerClock.Rate,
resetPeriod: CARDINAL, timeLastReset: System.GreenwichMeanTime,
sourceLastReset: System.NetworkAddress, changeLastReset: LONG INTEGER] = {
RETURN[
tsDrift, tsWobble, tsResetPeriod, SpecialSystem.TimeLastSet[],
tsSourceLastReset, tsLastChange]};
Read: PUBLIC ENTRY PROCEDURE RETURNS [
time: System.GreenwichMeanTime, error: TimeServerClock.Error,
accurate: BOOLEAN, version: LONG CARDINAL] =
BEGIN
IF clockInvalid THEN SIGNAL Invalid;
error ← ErrorInternal[];
RETURN[
System.GetGreenwichMeanTime[], error,
(error # TimeServerClock.undefined) AND NOT SpecialSystem.clockSlipping,
tsVersion];
END;
ResetClock: PUBLIC PROCEDURE [from: System.NetworkAddress]
RETURNS [reset: BOOLEAN ← FALSE] =
BEGIN
cH: Socket.ChannelHandle ← Socket.Create[Socket.AssignNetworkAddress[]];
b: Buffer.NSBuffer ← NIL;
Socket.SetWaitTime[cH, WaitForHops[Router.GetDelayToNet[
from.net ! Router.NoTableEntryForNet => {
Socket.Delete[cH]; GOTO NoReset}]]];
IF from.host # System.broadcastHostNumber THEN
BEGIN ENABLE UNWIND => {
IF b # NIL THEN Socket.ReturnBuffer[b]; Socket.Delete[cH]};
time: LONG INTEGER;
error, delay, version: LONG CARDINAL;
sendMsec: System.Microseconds;
MaxVersion: ENTRY PROCEDURE RETURNS [LONG CARDINAL] = {
RETURN[MAX[tsVersion, version]]};
FOR tries: CARDINAL IN [0.. 2*resetTries) UNTIL reset DO
sendMsec ← System.PulsesToMicroseconds[System.GetClockPulses[]];
SendTimeRequest[cH, from, TRUE, (tries < resetTries)];
b ← Socket.GetPacket[cH ! Socket.TimeOut => LOOP];
IF b.ns.packetType = packetExchange AND b.ns.exchangeType =
PacketExchange.ExchangeClientType[timeService]
AND LOOPHOLE[b.ns.exchangeID, LONG CARDINAL] = lastID THEN
BEGIN
p: LONG POINTER TO TimeServerFormat.TSPacket =
LOOPHOLE[@b.ns.exchangeBody];
delay ← (
System.PulsesToMicroseconds[System.GetClockPulses[]] - sendMsec)
/1000;
WITH r: p↑ SELECT FROM
internalTimeResponse =>
BEGIN
IF System.GetGreenwichMeanTime[] = System.gmtEpoch THEN
BEGIN
-- Clock not set yet.
-- Avoid disaster in SystemImpl.AdjustClock.
-- Avoid lies in error due to time passing but time not changing.
ProcessorFace.SetGreenwichMeanTime[
TimeServerFormat.WireToGMT[r.time]];
END;
reset ← TRUE;
time ← System.SecondsSinceEpoch[TimeServerFormat.WireToGMT[r.time]]
- System.SecondsSinceEpoch[System.GetGreenwichMeanTime[]];
error ← TimeServerFormat.WireToLong[r.absoluteError];
IF error # TimeServerClock.undefined
THEN error ← error + delay + 1000;
version ← TimeServerFormat.WireToLong[r.timeVersion];
END;
timeResponse =>
BEGIN
IF System.GetGreenwichMeanTime[] = System.gmtEpoch THEN
BEGIN
-- Clock not set yet.
-- Avoid disaster in SystemImpl.AdjustClock.
-- Avoid lies in error due to time passing but time not changing.
ProcessorFace.SetGreenwichMeanTime[
TimeServerFormat.WireToGMT[r.time]];
END;
reset ← TRUE;
time ← System.SecondsSinceEpoch[TimeServerFormat.WireToGMT[r.time]]
- System.SecondsSinceEpoch[System.GetGreenwichMeanTime[]];
error ← IF NOT r.errorAccurate THEN TimeServerClock.undefined
ELSE TimeServerFormat.WireToLong[r.absoluteError] + delay + 1000;
version ← 0;
END;
ENDCASE;
IF reset THEN AdjustClockEntry[
time, error, MaxVersion[], delay, from, TRUE];
END;
Socket.ReturnBuffer[b];
ENDLOOP;
END
ELSE
FOR tries: CARDINAL IN [0.. resetTries) UNTIL reset DO
from.socket ← NSConstants.timeServerSocket;
BEGIN
ENABLE Inconsistent => RETRY;
pulsesSent: System.Pulses = System.GetClockPulses[];
SendTimeRequest[cH, from, (tries = 0), TRUE];
reset ← CollectTimeReplies[cH, pulsesSent];
END;
ENDLOOP;
Socket.Delete[cH];
EXITS NoReset => NULL;
END;
ResyncNow: PUBLIC ENTRY PROCEDURE = {
IF started THEN Process.Abort[surveyorProcess]};
SetParameters: PUBLIC ENTRY PROCEDURE [
drift, wobble: TimeServerClock.Rate, resetPeriod: CARDINAL] =
BEGIN
tsDrift ← drift; tsWobble ← wobble; tsResetPeriod ← resetPeriod + Random[];
IF FALSE THEN
IF started THEN Process.Abort[surveyorProcess]; -- signal new wait time
END;
Start: PUBLIC ENTRY PROCEDURE =
BEGIN ENABLE UNWIND => NULL;
IF started THEN RETURN;
clockInvalid ← FALSE;
tsVersion ← 0;
surveyorProcess ← FORK SurveyorProcess[];
started ← TRUE;
END;
Stop: PUBLIC ENTRY PROCEDURE =
BEGIN ENABLE UNWIND => NULL;
IF NOT started THEN RETURN;
started ← FALSE;
Process.Abort[surveyorProcess]; -- do after setting started to FALSE
JOIN surveyorProcess;
END;
-- private procedures
AdjustClockEntry: ENTRY PROCEDURE [
amount: LONG INTEGER, error: TimeServerClock.Error, version: LONG CARDINAL,
delay: LONG CARDINAL, who: System.NetworkAddress, useAlways: BOOLEAN] =
BEGIN ENABLE UNWIND => NULL;
me: TimeServerClock.Error = ErrorInternal[];
IF useAlways OR version > tsVersion
OR (version = tsVersion AND (error+1000) < me) THEN
BEGIN
TimeServerLog.LogClockChange[
System.GetGreenwichMeanTime[], amount, who,
me, error, version, delay];
IF System.GetGreenwichMeanTime[] = System.gmtEpoch THEN
ProcessorFace.SetGreenwichMeanTime[System.gmtEpoch + amount]
ELSE SpecialSystem.AdjustClock[amount];
tsError ← error;
tsSourceLastReset ← who;
tsLastChange ← amount;
IF tsVersion < version THEN SendNewVersionNote[tsVersion ← version];
END;
END;
BroadcastAddress: PROCEDURE [net: System.NetworkNumber]
RETURNS [System.NetworkAddress] = INLINE {
RETURN[
[net: net, host: System.broadcastHostNumber,
socket: NSConstants.timeServerSocket]]};
CheckForConsistency: ENTRY PROCEDURE [
amount: LONG INTEGER, error: TimeServerClock.Error, version: LONG CARDINAL,
who: System.NetworkAddress] =
BEGIN ENABLE UNWIND => NULL;
MarkInconsistent: INTERNAL PROCEDURE =
BEGIN
now: System.GreenwichMeanTime = System.GetGreenwichMeanTime[];
TimeServerLog.LogInconsistancy[
who, now, me, System.AdjustGreenwichMeanTime[now, amount], error];
ignoringServer ← TRUE;
ignoringServerAddress ← who.host;
tsError ← TimeServerClock.undefined;
END;
me: TimeServerClock.Error = ErrorInternal[];
IF tsVersion = version
AND me # TimeServerClock.undefined AND ABS[amount*1000] > error + me THEN {
MarkInconsistent[]; ERROR Inconsistent};
END;
CollectTimeReplies: PROCEDURE [
cH: Socket.ChannelHandle, sent: System.Pulses] RETURNS [success: BOOLEAN] =
BEGIN
sendMsec: System.Microseconds = System.PulsesToMicroseconds[sent];
bestTime, time: LONG INTEGER;
bestError: LONG CARDINAL ← TimeServerClock.undefined;
bestVersion: LONG CARDINAL ← 0;
error, delay, version, bestDelay: LONG CARDINAL;
bestServer: System.NetworkAddress;
myHostNumber: System.HostNumber = Router.FindMyHostID[];
b: Buffer.NSBuffer;
success ← FALSE;
DO
b ← Socket.GetPacket[cH !
Socket.TimeOut => EXIT; ABORTED => IF started THEN RETRY];
IF b.ns.packetType = packetExchange
AND b.ns.exchangeType = PacketExchange.ExchangeClientType[timeService]
AND LOOPHOLE[b.ns.exchangeID, LONG CARDINAL] = lastID
AND b.ns.source.host # myHostNumber
AND (NOT ignoringServer OR b.ns.source.host # ignoringServerAddress) THEN
BEGIN
p: LONG POINTER TO TimeServerFormat.TSPacket =
LOOPHOLE[@b.ns.exchangeBody];
delay ← (System.PulsesToMicroseconds[System.GetClockPulses[]]-sendMsec)
/1000;
WITH r: p↑ SELECT FROM
internalTimeResponse =>
BEGIN
IF System.GetGreenwichMeanTime[] = System.gmtEpoch THEN
BEGIN
-- Clock not set yet.
-- Avoid disaster in SystemImpl.AdjustClock.
-- Avoid lies in error due to time passing but time not changing.
ProcessorFace.SetGreenwichMeanTime[TimeServerFormat.WireToGMT[r.time]];
END;
error ← TimeServerFormat.WireToLong[r.absoluteError];
IF error # TimeServerClock.undefined THEN
BEGIN
success ← TRUE;
error ← error + delay + 1000;
time ← System.SecondsSinceEpoch[TimeServerFormat.WireToGMT[r.time]]
- System.SecondsSinceEpoch[System.GetGreenwichMeanTime[]];
version ← TimeServerFormat.WireToLong[r.timeVersion];
IF version > bestVersion
OR (version = bestVersion AND error <= bestError) THEN {
bestTime ← time; bestError ← error; bestVersion ← version;
bestServer ← b.ns.source; bestDelay ← delay};
END;
END;
ENDCASE;
END;
Socket.ReturnBuffer[b];
ENDLOOP;
IF success THEN
BEGIN
CheckForConsistency[bestTime, bestError, bestVersion, bestServer];
AdjustClockEntry[bestTime, bestError, bestVersion, bestDelay,
bestServer, FALSE !
SpecialSystem.SetBackTooFar => {clockInvalid ← TRUE; CONTINUE}];
END;
END;
ErrorInternal: INTERNAL PROCEDURE RETURNS [error: LONG CARDINAL] =
BEGIN
wobbleBase, driftBase: LONG CARDINAL;
IF tsError = TimeServerClock.undefined THEN RETURN [tsError];
driftBase ← System.GetGreenwichMeanTime[] - SpecialSystem.TimeLastSet[];
wobbleBase ← MIN[tsWobble.base, driftBase];
error ← tsWobble.amount*wobbleBase/tsWobble.base
+ tsDrift.amount*driftBase/tsDrift.base;
RETURN[error + tsError];
END;
Init: PROCEDURE =
BEGIN
tsError ← TimeServerClock.undefined;
tsDrift ← [base: 86400, amount: 1000];
tsWobble ← [base: 3600, amount: 1000];
tsResetPeriod ← 15 + Random[];
END;
Random: PROCEDURE RETURNS [CARDINAL] =
-- This generates a "random" (across servers) number between 0 and 10
-- which is added to the reset rate in order to keep the server polling
-- unsynchronized
BEGIN
words: ARRAY [0.. SIZE[System.HostNumber]) OF CARDINAL
= LOOPHOLE[Router.FindMyHostID[]];
RETURN[Inline.DIVMOD[words[SIZE[System.HostNumber]-1], 10].remainder];
END;
RequestTime: PROCEDURE =
BEGIN
haveReply: BOOLEAN ← FALSE;
incID: BOOLEAN;
pulsesSent: System.Pulses;
cH: Socket.ChannelHandle ← Socket.Create[
Socket.AssignNetworkAddress[], 1, 5];
BEGIN ENABLE UNWIND => {Socket.Delete[cH]; Router.FillRoutingTable[0]};
Router.FillRoutingTable[];
Process.Pause[2*oneSecond];
FOR hops: CARDINAL IN [0.. Router.infinity) UNTIL haveReply DO
Socket.SetWaitTime[cH, WaitForHops[hops]];
incID ← TRUE;
THROUGH [0.. triesPerNetwork) UNTIL haveReply DO
pulsesSent ← System.GetClockPulses[];
IF hops = 0 THEN
BEGIN
SendTimeRequest[
cH, BroadcastAddress[System.nullNetworkNumber], TRUE, TRUE];
incID ← FALSE;
END
ELSE FOR net: System.NetworkNumber ←
Router.EnumerateRoutingTable[Router.startEnumeration, hops],
Router.EnumerateRoutingTable[net, hops]
UNTIL net = Router.endEnumeration DO
SendTimeRequest[cH, BroadcastAddress[net], incID, TRUE];
incID ← FALSE;
ENDLOOP;
-- if incID is FALSE here, then a time request was sent
IF NOT incID THEN haveReply ← CollectTimeReplies[cH, pulsesSent];
ENDLOOP;
ENDLOOP;
Socket.Delete[cH];
Router.FillRoutingTable[0];
END;
END;
SendNewVersionNote: INTERNAL PROCEDURE [new: LONG CARDINAL] =
BEGIN
cH: Socket.ChannelHandle ← Socket.Create[Socket.AssignNetworkAddress[]];
b: Buffer.NSBuffer = Socket.GetSendBuffer[cH];
note: LONG POINTER TO noteNewVersion TimeServerFormat.TSPacket
= LOOPHOLE[@b.ns.exchangeBody];
b.ns.packetType ← packetExchange;
b.ns.exchangeID ← LOOPHOLE[lastID];
b.ns.exchangeType ← PacketExchange.ExchangeClientType[timeService];
b.ns.destination ← BroadcastAddress[System.nullNetworkNumber];
Socket.SetPacketWords[b, 3 +
SIZE[noteNewVersion TimeServerFormat.TSPacket]];
note↑ ← [
version: TimeServerFormat.Version,
tsBody: noteNewVersion[TimeServerFormat.LongToWire[new]]];
Socket.BroadcastPacketToAllConnectedNets[cH, b];
Socket.Delete[cH];
END;
SendTimeRequest: PROCEDURE [
cH: Socket.ChannelHandle, who: System.NetworkAddress, incrementID: BOOLEAN,
internalFormat: BOOLEAN] =
BEGIN
b: Buffer.NSBuffer = Socket.GetSendBuffer[cH];
iReq: LONG POINTER TO internalTimeRequest TimeServerFormat.TSPacket
= LOOPHOLE[@b.ns.exchangeBody];
eReq: LONG POINTER TO timeRequest TimeServerFormat.TSPacket
= LOOPHOLE[@b.ns.exchangeBody];
IF incrementID THEN lastID ← lastID + 1;
b.ns.packetType ← packetExchange;
b.ns.exchangeID ← LOOPHOLE[lastID];
b.ns.exchangeType ← PacketExchange.ExchangeClientType[timeService];
b.ns.destination ← who;
Socket.SetPacketWords[b, 3 + (
IF internalFormat THEN SIZE[internalTimeRequest TimeServerFormat.TSPacket]
ELSE SIZE[timeRequest TimeServerFormat.TSPacket])];
IF internalFormat THEN
iReq↑ ← [version: TimeServerFormat.Version, tsBody: internalTimeRequest[]]
ELSE eReq↑ ← [version: TimeServerFormat.Version, tsBody: timeRequest[]];
(IF who.net = System.nullNetworkNumber THEN
Socket.BroadcastPacketToAllConnectedNets ELSE Socket.PutPacket)[cH, b]
END;
SurveyorProcess: PROCEDURE =
-- broadcasts a time request every tsResetPeriod minutes
BEGIN
Process.SetPriority[Process.priorityBackground];
UNTIL NOT started DO
ENABLE ABORTED => LOOP;
IF NOT SpecialSystem.clockSlipping
THEN RequestTime[! Inconsistent => RETRY];
ignoringServer ← FALSE;
Wait[tsResetPeriod];
ENDLOOP;
END;
Wait: PROCEDURE [time: CARDINAL -- minutes --] =
BEGIN ENABLE ABORTED => GOTO StopWaiting;
oneMinute: LONG CARDINAL = LONG[Process.SecondsToTicks[60]];
ticksToWait: LONG CARDINAL = oneMinute*LONG[time];
maxTicks: Process.Ticks = LAST[Process.Ticks];
ticksLeft: LONG CARDINAL;
FOR ticksLeft ← ticksToWait, ticksLeft - MIN[ticksLeft, maxTicks] UNTIL
ticksLeft = 0 DO Process.Pause[Inline.LowHalf[MIN[ticksLeft, maxTicks]]];
ENDLOOP;
EXITS StopWaiting => NULL;
END;
WaitForHops: PROCEDURE [hops: CARDINAL] RETURNS [delay: CARDINAL] = INLINE {
RETURN[MAX[2+3*(hops/2), 6]*1000]};
Init[];
END...