QueueControlImpl.mesa
Copyright (C) Xerox Corporation 1984, 1985, 1986. All rights reserved.
Last edited by Jacks: 16-May-86 15:04:17
Tim Diebert: December 23, 1986 1:24:22 pm PST
<<This module contains the queue monitoring routines for aborted and marked queues.>>
DIRECTORY
DecomposerControl USING [FreeDecomposeObject],
NSFile USING [nullID],
NSString USING [String, StringFromMesaString],
PrintingTypes USING [Option],
PrintQueue USING [
Empty, nilQueueObjectHandle, QueueObjectHandle, Requeue, QueueStage,
QueueWatcher],
Process USING [
Detach, Priority, priorityBackground, SecondsToTicks, SetPriority, SetTimeout],
PSAsyncMsg USING [
Expand1AndPutString, ExpandArrayAndPutString, Proc, PutMesaString],
QueueControl USING [CompletionProc, TraceLevel],
QueueFile USING [Delete],
String USING [AppendLongDecimal],
System USING [GetGreenwichMeanTime, SecondsSinceEpoch],
Time USING [Append, Unpack];
QueueControlImpl: CEDAR MONITOR
IMPORTS
DecomposerControl, NSString, PrintQueue, Process, PSAsyncMsg, QueueFile, String, System, Time
EXPORTS QueueControl =
BEGIN
State: TYPE = RECORD [
trace: QueueControl.TraceLevel ← none,
docCompleted: QueueControl.CompletionProc ← NIL
];
state: State;
abortedQEvent: CONDITION; -- something on aborted queue
markedQEvent: CONDITION; -- something on marked queue
forwardedQEvent: CONDITION; -- something on forwarded queue
abortQProcessed: CONDITION; -- something moved from aborted to inactive queue
markedQProcessed: CONDITION; -- something moved from marked to inactive queue
forwardedQProcessed: CONDITION; -- something moved from forwarded to inactive queue
Init: PUBLIC ENTRY PROCEDURE [printingOption: PrintingTypes.Option,
documentCompleted: QueueControl.CompletionProc,
putAsyncMsgFromKey: PSAsyncMsg.Proc, trace: QueueControl.TraceLevel] = BEGIN
state.trace ← trace;
state.docCompleted ← documentCompleted;
start queue watcher processes...
Process.Detach[FORK ProcessAbortedQueue[]];
Process.SetTimeout[@abortedQEvent, Process.SecondsToTicks[seconds: 180]];
IF printingOption = feps9700
THEN BEGIN
Process.Detach[FORK ProcessForwardedQueue[]];
Process.SetTimeout[@forwardedQEvent, Process.SecondsToTicks[seconds: 60]];
END
ELSE BEGIN
Process.Detach[FORK ProcessMarkedQueue[]];
Process.SetTimeout[@markedQEvent, Process.SecondsToTicks[seconds: 60]];
END;
all jobs found on these queues must be processed before
initialization is completed...
UNTIL PrintQueue.Empty[aborted] DO WAIT abortQProcessed; ENDLOOP;
IF printingOption = feps9700
THEN UNTIL PrintQueue.Empty[forwarded] DO WAIT forwardedQProcessed; ENDLOOP
ELSE UNTIL PrintQueue.Empty[marked] DO WAIT markedQProcessed; ENDLOOP;
END; --Init
ModifyTraceLevel: PUBLIC ENTRY PROCEDURE [trace: QueueControl.TraceLevel] = BEGIN
state.trace ← trace;
END; --ModifyTraceLevel
-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
WaitAbortQueueEmpty: PUBLIC ENTRY PROCEDURE = BEGIN
-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
UNTIL PrintQueue.Empty[aborted] DO WAIT abortQProcessed; ENDLOOP;
END; -- WaitAbortQueueEmpty
-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
AbortedQEntry: ENTRY PROCEDURE = BEGIN -- Was nested in ProcessAbortedQueue
-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
called by PrintQueue when a QObj shows up on aborted queue
ENABLE UNWIND => NULL;
BROADCAST abortedQEvent;
IF state.trace = verbose THEN
PSAsyncMsg.PutMesaString["$$BROADCAST abortedQEvent"];
END; -- AbortedQEntry
-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
ProcessAbortedQueue: PUBLIC ENTRY PROCEDURE = BEGIN
-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
<<During initialization all jobs found on the aborted queue are processed and moved to the inactive queue. Otherwise, only ten jobs at a time are processed without stopping and waiting to be notified by the print queue implementation via AbortedQEntry that a job has been added to the aborted queue. We stop and wait after ten jobs to prevent the processes forked by the print queue implementation from piling up, as occasionally happens when many jobs are aborted quickly (for example, when a single job which can't be spooled is retried many times). When this happens we have observed that abortedQEvent may be broadcast more than once before ProcessAbortedQueue runs--meaning that ProcessAbortedQueue misses notification for some number of jobs. Processing blocks of ten jobs should make up for any missed notifications. There is also a three minute timeout on abortedQEvent.>>
ENABLE UNWIND => NULL;
jobCount: CARDINAL ← 0;
abortedQInitialized: BOOLEANFALSE;
qOH: PrintQueue.QueueObjectHandle ← PrintQueue.nilQueueObjectHandle;
Process.SetPriority[Process.priorityBackground];
PrintQueue.QueueWatcher[AbortedQEntry, aborted];
DO -- forever
IF abortedQInitialized AND (jobCount = 0) THEN WAIT abortedQEvent;
<<Normally when doing a wait, it is proper to check a variable before proceeding (in case of a bogus broadcast, for example). We cover that base by checking for nilQueueObjectHandle when we try to get the next job off of the aborted queue.>>
IF jobCount < 10 THEN jobCount ← jobCount + 1 ELSE jobCount ← 0;
qOH ← PrintQueue.Requeue[fromQueue: aborted, toQueue: temp];
IF qOH = PrintQueue.nilQueueObjectHandle THEN
{abortedQInitialized ← TRUE; jobCount ← 0; LOOP};
IF qOH.printObjectHandle # NIL THEN BEGIN
DecomposerControl.FreeDecomposeObject[qOH.uid];
qOH.printObjectHandle ← NIL;
END;
IF NOT qOH.bannerOnly
AND (qOH.currentStatus IN [sysRestartInSpooler..sysRestartInQueue] OR qOH.currentStatus IN [decomposeFailure..markFailure])
THEN BEGIN
Delete the interleaved interpress master, if any, before putting doc on bannerOnly queue:
WITH q: qOH SELECT FROM
feps9700 => IF q.ivFileID # NSFile.nullID THEN
q.ivFileID ← QueueFile.Delete[q.ivFileID];
ENDCASE;
qOH.priorStatus ← qOH.currentStatus;
qOH.bannerOnly ← TRUE;
qOH.completionDate ← System.GetGreenwichMeanTime[];
IF state.trace # none THEN BEGIN
PSAsyncMsg.Expand1AndPutString["$$Document \"<1>\" Moved to BannerOnlyQ", qOH.fileName];
END;
[] ← PrintQueue.Requeue[qOH: qOH, fromQueue: temp, toQueue: bannerOnly];
END
ELSE BEGIN
IF qOH.bannerOnly THEN qOH.currentStatus ← qOH.priorStatus;
qOH.completionDate ← BasicTime.Now[];
Notify command level that a document was aborted and send pertenant data...
SELECT qOH.currentStatus FROM
spoolFailure => state.docCompleted[status: spoolFailure, documentID: qOH.uid];
ENDCASE => WITH q: qOH SELECT FROM
fax495 => BEGIN
A fax job may be aborted AFTER a local print and some number of remote transmissions.
printed: CARDINAL
IF q.localPrintStatus = printed THEN 1 ELSE 0;
transmitted: CARDINAL ← 0;
FOR i: CARDINAL IN [0..q.phoneNoCount) DO
IF q.transmitData[i].status = transmitted THEN transmitted ← transmitted + 1
ENDLOOP;
state.docCompleted[status: aborted, documentID: q.uid, docsPrinted: printed, docsTransmitted: transmitted];
END;
ENDCASE => state.docCompleted[status: aborted, documentID: q.uid];
IF state.trace # none THEN BEGIN
nsAborted: NSString.String = Str["$$Document ""<1>"" Aborted"L];
PSAsyncMsg.Expand1AndPutString[nsAborted, qOH.fileName];
END;
Requeue deletes spooled file and interleaved interpress master, if any...
[] ← PrintQueue.Requeue[
qOH: qOH, fromQueue: temp, toQueue: inactive];
END;
BROADCAST abortQProcessed;
ENDLOOP;
END; -- ProcessAbortedQueue
MarkedQEntry: ENTRY PROCEDURE = -- called by PrintQueue when a QObj shows up on marked queue
BEGIN
ENABLE UNWIND => NULL;
BROADCAST markedQEvent;
IF state.trace = verbose THEN
PSAsyncMsg.PutMesaString["$$BROADCAST markedQEvent"L];
END; -- MarkedQEntry
-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
ProcessMarkedQueue: PUBLIC ENTRY PROCEDURE = BEGIN
-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
<<During initialization all jobs found on the marked queue are processed and moved to the inactive queue. Otherwise, only one job at a time is processed. ProcessMarkedQueue waits until the print queue implementation forks MarkedQEntry, notifying that a job has been moved to the marked queue. To guard against any notification being missed, there is a one minute timeout on markedQEvent.>>
markedQInitialized: BOOLEANFALSE;
qOH: PrintQueue.QueueObjectHandle ← PrintQueue.nilQueueObjectHandle;
Process.SetPriority[Process.priorityBackground];
PrintQueue.QueueWatcher[MarkedQEntry, marked];
DO -- forever
IF markedQInitialized THEN WAIT markedQEvent;
<<Normally when doing a wait, it is proper to check a variable before
proceeding (in case of a bogus broadcast, for example). We cover
that base by checking for nilQueueObjectHandle when we try to get
the next job off of the marked queue.>>
qOH ← PrintQueue.Requeue[fromQueue: marked, toQueue: temp];
IF qOH = PrintQueue.nilQueueObjectHandle THEN {markedQInitialized ← TRUE; LOOP};
IF qOH.printObjectHandle # NIL THEN BEGIN
DecomposerControl.FreeDecomposeObject[qOH.uid];
qOH.printObjectHandle ← NIL;
END;
IF qOH.bannerOnly THEN qOH.currentStatus ← qOH.priorStatus;
qOH.completionDate ← System.GetGreenwichMeanTime[];
Notify command level that a document has completed
and send pertenant data...
WITH q: qOH SELECT FROM
fax495 => BEGIN
printed: CARDINAL
IF q.localPrintStatus = printed THEN 1 ELSE 0;
transmitted: CARDINAL ← 0;
FOR i: CARDINAL IN [0..q.phoneNoCount) DO
IF q.transmitData[i].status = transmitted THEN
transmitted ← transmitted + 1;
ENDLOOP;
IF qOH.bannerOnly THEN
state.docCompleted[status: aborted, documentID: q.uid,
docsPrinted: printed, docsTransmitted: transmitted]
ELSE state.docCompleted[status: successful, documentID: q.uid,
docsPrinted: printed, docsTransmitted: transmitted];
END;
ENDCASE => IF qOH.bannerOnly THEN
state.docCompleted[
status: aborted, documentID: q.uid, docsPrinted: 0]
ELSE state.docCompleted[
status: successful, documentID: q.uid, docsPrinted: 1];
IF state.trace # none THEN BEGIN
nsCompleted: NSString.String = Str["$$Document ""<1>"" Completed: <2>; Elapsed time since decomposing: <3>"L];
stringArray: ARRAY [0..3) OF NSString.String;
startSinceEpoch: LONG CARDINAL
← System.SecondsSinceEpoch[qOH.startDecomposeDate];
endSinceEpoch: LONG CARDINAL
← System.SecondsSinceEpoch[qOH.completionDate];
timeElapsed: LONG CARDINAL ← 0;
sTime: STRING ← [20];
sElapsed: STRING ← [20];
Name of document
stringArray[0] ← qOH.fileName;
Get current time
Time.Append[sTime, Time.Unpack[qOH.completionDate]];
stringArray[1] ← Str[sTime];
Get elapsed time
IF endSinceEpoch >= startSinceEpoch THEN
timeElapsed ← endSinceEpoch - startSinceEpoch;
String.AppendLongDecimal[sElapsed, timeElapsed];
stringArray[2] ← Str[sElapsed];
PSAsyncMsg.ExpandArrayAndPutString[nsCompleted, DESCRIPTOR[stringArray]];
END;
Requeue deletes spooled file...
[] ← PrintQueue.Requeue[
qOH: qOH, fromQueue: temp, toQueue: inactive];
BROADCAST markedQProcessed;
ENDLOOP;
END; -- ProcessMarkedQueue
-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
ProcessForwardedQueue: PUBLIC ENTRY PROCEDURE =
-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
BEGIN
<<During initialization all jobs found on the forwarded queue are processed
and moved to the inactive queue. Otherwise, only one job at a time
is processed. ProcessForwardedQueue waits until the print queue
implementation forks ForwardedQEntry, notifying that a job
has been moved to the forwarded queue. To guard against any
notification being missed, there is a one minute timeout on forwardedQEvent.>>
ForwardedQEntry: ENTRY PROCEDURE = -- called by PrintQueue when a QObj shows up on forwarded queue
BEGIN
ENABLE UNWIND => NULL;
BROADCAST forwardedQEvent;
IF state.trace = verbose THEN
PSAsyncMsg.PutMesaString["$$BROADCAST forwardedQEvent"L];
END; -- ForwardedQEntry
forwardedQInitialized: BOOLEANFALSE;
qOH: PrintQueue.QueueObjectHandle ← PrintQueue.nilQueueObjectHandle;
Process.SetPriority[Process.priorityBackground];
PrintQueue.QueueWatcher[ForwardedQEntry, forwarded];
DO -- forever
IF forwardedQInitialized THEN WAIT forwardedQEvent;
<<Normally when doing a wait, it is proper to check a variable before
proceeding (in case of a bogus broadcast, for example). We cover
that base by checking for nilQueueObjectHandle when we try to get
the next job off of the forwarded queue.>>
qOH ← PrintQueue.Requeue[fromQueue: forwarded, toQueue: temp];
IF qOH = PrintQueue.nilQueueObjectHandle THEN {forwardedQInitialized ← TRUE; LOOP};
IF qOH.bannerOnly THEN qOH.currentStatus ← qOH.priorStatus;
qOH.completionDate ← System.GetGreenwichMeanTime[];
Notify command level that a document was forwarded
and send pertenant data...
WITH q: qOH SELECT FROM
feps9700 => IF qOH.bannerOnly THEN
state.docCompleted[
status: aborted, documentID: q.uid, docsPrinted: 0]
ELSE state.docCompleted[
status: successful, documentID: q.uid, docsPrinted: 1];
ENDCASE => ERROR; --only feps9700 uses the forwarded queue
IF state.trace # none THEN BEGIN
nsCompleted: NSString.String = Str["$$Document ""<1>"" Completed: <2>; Elapsed time since decomposing: <3>"L];
stringArray: ARRAY [0..3) OF NSString.String;
startSinceEpoch: LONG CARDINAL
← System.SecondsSinceEpoch[ qOH.startDecomposeDate];
endSinceEpoch: LONG CARDINAL
← System.SecondsSinceEpoch[qOH.completionDate];
timeElapsed: LONG CARDINAL ← 0;
sTime: STRING ← [20];
sElapsed: STRING ← [20];
Get document name
stringArray[0] ← qOH.fileName;
Get current time
Time.Append[sTime, Time.Unpack[qOH.completionDate]];
stringArray[1] ← Str[sTime];
Get elapsed time
IF endSinceEpoch >= startSinceEpoch THEN
timeElapsed ← endSinceEpoch - startSinceEpoch;
String.AppendLongDecimal[sElapsed, timeElapsed];
stringArray[2] ← Str[sElapsed];
PSAsyncMsg.ExpandArrayAndPutString[nsCompleted, DESCRIPTOR[stringArray]];
END;
Requeue deletes interleaved interpress master...
(Spooled file was deleted by MergeControlImpl)
[] ← PrintQueue.Requeue[
qOH: qOH, fromQueue: temp, toQueue: inactive];
BROADCAST forwardedQProcessed;
ENDLOOP;
END; -- ProcessForwardedQueue
Str: PROCEDURE [s: REF TEXT] RETURNS [ns: NSString.String] = INLINE {
RETURN[NSString.StringFromMesaString[s]]};
END. -- of QueueControlImpl
LOG
****EARLIER LOG ENTRIES DELETED. See archived version from 8.0.
17-Sep-84 16:18:39 - Jacks - Added ModifyTraceLevel and ProcessForwardedQueue.
16-Oct-84 11:27:44 - Jacks - Added actual code to ProcessForwardedQueue.
15-Nov-84 17:16:20 - Jacks - ProcessAbortedQueue now deleted interleaved interpress master file, if any, before placing document on bannerOnly queue; updated to new QueueControl interface.
17-Jun-85 15:46:28 - Jacks - Added copyright notice.
17-Jul-85 14:26:31 - Jacks - New PSAsyncMsg interface.
6-Aug-85 9:15:57 - Jacks - Updated to 10.0 DecomposerControl.FreeDecomposeObject.
15-Nov-85 10:23:30 - Jacks - Count bannerOnly docs as aborted instead of printed.
11-Apr-86 11:22:43 - Jacks - MAJOR CHANGES to prevent bug which occured when many jobs were aborted because of spool failure. PrintQueueImpl was forking processes to call AbortedQEntry right and left, but they were all monitor locked out. This was because ProcessAbortedQueue just kept processing the aborted queue because the queue was never empty. It never gave up the lock and eventually the server crashed when over 140 processes had been forked by PrintQueueImpl (error = TooManyProcesses).
To prevent this, ProcessAbortedQueue now processes only ten jobs at a time, then it must wait to be notified by PrintQueueImpl. Hopefully waiting after every ten jobs will prevent PrintQueueImpl from forking too many processes than never get through. I tried processing only one job at a time, but that didn't work because sometimes AbortedQEntry would be executed more than once before ProcessAbortedQueue would run, causing ProcessAbortedQueue to miss some notifies and leave jobs orphaned on the aborted queue.
ProcessMarkedQueue and ProcessForwardedQueue don't have the problem of jobs coming on their queues to quickly to keep up. So those procedures process one job every time they are notified by PrintQueueImpl. Also, since they are very often used, there are timeouts on the conditions variables which wake them up (just in case a notify is missed or something).
21-Apr-86 14:12:16 - Jacks - Modified trace code.
16-May-86 15:02:03 - Jacks - No longer check QEntry variables when waiting on QEvents; when event is broadcast, processing begins and we check for nilQueueObjectHandle.