ArpaQueueImpl.mesa
Last Edited by: DCraft, December 21, 1983 9:01 pm
Last Edited by: Taft, February 3, 1984 11:25:43 am PST
Last Edited by: HGM, April 11, 1984 11:53:06 pm PST
John Larson, October 9, 1987 2:49:56 pm PDT
DIRECTORY
BasicTime USING [earliestGMT, GMT, Now, Period, Update],
ArpaQueue USING [ElemProc],
Rope USING [ROPE];
ArpaQueueImpl:
CEDAR PROGRAM
IMPORTS BasicTime
EXPORTS ArpaQueue =
BEGIN
MQ: TYPE = REF MQRep;
MQRep:
PUBLIC
TYPE =
RECORD[
name: ROPE,
elemList: LIST OF QElem ← NIL,
lastSlot: LIST OF QElem ← NIL];
QElem: TYPE = REF QElemRep;
QElemRep:
PUBLIC
TYPE =
RECORD[
value: REF ANY,
inhibitUntil: GMT ← notInhibited,
inhibitReason: ROPE ← notInhibitedRope];
ROPE: TYPE = Rope.ROPE;
GMT: TYPE = BasicTime.GMT;
Queue and Element Creation, Enqueueing, Dequeueing, and Enumeration
A list cell is a pair of (qElem, ref to the next pair). When an element is dequeued, this pair is not reused - thus the ref to the next pair is still valid, and in fact it may still have a ref to it by someone who followed (i.e. copied) the link from the previous pair before it was changed. All of this presents no real problem if we can detect when a cell has been dequeued. For this purpose, in each qElem is included a flag which is initially true, and is set false by Dequeue. If this flag indicates that an element has been dequeued during enumeration, that element is simply skipped. If several adjacent elements have been dequeued, they will all be skipped. Because elements are only ever added at the end of the queue, the only integrity loss during enumeration occurs when the last element is dequeued, another takes its place, and Enumerate has already taken a copy of a ref to the deleted element (or a deleted predecessor). In this case, the newly added element will not be enumerated (big deal!). An element's being requeued [on another queue] causes no problem because the actual pair it occupied in the old queue is not changed.
NewElem:
PUBLIC
PROC [value:
REF
ANY]
RETURNS [newElem: QElem] = {
RETURN[NEW[QElemRep ← [value]]]; };
Value:
PUBLIC
PROC [qElem: QElem]
RETURNS [value:
REF
ANY] = {
RETURN[qElem.value]; };
Create:
PUBLIC
PROC [name:
ROPE]
RETURNS [newQueue:
MQ] = {
newQueue ← NEW[MQRep ← [name: name]]; };
Name:
PUBLIC
PROC [queue:
MQ]
RETURNS [
ROPE] = {
RETURN[queue.name]; };
IsEmpty:
PUBLIC
PROC [queue:
MQ]
RETURNS [
BOOL] = {
RETURN[queue.elemList = NIL]; };
Enqueue:
PUBLIC PROC [queue:
MQ, qElem: QElem] = {
Place element at end of queue.
ENABLE UNWIND => NULL;
IF queue.lastSlot =
NIL
THEN
-- empty queue
queue.elemList ← queue.lastSlot ← LIST[qElem]
ELSE {
queue.lastSlot.rest ← LIST[qElem];
queue.lastSlot ← queue.lastSlot.rest; }; };
Dequeue:
PUBLIC PROC [queue:
MQ, value:
REF
ANY] = {
Search down the list until value found and remove it. This will be called several times for each mail item going through the system, but for all except the last (LeavingSystem) the item should be at the beginning of the list or possibly within a short list (if a host is refusing this particular item). Expects a value rather than an element because client may well only have the value.
ENABLE UNWIND => NULL;
elemList: LIST OF QElem ← queue.elemList;
previousList: LIST OF QElem;
IF elemList = NIL THEN RETURN WITH ERROR NotOnQueue;
IF elemList.first.value = value
THEN {
queue.elemList ← elemList.rest;
IF queue.elemList = NIL THEN queue.lastSlot ← NIL;
RETURN; };
previousList ← elemList;
FOR restElems:
LIST
OF QElem ← elemList.rest, previousList.rest
UNTIL restElems =
NIL
DO
IF restElems.first.value = value
THEN {
previousList.rest ← restElems.rest;
IF restElems.rest = NIL THEN queue.lastSlot ← previousList;
RETURN;
};
previousList ← restElems;
ENDLOOP;
ERROR NotOnQueue; };
NotOnQueue: PUBLIC ERROR = CODE;
GetHead:
PUBLIC
PROC [queue:
MQ]
RETURNS [qElem: QElem] = {
Return the first element in the queue, without Dequeueing it, raising QueueEmpty if none.
ENABLE UNWIND => NULL;
elemList: LIST OF QElem = queue.elemList;
IF elemList = NIL THEN RETURN [NIL];
RETURN[elemList.first]; };
GetProcessableElement:
PUBLIC
PROC [queue:
MQ]
RETURNS [qElem: QElem] = {
ENABLE UNWIND => NULL;
FOR restElems:
LIST
OF QElem ← queue.elemList, restElems.rest
UNTIL restElems =
NIL
DO
qElem: QElem = restElems.first;
IF Inhibition[qElem].for = 0 THEN RETURN[qElem];
ENDLOOP;
RETURN[NIL]; };
QueueEmpty: PUBLIC ERROR = CODE;
Enumerate:
PUBLIC
PROC [queue:
MQ, proc: ArpaQueue.ElemProc, procData:
REF
ANY ←
NIL] = {
FOR restElems:
LIST
OF QElem ← queue.elemList, restElems.rest
UNTIL restElems =
NIL
DO
qElem: QElem ~ restElems.first;
IF NOT proc[qElem, procData] THEN EXIT;
ENDLOOP; };
Inhibition
An element may be inhibited as the result of a user order or automatically when a host refuses to accept it. Inhibition may be used in conjunction with placing items on the BadItems queue.
notInhibited: GMT ~ BasicTime.earliestGMT;
notInhibitedRope: ROPE ~ "not inhibited";
Inhibit:
PUBLIC
PROC [qElem: QElem, for
--seconds--:
INT, why:
ROPE] ~ {
qElem.inhibitUntil ← BasicTime.Update[BasicTime.Now[], for];
qElem.inhibitReason ← why; };
Uninhibit:
PUBLIC
PROC [qElem: QElem, queue:
MQ] ~ {
qElem.inhibitUntil ← notInhibited;
qElem.inhibitReason ← notInhibitedRope; };
Inhibition:
PUBLIC
PROC [qElem: QElem]
RETURNS [for:
INT, why:
ROPE] = {
for ← MAX[BasicTime.Period[from: BasicTime.Now[], to: qElem.inhibitUntil], 0];
IF for = 0 THEN qElem.inhibitReason ← notInhibitedRope;
RETURN[for, qElem.inhibitReason]; };
END.