-- Transport Mechanism Mail Server - policy module --

-- [Indigo]<Grapevine>MS>Policy.mesa --

-- Randy Gobbel		20-May-81 12:58:17 --
-- Andrew Birrell	20-Sep-82  9:16:26 --
-- Mike Schroeder	25-Jan-83 13:41:07F  --

DIRECTORY
Ascii		USING[ CR ],
EnquiryDefs	USING[ ],
GlassDefs	USING[ Handle ],
LogDefs		USING[ DisplayNumber, WriteLogEntry ],
PolicyDefs	-- using everything --,
Process		USING[ DisableTimeout, GetPriority, InitializeCondition,
		       MsecToTicks, Priority, SetPriority, SetTimeout,
		       Ticks ],
String		USING[ AppendChar, AppendDecimal, AppendString ],
Time		USING[ Current, Pack, Packed, Unpack, Unpacked ];

Policy: MONITOR
   IMPORTS LogDefs, Process, String, Time
   EXPORTS EnquiryDefs, PolicyDefs =

BEGIN


-- Egg-timer --

minsCond: CONDITION;
secsCond: CONDITION;

Wait: PUBLIC PROCEDURE[ days: CARDINAL ← 0,
                        hrs:  [0..24)  ← 0,
                        mins: [0..60)  ← 0,
                        secs: [0..60)  ← 0 ] =
   BEGIN
   limit: Time.Packed = LOOPHOLE[Time.Current[] + 
                                   days * (LONG[24] * 60 * 60) +
                                   hrs * (LONG[60] * 60) +
                                   mins * LONG[60] +
                                   LONG[secs]];
   WaitUntil[limit];
   END;

WaitUntil: PUBLIC ENTRY PROC[time: Time.Packed] =
   BEGIN
   UNTIL Time.Current[] + 60 >= time DO WAIT minsCond ENDLOOP;
   UNTIL Time.Current[] >= time DO WAIT secsCond ENDLOOP;
   END;


-- Compactor scheduling strategy --

compactorEnabled: BOOLEAN; -- whether compactor should run at all --
compactorWanted:  BOOLEAN; -- whether compactor should start another cycle --
compactorDelay:   CARDINAL; -- max delay in milliseconds --
compactorStart:   CONDITION;
gapsNotified:     CARDINAL ← 0; -- number of calls on "GapExists" --

CompactorStart: PUBLIC ENTRY PROCEDURE =
   BEGIN
   UNTIL compactorEnabled AND compactorWanted DO WAIT compactorStart ENDLOOP;
   compactorWanted ← FALSE;
   END;

compactorPause: CONDITION;

CompactorPause: PUBLIC ENTRY PROCEDURE =
   BEGIN
   delay: Process.Ticks = Process.MsecToTicks[
      (compactorDelay / (100-minFreeHeap) ) * --beware of overflow!--
            (IF freeHeap<minFreeHeap THEN 0 ELSE freeHeap-minFreeHeap) ];
   UNTIL compactorEnabled DO WAIT compactorPause ENDLOOP;
   IF current[work] = 0 THEN RETURN;
   IF gapsNotified > 0 THEN { gapsNotified ← gapsNotified - 1; RETURN };
   IF delay = 0 THEN RETURN;
   Process.SetTimeout[@compactorPause, delay];
   WAIT compactorPause;
   END;

freeHeap:     [0..100];
minFreeHeap:  [0..100]; -- min free heap for running compactor with pauses --
loggedHeap:   [0..100] ← 100; -- free heap recorded in log --

AmountOfFreeHeap: PUBLIC ENTRY PROCEDURE[ given: [0..100] ] =
   BEGIN
   freeHeap ← given;
   IF given # loggedHeap
   AND ( given < minFreeHeap OR loggedHeap < minFreeHeap
         OR given NOT IN (loggedHeap-5..loggedHeap+5) )
   THEN LogFreeHeap[];
   END;

LogFreeHeap: INTERNAL PROC =
   BEGIN
   s: STRING = [14]; -- 100% free heap --
   String.AppendString[s, "Free heap: "L];
   String.AppendDecimal[s, freeHeap];
   String.AppendChar[s, '%];
   LogDefs.WriteLogEntry[s];
   loggedHeap ← freeHeap;
   END;

GapExists: PUBLIC ENTRY PROCEDURE =
   BEGIN
   compactorWanted ← TRUE; NOTIFY compactorStart;
   IF gapsNotified = 0 THEN NOTIFY compactorPause;
   gapsNotified ← gapsNotified + 1;
   END;


-- Other time delays --

periodicWantedNow: PACKED ARRAY PolicyDefs.PeriodicProcess
                   OF BOOLEAN ← ALL[FALSE];

readPendingDelay: CARDINAL ← 15; -- minutes --
prodServersDelay: CARDINAL ← 15; -- minutes --
archiverHour:     [0..24) ← 23; -- time of day --
regPurgerHour:    [0..24) ← 23; -- time of day --

PeriodicWait: PUBLIC ENTRY PROC[process: PolicyDefs.PeriodicProcess] =
   BEGIN
   limit: LONG CARDINAL = SELECT process FROM
       readPending => Time.Current[] + readPendingDelay*60,
       prodServers => Time.Current[] + prodServersDelay*60,
       archiver =>    CalculateNextTime[archiverHour],
       regPurger =>   CalculateNextTime[regPurgerHour],
     ENDCASE => ERROR;
   UNTIL Time.Current[] >= limit OR periodicWantedNow[process]
   DO WAIT minsCond ENDLOOP;
   periodicWantedNow[process] ← FALSE;
   END;

Activate: PUBLIC ENTRY PROC[process: PolicyDefs.PeriodicProcess] =
   BEGIN
   periodicWantedNow[process] ← TRUE;
   BROADCAST minsCond;
   END;

CalculateNextTime: PROC[wantedHour: [0..24)] RETURNS[Time.Packed] =
   BEGIN
   unpacked: Time.Unpacked ←
       Time.Unpack[Time.Current[]];
   IF unpacked.hour >= wantedHour
   THEN -- move to next day --
        unpacked ← Time.Unpack[LOOPHOLE[Time.Current[]
                                     + 24*60*LONG[60]]];
   unpacked.minute ← 0; unpacked.second ← 0;
   unpacked.hour ← wantedHour;
   RETURN[ Time.Pack[unpacked, FALSE] ]
   END;

expressThreshold: CARDINAL ← 1;

ExpressAllowed: PUBLIC ENTRY PROC[inputLength: CARDINAL] RETURNS[BOOLEAN] =
   BEGIN
   RETURN[inputLength < expressThreshold]
   END;

SetExpressThreshold: PUBLIC ENTRY PROC[thresh: CARDINAL] =
  { expressThreshold ← thresh };

-- Control on operations --

control: PACKED ARRAY PolicyDefs.Operation OF PolicyDefs.Control;
current: ARRAY PolicyDefs.Operation OF PolicyDefs.OpLimit;
high:    ARRAY PolicyDefs.Operation OF PolicyDefs.OpLimit;
reject:  ARRAY PolicyDefs.Operation OF LONG CARDINAL;
total:   ARRAY PolicyDefs.Operation OF LONG CARDINAL;
opWait:  CONDITION;

WaitOperation: PUBLIC ENTRY PROCEDURE[ op: PolicyDefs.Operation ] =
   { UNTIL CheckOp[op] DO WAIT opWait ENDLOOP };

CheckOperation: PUBLIC ENTRY PROCEDURE[ op: PolicyDefs.Operation ]
                               RETURNS[ BOOLEAN ] =
   { RETURN[ CheckOp[op] ] };

CheckOp: INTERNAL PROCEDURE[ op: PolicyDefs.Operation ]
                    RETURNS[ BOOLEAN ] =
   BEGIN
   IF current[op] < control[op].limit AND control[op].allowed
   AND( SELECT op FROM
          clientInput, serverInput, MTP =>
             (freeHeap > minFreeHeap/2 AND CheckOp[connection]),
          readMail, regExpand, lily, FTP => CheckOp[connection],
          readExpress, readInput, readPending, readForward, readMailbox => CheckOp[mainLine],
          remailing =>
             (freeHeap > minFreeHeap/2 AND CheckOp[mainLine]),
          RSReadMail, MSReadMail, archiver,
                      regPurger => CheckOp[background],
          connection, telnet, mainLine, background => CheckOp[work],
          work => TRUE,
        ENDCASE => ERROR )
   THEN BEGIN
        current[op] ← current[op] + 1;
        IF current[op] > high[op] THEN high[op] ← current[op];
        total[op] ← total[op] + 1;
        RETURN[ TRUE ]
        END
   ELSE BEGIN
        reject[op] ← reject[op] + 1;
        RETURN[ FALSE ]
        END;
   END;

EndOperation: PUBLIC ENTRY PROCEDURE[ op: PolicyDefs.Operation ] =
   { EndOp[op] };

EndOp: INTERNAL PROCEDURE[ op: PolicyDefs.Operation ] =
   BEGIN
   current[op] ← current[op] - 1;
   SELECT op FROM
          clientInput, serverInput, readMail,
          regExpand, lily, MTP, FTP => EndOp[connection];
          readExpress, readInput, readPending, readForward, readMailbox,
          remailing => EndOp[mainLine];
          RSReadMail, MSReadMail, archiver,
                      regPurger => EndOp[background];
          connection, telnet, mainLine, background, lily => EndOp[work];
          work => NULL;
   ENDCASE => ERROR;
   BROADCAST opWait;
   END;

ReadOperationCurrent: PUBLIC ENTRY PROC[op: PolicyDefs.Operation ]
                      RETURNS[ PolicyDefs.OpLimit ] =
   { RETURN[ current[op] ] };

ReadOperationControl: PUBLIC ENTRY PROCEDURE[ op: PolicyDefs.Operation ]
                      RETURNS[ PolicyDefs.Control ] =
   BEGIN
   RETURN[ control[op] ]
   END;

SetOperationLimit: PUBLIC ENTRY PROCEDURE[ op: PolicyDefs.Operation,
                                           limit: PolicyDefs.OpLimit ] =
   BEGIN
   control[op].limit ← limit;
   BROADCAST opWait;
   END;

SetOperationAllowed: PUBLIC ENTRY PROCEDURE[ op: PolicyDefs.Operation,
                                             allowed: BOOLEAN ] =
   BEGIN
   control[op].allowed ← allowed;
   BROADCAST opWait;
   END;

SetTelnetAllowed: PUBLIC ENTRY PROCEDURE =
   BEGIN
   control[work].allowed ← control[telnet].allowed ← TRUE;
   END;

PolicyControls: PUBLIC PROC[str: GlassDefs.Handle] =
   BEGIN
   OPEN str;
   WriteChar[Ascii.CR];
   WriteString[
       "Operation:	Allowed	Limit	Current	High	Reject	Accepted"L];
     --	  clientInput	yes	127	127	127	65535	655355555 --
   FOR op: PolicyDefs.Operation IN PolicyDefs.Operation
   DO control: PolicyDefs.Control = ReadOperationControl[op];
      gap: STRING = "	"L;
      WriteChar[Ascii.CR];
      WriteString[SELECT op FROM
          work =>         "work          "L,
          connection =>   " connection   "L,
          clientInput =>  "  clientInput "L,
          serverInput =>  "  serverInput "L,
          readMail =>     "  readMail    "L,
          regExpand =>    "  regExpand   "L,
          lily =>         "  Lily        "L,
          MTP =>          "  MTP         "L,
          FTP =>          "  FTP         "L,
          telnet =>       " Telnet       "L,
          mainLine =>     " mainLine     "L,
          readExpress =>  "  readExpress "L,
          readInput =>    "  readInput   "L,
          readPending =>  "  readPending "L,
          readForward =>  "  readForward "L,
          readMailbox =>  "  readMailbox "L,
          remailing =>    "  remailing   "L,
          background =>   " background   "L,
          RSReadMail =>   "  RSReadMail  "L,
          MSReadMail =>   "  MSReadMail  "L,
          archiver =>     "  archiver    "L,
          regPurger =>    "  RegPurger   "L,
        ENDCASE => ERROR ];
      WriteString[gap];
      WriteString[IF control.allowed THEN "yes"L ELSE "no"L];
      WriteString[gap];
      WriteDecimal[control.limit];
      WriteString[gap];
      WriteDecimal[current[op]];
      WriteString[gap];
      WriteDecimal[high[op]];
      WriteString[gap];
      WriteLongDecimal[reject[op]];
      WriteString[gap];
      WriteLongDecimal[total[op]];
      WriteString[gap];
   ENDLOOP;
   WriteChar[Ascii.CR];
   WriteString["readPendingDelay="L]; WriteDecimal[readPendingDelay];
   WriteString[" mins"L];
   WriteChar[Ascii.CR];
   WriteString["prodServersDelay="L]; WriteDecimal[prodServersDelay];
   WriteString[" mins"L];
   END;


-- misc procedures for use from the debugger: use with care! --

BroadcastCondition: ENTRY PROC[cond: POINTER TO CONDITION] =
   { BROADCAST cond↑ };

forever: CONDITION; -- time-out is disabled --

WaitOnCondition: ENTRY PROC[cond: POINTER TO CONDITION] =
   { WAIT cond↑ };

Ready: SIGNAL = CODE;

SignalAtPriority: PROC[new: Process.Priority] =
   BEGIN
   old: Process.Priority = Process.GetPriority[];
   Process.SetPriority[new];
   SIGNAL Ready[];
   Process.SetPriority[old];
   END;


-- Initialisation --

Init: ENTRY PROCEDURE =
   BEGIN
   OPEN Process;

   -- Egg-timer --
   InitializeCondition[@minsCond, MsecToTicks[60000]];
   InitializeCondition[@secsCond, MsecToTicks[1000]];

   -- Compactor scheduling --
   compactorEnabled ← TRUE;
   compactorWanted ← TRUE;
   InitializeCondition[@compactorStart, 0];
   DisableTimeout[@compactorStart];
   compactorDelay ← 1000;
   InitializeCondition[@compactorPause, MsecToTicks[compactorDelay]];
   minFreeHeap  ← 10;
   freeHeap ← (minFreeHeap+100)/2;

   -- Operation controls --
   BEGIN
      max: PolicyDefs.OpLimit = LAST[PolicyDefs.OpLimit];
      control[ work		] ← [limit:max, allowed:TRUE];
      control[   connection	] ← [limit:12,  allowed:TRUE];
      control[     clientInput	] ← [limit:5,   allowed:TRUE];
      control[     serverInput	] ← [limit:5,   allowed:TRUE];
      control[     readMail	] ← [limit:8,   allowed:TRUE];
      control[     regExpand	] ← [limit:9,   allowed:TRUE];
      control[     lily		] ← [limit:4,   allowed:TRUE];
      control[     MTP  	] ← [limit:7,   allowed:TRUE];
      control[     FTP  	] ← [limit:2,   allowed:TRUE];
      control[   telnet		] ← [limit:3,   allowed:TRUE];
      control[   mainLine	] ← [limit:max, allowed:TRUE];
      control[     readExpress	] ← [limit:1,   allowed:TRUE];
      control[     readInput	] ← [limit:1,   allowed:TRUE];
      control[     readPending	] ← [limit:1,   allowed:TRUE];
      control[     readForward	] ← [limit:2,   allowed:TRUE];
      control[     readMailbox	] ← [limit:1,   allowed:TRUE];
      control[   background	] ← [limit:1,   allowed:TRUE];
      control[     RSReadMail	] ← [limit:1,   allowed:TRUE];
      control[     MSReadMail	] ← [limit:1,   allowed:TRUE];
      control[     remailing	] ← [limit:1,   allowed:TRUE];
      control[     archiver	] ← [limit:1,   allowed:TRUE];
      control[     regPurger	] ← [limit:1,   allowed:TRUE];
   END;
   current ← high ← ALL[0]; reject ← total ← ALL[LONG[0]];
   InitializeCondition[@opWait, 0];
   DisableTimeout[@opWait];

   DisableTimeout[@forever];

   -- statistics --
   LogDefs.DisplayNumber["Free heap"L, [percent[@freeHeap]] ];
   LogDefs.DisplayNumber["Connections"L, [short[@(current[connection])]] ];

   END;


Init[];



END.