-- File: PacketTimerImpl.mesa - last edit:
-- AOF                 19-Jun-87 18:29:06
-- DWG.PA               9-Apr-85 16:37:42

-- Copyright (C) 1985, 1986, 1987 by Xerox Corporation. All rights reserved.

DIRECTORY
  NSBuffer USING [Buffer, ReturnBuffer],
  Inline USING [DBITAND, LowHalf],
  PacketTimer USING [ResponseProc],
  PacketExchange USING [
    Error, CreateRequestor, Delete, ExchangeHandle, ExchangeClientType,
    nullExchangeHandle, SetWaitTimes, ExchangeID],
  PacketExchangeInternal USING [SendRequestPacket],
  Process USING [Detach, MsecToTicks, SetTimeout],
  Router USING [infinity, GetDelayToNet, NoTableEntryForNet],
  <<SocketInternal USING [SocketHandle],>>
  System USING [
    GetClockPulses, MicrosecondsToPulses, Pulses, PulsesToMicroseconds];

PacketTimerImpl: MONITOR
  IMPORTS
    NSBuffer, Inline, PacketExchange, PacketExchangeInternal,
    Process, Router, System
  EXPORTS PacketTimer =
  BEGIN
  
  <<
  This module sets timeouts on PacketExchange handles to minimize NET TRAFFIC
  and maximize response time.  We want to retransmit a packet when the foreign
  host does not respond within the average round-trip delay time.
  
  It currently takes 1 millisec to set an ExchangeHandle timeout, or 7 millisecs
  to create one, so we cache a single handle.

  By sending varying client ID's in each packet, we can compute the real round
  trip delay [RTD](because we know which packet the foreign host responds to).
  >>
  
  triesConstant: CARDINAL = 6;		-- # of times we retransmit pkts
  tries: PUBLIC CARDINAL ← triesConstant;

  ExchangeHandle: TYPE = PacketExchange.ExchangeHandle;

  HopRecord: TYPE = RECORD [
    delay: LONG CARDINAL,		-- RTD in millisecs
    workedTries: CARDINAL,
    workedPackets: CARDINAL,
    extraPackets: CARDINAL,		-- ones we didn't have to send
    failedPackets: CARDINAL];

  cache: CacheRecord;
  CacheRecord: TYPE = RECORD[
    condition: CONDITION,  --what the process is waiting on
    watcher: PROCESS ← NIL,  --detached process watching the cache
    oldID: LONG CARDINAL ← firstID,  --the last unique ID returned
    cachedHandle: ExchangeHandle ← PacketExchange.nullExchangeHandle,
    timeout, timein: LONG CARDINAL ← System.MicrosecondsToPulses[10D6],
    cache: ARRAY NATURAL[0..Router.infinity] OF HopRecord ← TRASH];
  
  
  -- Constants
  firstID: LONG CARDINAL = 0;		-- must be multiple of space
  space: LONG CARDINAL = 8;		-- gap between UIDs
  bottomPart: LONG CARDINAL = 7B;  -- reads the gap (lower three bits)
  topPart: LONG CARDINAL = 37777777770B; -- zeroes the gap (upper 29)
  
  weightHistory: CARDINAL = 8;	        -- credence given to past RTD statistics
  
  maxWaitTime: LONG CARDINAL = 180000; -- 3 minutes max wait for a response
  maxTimeout: LONG CARDINAL = maxWaitTime/triesConstant; -- millisecs per pkt max
  minTimeout: LONG CARDINAL = 200;     -- milisecs per pkt minimum before rexmit
  
  GetElapsedTime: PROC [sent, received: System.Pulses] RETURNS [LONG CARDINAL] =
    INLINE {RETURN[System.PulsesToMicroseconds[[received - sent]] / 1024]};
  
  -- looks up a packet uid in a table of transmit times, and returns
  -- the elasped time since this the packet with "uid" was transmitted.
  RoundTripDelay: PROC [
    uid: LONG CARDINAL, transmitTimes:LONG DESCRIPTOR FOR ARRAY OF System.Pulses]
    RETURNS[LONG CARDINAL] = INLINE {
    RETURN[GetElapsedTime[transmitTimes[Delta[uid]],System.GetClockPulses[]]]};
      
  Delta: PROC[uid: LONG CARDINAL] RETURNS[CARDINAL] = INLINE
    {RETURN[Inline.LowHalf[Inline.DBITAND[uid, bottomPart]]]};

  Init: ENTRY PROCEDURE =
    BEGIN
    -- We start with static timeouts based on the following formula,
    -- eliminating a special case for the first transmission to a given 
    -- hop-distance.  The timeout is large to assure that we get a response.
    Process.SetTimeout[@cache.condition, Process.MsecToTicks[1000]];
    cache.watcher ← NIL;  --detached process watching the cache
    cache.oldID ← firstID;  --the last unique ID returned
    cache.cachedHandle ← PacketExchange.nullExchangeHandle;  --cached handle
    cache.timeout ← cache.timein ← System.MicrosecondsToPulses[10D6];
    FOR i: CARDINAL IN [0..Router.infinity] DO 
      cache.cache[i] ← [i * 2048 + 512, 0, 0, 0, 0]; ENDLOOP;
    END;

  -- PUBLIC procs

  -- transmits some packets to the foreign host, and calls back with the first
  -- response.  Checks in the px handle, and deletes send/receive buffers.
  SendPackets: PUBLIC PROCEDURE [
    pxH: ExchangeHandle, requestBuffer: NSBuffer.Buffer,
    firstResponse: PacketTimer.ResponseProc, numberOfPackets: CARDINAL ← tries] =
    BEGIN
    hops: CARDINAL;
    ptc: LONG POINTER TO HopRecord;
    replyBuffer: NSBuffer.Buffer ← NIL;
    baseTime: System.Pulses ← System.GetClockPulses[];
    sendTimes: ARRAY [0..triesConstant) OF System.Pulses;
    id: LONG CARDINAL;		-- universal ID used for sending packet.
    
    Cleanup: PROC = {
      IF replyBuffer # NIL THEN NSBuffer.ReturnBuffer[replyBuffer];
      NSBuffer.ReturnBuffer[requestBuffer]; GiveHandle[pxH]};
    
    -- This generates special UIDs, whose upper 29 bits are unique, and
    -- guaranteed to stay unique for a long time (>> minutes)
    GetUniqueID: ENTRY PROC[] RETURNS[uid: PacketExchange.ExchangeID] =
      BEGIN
      id: LONG CARDINAL ← System.GetClockPulses[];
      IF id <= cache.oldID + space THEN id ← cache.oldID + space;
      RETURN[LOOPHOLE[cache.oldID ← Inline.DBITAND[id, topPart]]];
      END;

    hops ← Router.GetDelayToNet[requestBuffer.ns.destination.net !
      Router.NoTableEntryForNet => {hops ← Router.infinity; CONTINUE}];
    ptc ← @cache.cache[hops];  --makes it easier to get the record
    requestBuffer.ns.exchangeID ← GetUniqueID[]; -- we use our own UIDs
     
    FOR packets: CARDINAL IN [0..numberOfPackets) DO  -- send a single packet
      ENABLE UNWIND => Cleanup[];
      sendTimes[packets] ← System.GetClockPulses[]; -- record send time
      replyBuffer ← PacketExchangeInternal.SendRequestPacket[
        pxH, requestBuffer, requestBuffer.ns.exchangeType, topPart !
        PacketExchange.Error => IF why=timeout THEN CONTINUE ELSE EXIT;];

      IF replyBuffer # NIL THEN
        BEGIN  -- got a response
	id ← LOOPHOLE[replyBuffer.ns.exchangeID];
	ptc.workedTries ← ptc.workedTries + 1;
	ptc.workedPackets ← ptc.workedPackets + packets + 1;
	ptc.extraPackets ← ptc.extraPackets + (packets - Delta[id]);
	firstResponse[replyBuffer, hops];
	NSBuffer.ReturnBuffer[replyBuffer];
	NSBuffer.ReturnBuffer[requestBuffer];
        Checkin[pxH, hops, RoundTripDelay[id, DESCRIPTOR[sendTimes]]];
        RETURN;
        END;

      requestBuffer.ns.exchangeID ← -- increment the UID so we can measure RTD.
        LOOPHOLE[LOOPHOLE[requestBuffer.ns.exchangeID, LONG CARDINAL] + 1];
      ENDLOOP;
    
    ptc.failedPackets ← ptc.failedPackets + numberOfPackets;
    Cleanup[];
    END; -- SendPackets

  -- Checks out a packet Exchange Handle suitable for sending a single packet.
  Checkout: PUBLIC PROCEDURE [hops: CARDINAL] RETURNS [pxH: ExchangeHandle] =
    BEGIN
    rexmitTime: LONG CARDINAL;
    [pxH, rexmitTime] ← CheckoutInternal[@cache.cache[hops]];
    IF (pxH = PacketExchange.nullExchangeHandle) THEN  -- wasn't cached
      pxH ← PacketExchange.CreateRequestor[rexmitTime, rexmitTime]
    ELSE PacketExchange.SetWaitTimes[pxH, rexmitTime, rexmitTime];
    END;

  -- Checks in a packet Exchange Handle.  Updates statistics.  
  Checkin: PUBLIC ENTRY PROCEDURE [
    pxH: PacketExchange.ExchangeHandle, hops: CARDINAL, timeout: LONG CARDINAL] =
    BEGIN
    oldTimeout: LONG CARDINAL ← cache.cache[hops].delay;
    -- weighted average formula is: [[N-1]*old + 1.5*new] / N, N a power of two.
    timeout ← ((oldTimeout * weightHistory - oldTimeout) +
      (timeout + timeout / 2)) / weightHistory;
    -- now clip the timeout at upper and lower boundaries to prevent divergence.
    cache.cache[hops].delay ← IF timeout<minTimeout THEN minTimeout 
      ELSE IF timeout > maxTimeout THEN maxTimeout ELSE timeout;
    SELECT TRUE FROM
      (cache.watcher = NIL) =>
        BEGIN
	Process.Detach[(cache.watcher ← FORK Watcher[])];  --create process
	cache.cachedHandle ← pxH;  --load the single element cache
	cache.timein ← System.GetClockPulses[];  --restart the clock
	END;
      (cache.cachedHandle = PacketExchange.nullExchangeHandle) =>
        BEGIN
	cache.cachedHandle ← pxH;  --load the single element cache
	cache.timein ← System.GetClockPulses[];  --restart the clock
	END;
      ENDCASE => PacketExchange.Delete[pxH];
    END;

  ReadTimeout: PUBLIC ENTRY PROCEDURE [hops: CARDINAL]
    RETURNS [timeout: LONG CARDINAL] = {RETURN[cache.cache[hops].delay]};

  -- INTERNAL, PRIVATE ENTRY PROCS

  GiveHandle: -- back -- ENTRY PROCEDURE [pxH: ExchangeHandle] =
    BEGIN
    SELECT TRUE FROM
      (cache.watcher = NIL) =>
        BEGIN
	Process.Detach[(cache.watcher ← FORK Watcher[])];  --create process
	cache.cachedHandle ← pxH;  --load the single element cache
	cache.timein ← System.GetClockPulses[];  --restart the clock
	END;
      (cache.cachedHandle = PacketExchange.nullExchangeHandle) =>
        BEGIN
	cache.cachedHandle ← pxH;  --load the single element cache
	cache.timein ← System.GetClockPulses[];  --restart the clock
	END;
      ENDCASE => PacketExchange.Delete[pxH];
    END;

  CheckoutInternal: ENTRY PROCEDURE [cH: LONG POINTER TO HopRecord]
    RETURNS [pxH: ExchangeHandle, timeout: LONG CARDINAL] =
    BEGIN
    pxH ← cache.cachedHandle;
    cache.cachedHandle ← PacketExchange.nullExchangeHandle;
    timeout ← cH.delay;
    END;

  Watcher: <<DETACHED>> ENTRY PROC[] =
    BEGIN
    --FOR A WHILE-- DO
      SELECT TRUE FROM
        (cache.cachedHandle = PacketExchange.nullExchangeHandle),
	((System.GetClockPulses[] - cache.timein) < cache.timeout) =>
	  WAIT cache.condition;  --wait for a small period of time
	ENDCASE => EXIT;  --let's get out of here
      ENDLOOP;
    cache.watcher ← NIL;  --cleanup our tracks
    PacketExchange.Delete[cache.cachedHandle];  --get rid of exchange object
    cache.cachedHandle ← PacketExchange.nullExchangeHandle;  --and wipe out that
    END;  --Watcher

  Init[];

  END..

  
LOG
 3-Dec-84 15:41:41 DWG created
14-Mar-85 22:14:48 DWG rewritten to remove multiple cached handles
15-Apr-85 15:47:00 DWG bent to Jody and Mark's will.
17-Jan-86 10:53:55 AOF gunned interface OPEN statement
 6-Feb-86  8:54:58 AOF Merged PacketExchangeExtras
 6-Jun-86 14:31:08 AOF Put cache array (+) into Courier's heap
18-Aug-86 18:45:38 AOF Allocate one more for the array
23-Dec-86 18:23:17 AOF Put the +1 in the right place
17-Feb-87 11:51:37 AOF Watcher on PEX handle caches.
19-Jun-87 18:05:27 AOF cache back in gf.