QueueImpl.mesa
Last Edited by: HGM, April 11, 1984 11:53:06 pm PST
Last Edited by: DCraft, December 21, 1983 9:01 pm
Last Edited by: Taft, February 3, 1984 11:25:43 am PST
DIRECTORY
BasicTime USING [earliestGMT, GMT, Now, Period, Update],
Queue USING [ElemProc],
Rope USING [ROPE];
QueueImpl: CEDAR PROGRAM
IMPORTS BasicTime
EXPORTS Queue =
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: Queue.ElemProc, procData: REF ANYNIL] = {
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.