-- Copyright (C) 1984  by Xerox Corporation. All rights reserved. 
-- NetDirCache.mesa, HGM, 22-Jan-84  1:18:38

DIRECTORY
  String USING [Equivalent],

  Stats USING [StatCounterIndex, StatIncr],
  NameServerDefs USING [
    CacheEntry, CacheEntryObject, SearchNetDirForName, SearchNetDirForAddress],
  PupTypes USING [PupAddress],
  ServerHeap USING [Create, Free, Node];

NetDirCache: MONITOR
  IMPORTS String, Stats, NameServerDefs, ServerHeap
  EXPORTS NameServerDefs =
  BEGIN OPEN Stats, NameServerDefs;

  PupAddress: TYPE = PupTypes.PupAddress;
  CacheEntry: TYPE = NameServerDefs.CacheEntry;
  CacheEntryObject: TYPE = NameServerDefs.CacheEntryObject;

  firstCacheEntry: CacheEntry ← NIL;
  cacheLimit: CARDINAL ← 400;
  cacheWordsInUse: CARDINAL ← 0;
  sequenceNumber: CARDINAL ← 0;

  statHits, statMisses, statNone, statFile: PUBLIC StatCounterIndex;


  emptyCacheEntryObject: CacheEntryObject ← [
    next: NIL, size: SIZE[CacheEntryObject], count: 1, sequence: 0,
    names: DESCRIPTOR[NIL, 0], addrs: DESCRIPTOR[NIL, 0]];

  SetCacheSize: PUBLIC PROCEDURE [n: CARDINAL] = BEGIN cacheLimit ← n; END;

  BumpCacheSize: PUBLIC PROCEDURE [n: INTEGER] =
    BEGIN cacheLimit ← cacheLimit + n; END;


  FlushWholeCache: PUBLIC ENTRY PROCEDURE =
    BEGIN
    WHILE firstCacheEntry # NIL DO FlushOneEntry[]; ENDLOOP;
    IF cacheWordsInUse # 0 THEN ERROR CacheShouldBeEmptyNow;
    END;

  EnumerateCache: PUBLIC ENTRY PROCEDURE [proc: PROCEDURE [CacheEntry]] =
    BEGIN
    FOR ce: CacheEntry ← firstCacheEntry, ce.next UNTIL ce = NIL DO
      proc[ce]; ENDLOOP;
    END;

  SearchCacheForName: PUBLIC ENTRY PROCEDURE [key: LONG STRING, proc: PROCEDURE [CacheEntry]]
    RETURNS [hit: BOOLEAN] =
    BEGIN
    previous: CacheEntry;
    FOR ce: CacheEntry ← firstCacheEntry, ce.next UNTIL ce = NIL DO
      FOR i: CARDINAL IN [0..LENGTH[ce.names]) DO
        IF String.Equivalent[key, ce.names[i]] THEN
          BEGIN
          StatIncr[statHits];
          IF LENGTH[ce.addrs] = 0 THEN StatIncr[statMisses];
          ce.count ← ce.count + 1;
          IF firstCacheEntry # ce THEN
            BEGIN  -- move this entry to the head of the list
            previous.next ← ce.next;
            ce.next ← firstCacheEntry;
            firstCacheEntry ← ce;
            END;
	  proc[ce];
          RETURN[TRUE];
          END;
        ENDLOOP;
      previous ← ce;
      ENDLOOP;
    StatIncr[statNone];
    RETURN[FALSE];
    END;

  SearchCacheForAddress: PUBLIC ENTRY PROCEDURE [key: PupAddress, proc: PROCEDURE [CacheEntry]]
    RETURNS [hit: BOOLEAN] =
    BEGIN
    previous: CacheEntry;
    FOR ce: CacheEntry ← firstCacheEntry, ce.next UNTIL ce = NIL DO
      FOR i: CARDINAL IN [0..LENGTH[ce.addrs]) DO
        IF key = ce.addrs[i] THEN
          BEGIN
          StatIncr[statHits];
          IF LENGTH[ce.addrs] = 0 THEN StatIncr[statMisses];
          ce.count ← ce.count + 1;
          IF firstCacheEntry # ce THEN
            BEGIN  -- move this entry to the head of the list
            previous.next ← ce.next;
            ce.next ← firstCacheEntry;
            firstCacheEntry ← ce;
            END;
	  proc[ce];
          RETURN[TRUE];
          END;
        ENDLOOP;
      previous ← ce;
      ENDLOOP;
    StatIncr[statNone];
    RETURN[FALSE];
    END;

  ForceNameIntoCache: PUBLIC ENTRY PROCEDURE [key: LONG STRING, proc: PROCEDURE [CacheEntry]]
    RETURNS [hit: BOOLEAN] =
    BEGIN
    mine: CacheEntryObject ← emptyCacheEntryObject;
    IF ~NameServerDefs.SearchNetDirForName[key, @mine] THEN RETURN[FALSE];
    StatIncr[statFile];
    proc[AddToCache[@mine]];
    RETURN[TRUE]
    END;

  ForceAddressIntoCache: PUBLIC ENTRY PROCEDURE [key: PupAddress, proc: PROCEDURE [CacheEntry]]
    RETURNS [hit: BOOLEAN] =
    BEGIN
    mine: CacheEntryObject ← emptyCacheEntryObject;
    IF ~NameServerDefs.SearchNetDirForAddress[key, @mine] THEN RETURN[FALSE];
    StatIncr[statFile];
    proc[AddToCache[@mine]];
    RETURN[TRUE]
    END;

  NothingInCacheToFlush: ERROR = CODE;
  CacheShouldBeEmptyNow: ERROR = CODE;

  FlushOneEntry: INTERNAL PROCEDURE =
    BEGIN
    previous, ce: CacheEntry;
    IF firstCacheEntry = NIL THEN ERROR NothingInCacheToFlush;
    ce ← firstCacheEntry;
    IF firstCacheEntry.next = NIL THEN firstCacheEntry ← NIL
    ELSE
      BEGIN
      WHILE ce.next # NIL DO previous ← ce; ce ← ce.next; ENDLOOP;
      previous.next ← NIL;
      END;
    cacheWordsInUse ← cacheWordsInUse - ce.size;
    FOR i: CARDINAL IN [0..LENGTH[ce.names]) DO
      ServerHeap.Free[ce.names[i]]; ENDLOOP;
    IF BASE[ce.names] # NIL THEN ServerHeap.Free[BASE[ce.names]];
    IF BASE[ce.addrs] # NIL THEN ServerHeap.Free[BASE[ce.addrs]];
    ServerHeap.Free[ce];
    END;


  AddToCache: INTERNAL PROCEDURE [his: CacheEntry] RETURNS [ce: CacheEntry] =
    BEGIN
    WHILE his.size + cacheWordsInUse > cacheLimit DO FlushOneEntry[]; ENDLOOP;
    ce ← ServerHeap.Node[SIZE[CacheEntryObject]];
    ce↑ ← his↑;
    ce.next ← firstCacheEntry;
    ce.sequence ← (sequenceNumber ← sequenceNumber + 1);
    firstCacheEntry ← ce;
    cacheWordsInUse ← cacheWordsInUse + ce.size;
    END;

  ServerHeap.Create[];
  END.