//Spruce.Bcpl - last modified:March 4, 1980 9:41 AM // Spruce.Bcpl -- Spruce Spooler -- Main Procedure // errors 100 get "BcplFiles.d" get "Spruce.d" get "sprucefiles.d" get "PressFile.d" // outgoing procedures external // defined here [ Spruce Sprouller SpruceUserFinishProc ] // incoming procedures external // external [ // SpruceSpool PrepareToSpool; SomeoneIsKnocking; SpoolAFile // SpruceUser Post // SpruceQueue CheckPointQueue; CleanupQueue; MarkQueueEntry; SortQueue // SpruceCheck CheckPoint; RestoreFromCheckPoint; ActOnEntry // SpruceInit SpruceInit // SpruceUtils FindErrorMessage; FSGetX; FSPut; SetRMR; Reclaim // Spruce Errors SpruceCondition // SpruceMl InitFrameRuntime // Timer Dismiss; SetTimer; TimerHasExpired // Contexts Block; CallContextList; Unqueue // Interrupts DisableInterrupts // OS InLd; MoveBlock; ReadCalendar; StartIO // statics ctxQ; CheckFp; stopsPrinting; SpoolVec Debug; DebugSystem; DPzero; symsFile; Capabilities inMsg; lvSwatContextProc; lvUserFinishProc; numMustPrint; numFilesSpooled; outMsg PressServer; printDoc; printing; savedSCP; spooling; timeRestart sprintFPRD; SproullerQ; spruceFPRD; SpruceSavedUFP; SpruceZone firstDCB //defined here ] static firstDCB manifest MinTarryTime = 300 // at least 3 sec. after any invocation manifest PostSpoolerTarryTime = 300 // wait at least 3 sec. before returning manifest PostSpoolerKnockTime = 700 // wait 7 seconds before printing, if someone else knocked manifest PostPrinterTarryTime = 1000 // 10 seconds after printer return, if someone else knocked or error // Discussions: // ------------------------------------------------------ // numMustPrint is an optimization. You can ignore references to it on first reading. It is a count of // the number of files that must be printed before more spooling can occur -- used to flush all or part of // the spool queue, to make room, or assure forward progress. // Spooling requests generally have priority over formatting and printing. While one file is being // accepted, others are rejected, but the spooling detects "knocking": If there's any indication at all that // another file might be on the way, Spruce tarries a while waiting for it. Otherwise, after a minimum // delay to allow reading the file transmission message posted on the screen, if there's anything to // print, it is printed. // ------------------------------------------------------ let Spruce(pDoc, userParams, runCfa; numargs na) be // ------------------------------------------------------ // Called from OS. Controls switching to Initialization, then recalled after Junta, and dispatches to // operating contexts. [ //pDoc is actually BLDR layout vector DPzero= table [ 0;0 ] CheckFp = table [ 0; 0; 0; 0; 0; 0 ] // FP for checkpoint file, for use after Junta symsFile = table [ 0; 0; 0; 0; 0; 0 ] // FP for symsFile MoveBlock(CheckFp, pDoc, 6) // lFP is currently 5 // First, call initialization routine. It will call Spruce(pDoc) when finished. if na ne 1 then SpruceInit(userParams, runCfa) // Among the contexts is one that runs Sprouller, below. This here's the Spooler! CallContextList(ctxQ!0) repeat ] // ------------------------------------------------------ and Sprouller() be // ------------------------------------------------------ // Main Spruce control. Repeatedly monitor the network, accept files, print them by deferring to the // Sprint program, and analyze the reports that Sprint provides on its return. In addition, control Spruce's // tendencies to dally when there are files to be printed. It does this for two reasons: to give people time // to read the system status and error reports on the screen, and when it is suspected that someone is // about to send a file (a request was heard when Spruce or Sprint was doing something else). // When files have been spooled, but printing is disabled, store the queue and other vital // information in the checkpoint after the tarry interval -- then set the tarry interval to 5 minutes, to // avoid doing this often. This setting is useful only for recovery via "Spruce Restart". The queue is not // sorted into print priority order. // After the appropriate tarry time, if not printing or nothing to print, and if the spooling queue is // dirty, save it (in arrival order) in checkpoint file (all via CheckPointQueue()). [ let tarry = false; SetTimer(lv tarry, MinTarryTime) // This loop restarts at the top (via SpruceInit ) after every call to the printer. [ Block() // inMsg provides results of the print operation, if any. It exists, but is null, the first time. if inMsg& AnalyzeResults() then SetTimer(lv tarry, PostPrinterTarryTime) // Satisfy any new spooling requests, if legal. numMustPrint=0 if SomeoneIsKnocking() then SetTimer(lv tarry, SpoolAFile()? PostSpoolerKnockTime, PostSpoolerTarryTime) // may set numMustPrint // Tarry for the appropriate interval, then switch to the printing program if anything's to print. if numMustPrint&printing then until TimerHasExpired(lv tarry) do Block() // MUST print next if TimerHasExpired(lv tarry) then test printing&numFilesSpooled then PrintFiles() or [ CheckPointQueue(); SetTimer(lv tarry, 1) ] // keep timer fresh ] repeat ] // ------------------------------------------------------ and SpruceUserFinishProc() be // ------------------------------------------------------ // Called as Spruce finishes -- shut everything down, including RAM tasks. [ @lvSwatContextProc = savedSCP @#420 = 0 SetRMR(#177776) // setup for silent boot StartIO(#100000) // Silent boot for i = 0 to 30000 do [ ] // wait for display to quiesce @lvUserFinishProc=SpruceSavedUFP ] // ------------------------------------------------------ and PrintFiles() be // ------------------------------------------------------ // There are files to print. Fill in the outMsg supplied to Sprint with last-minute global printing // information, sort the SproullerQ (spooling queue) into print-priority order, and save the queue in the // LEVRunDocs region (see SpruceQueue, SpruceCheck). Shut things down, and defer to the previously- // saved Sprint image. Sprint will resume at the return from the initial OutLd in SpruceInit() -- all state // will be rebuilt in SpruceInit(), Sprouller() and AnalyzeResults() from information in the checkpoint // file and in the returned inMsg. // If the lead document after the sort was "inProgress" -- had been partially processed when Sprint // last suspended in favor of Spruce -- indicate permission to resume the processing. Otherwise, the in- // progress document has been aborted or rescheduled, and processing should begin at the beginning. // Sprint's suspend-resume code has complementary testing and state reporting (see also // AnalyseResults(), below). [ unless spooling do numMustPrint = numFilesSpooled // no switchout if spooling off // SortQueue(SORTPriority) //restore appropriate entry from QueueFile // let pDoc = @SproullerQ let pDoc = printDoc let inProg = pDoc>>DocG.inProgress ActOnEntry(SpoolVec!(SpoolVec!0), true) // restore last entry pDoc>>DocG.inProgress = true // even now ActOnEntry(SpoolVec!(SpoolVec!0), false) // write back Reclaim() //save appropriate entry in from QueueFile CheckPoint(LEVEternalStatics, LEVEternalStatics) CheckPoint(LEVSharedRun, LEVReport) // printDoc = pDoc // saved over checkpoint call, which uses printDoc as a temporary ~~ consider fix outMsg = FSGetX(20, SpruceZone, 0) // (lInLdMessage) MoveBlock(lv outMsg>>TOPrinterMsg.fprd, spruceFPRD, 5) // (lFP) outMsg>>TOPrinterMsg.Capabilities = Capabilities if inProg then [ outMsg>>TOPrinterMsg.inProgress = true outMsg>>TOPrinterMsg.fileCode = printDoc>>DocG.PressFile>>SPruceFile.fileCode // ~~ duplicate LEVSharedRun info here, for full swap situation outMsg>>TOPrinterMsg.numSpooled = numFilesSpooled outMsg>>TOPrinterMsg.numMustPrint = numMustPrint outMsg>>TOPrinterMsg.DebugSystem = DebugSystem ] @#420 = 0; SetRMR(#177776); StartIO(#100000) // display off, silent boot -- stop RAM tasks InitFrameRuntime(false) DisableInterrupts() InLd(sprintFPRD, outMsg) // go print ] // N.B. Have not saved self, so control returns within SpruceInit, thence to top of main loop // ------------------------------------------------------ and AnalyzeResults() = valof // ------------------------------------------------------ // There is an inMsg, so (because this routine deletes it), the system has just been started, or // Sprint has just resumed Spruce (see PrintFiles(), above, and SpruceInit()). The entire story is revealed // in inMsg, which must be analyzed with reference to the retrieved SproullerQ -- still in priority order. // inMsg reveals the number of completely processed files and commands, and the file index (see // SpoolAFile() and SpruceQueue) of the last one. It also contains a code (CC..., see Spruce.d), describing // the reason for resumption -- one indicates system (re)startup (then very little of this applies). // CCFinished indicates the completion of all processing. In all other cases, processing of some entry was // underway; its file index is also in the message. The values are checked against previous stored queue // entries, and discrepancy messages are posted. The status of the queued documents is also updated. // Finally, analyze suspension reason and display it. Often, a printing error or printer problem (e.g., // jam) occurred: the completion code is a Spruce error code, obtained from Spruce.Errors, substituting // parameters extracted from the original Sprint error call and supplied in inMsg. If the code doesn't let // processing continue (only spooling and keyboard-initiated suspensions and those due to printer jams // and such may continue), abort processing of the in-progress document by changing its status. // To finish up, sort the queue in arrival order, start up the spooler's listener, and delete inMsg. // Returns true only if a longer dalliance is desirable due to error reporting or spooling requests. [ let completionCode, inProg = inMsg>>TOSpoolerMsg.completionCode, false // unless completionCode eq CCInit do // [ RestoreFromCheckPoint(LEVSharedRun, LEVReport, 0 , true) RestoreFromCheckPoint(LEVEternalStatics, LEVEternalStatics, 0 , true) // ] switchon completionCode into [ case CCInit: endcase // nothing to do -- cold start case CCRestart: timeRestart = FSGetX(2); ReadCalendar(timeRestart) spooling, printing = false, false; docase -1 // go resort queue default: inProg = true // suspension before completion case CCFinished: [ // let pDoc, lastCode = @SproullerQ, 0 let pDoc, lastCode = 0, 0 // mark printed files printed, check for consistency for i = 1 to inMsg>>TOSpoolerMsg.numPrinted do [ ActOnEntry(SpoolVec!i, true) pDoc = printDoc // unless pDoc¬ pDoc>>DocG.printed do SpruceCondition(150, ECSpoolTerminate) let stat = STATPrinted + (pDoc>>DocG.protected? STATNeedsPassword, 0) MarkQueueEntry(pDoc, stat, true) // mark, but don't repair queue yet lastCode = pDoc>>DocG.PressFile; lastCode = lastCode eq -1? 0, lastCode>>SPruceFile.fileCode Post(pDoc, 0, "Printing Complete") ActOnEntry(SpoolVec!i, false) let lastDoc = pDoc // pDoc = @pDoc ] unless lastCode eq inMsg>>TOSpoolerMsg.lastPrintedCode do SpruceCondition(151, ECSpoolTerminate) stopsPrinting = inMsg>>TOSpoolerMsg.stopsPrinting // Handle completion report if inProg then [ unless pDoc& inMsg>>TOSpoolerMsg.inProgressCode eq pDoc>>DocG.PressFile>>SPruceFile. fileCode do SpruceCondition(152, ECSpoolTerminate) test completionCode eq CCKnock then [ Post(pDoc,0, "Suspended printing to honor spooling request") MarkQueueEntry(pDoc, STATInProg, true ) ] or test completionCode > maxCCOK then [ PressServer = true // inhibit automatic quit let errorClass = inMsg>>TOSpoolerMsg.errorClass let errorSpec = lv inMsg>>TOSpoolerMsg.reportFailed @errorSpec = completionCode // ~~ should redo all reports // ~~ could use new SpruceCondition stuff here, but would have to demote fatal print errors some let str = vec lenErrStr FindErrorMessage(errorSpec, str, lenErrStr) Post(pDoc, errorClass, str) // ~~ shd mark inProgress only a few times, then give up -- shd record abort condition better let stat = STATPrinted + (pDoc>>DocG.protected? STATNeedsPassword, 0) MarkQueueEntry(pDoc, (errorClass eq ECEngineTerminate? STATInProg, stat) , true) ] or Post(pDoc, 0, "Suspended printing for unknown reason") ] ] case -1: // Restart merges here // SortQueue(SORTFifo) // Restore chronological order // CleanupQueue() // Mark new avails, etc.; Only legal when queue in fifo order ] // switchon // Do whether returning from printer, starting, or restarting unless PressServer%numFilesSpooled do [ Dismiss(200); finish ] // Stand Alone, all done, good bye PrepareToSpool() // open up, first time or after printing inMsg = FSPut(inMsg) // delete message Post(0,ECStateRequest,"") resultis completionCode > CCFinished ] // ------- History . . . // DCS, July 15, 1977 4:40 PM, called Sprouller, derived from Spruce.Bcpl // July 16, 1977 3:33 PM, yield Storage Management, // Error, Overlay, Miscellaneous utilities to Spruceutils // July 18, 1977 9:31 PM, first cut at main spooler control loop // July 21, 1977 5:09 PM, first pass at shared-statics, LEVRun checkpoint version -- // almost complete. // July 22, 1977 4:25 PM, add OutLd, InLd stuff // July 25, 1977 9:52 AM, restore state after return from printer // July 25, 1977 3:23 PM, add a q sorter of sorts, better analysis // July 29, 1977 3:12 PM, handle stand-alone stuff better // August 3, 1977 1:19 PM, repair FIFO sort // August 8, 1977 9:38 PM, add error management code converging on correct // August 22, 1977 1:57 PM, freeFile set up before calling SortSpooledFiles // August 28, 1977 7:50 AM, Spruce->Sprint, Sprouller->Spruce // September 7, 1977 12:56 PM, add queue control (numMustPrint, etc.) // October 25, 1977 10:18 AM, numMustPrint=numFilesSpooled if not spooling // November 3, 1977 7:37 AM, improve spooler-printer comm. in full swap, v4.(2,7) // November 3, 1977 1:36 PM, user spooler for stand-alone stuff. v4.(2,7) // December 20, 1977 12:11 PM, SwatContextProc in finish for Trident // May 15, 1978 9:49 PM, improve tarry timing // August 31, 1978 7:28 AM, major reorganization, some queue mgmt. moved to SpruceQueue // September 1, 1978 9:34 AM, use SpruceCondition statt SpruceError -- will post // September 4, 1978 5:18 PM, repair and simplify tarry timing // September 5, 1978 8:31 AM, use MarkQueueEntry to modify queue entry status // September 5, 1978 9:30 AM, silent boot set up at time used // September 12, 1978 3:44 PM, add CheckCfa // September 12, 1978 11:36 PM, adapt further to new installation stuff // September 18, 1978 9:28 AM, CheckCfa -> CheckFp // September 20, 1978 3:35 PM, format, document // September 22, 1978 10:09 PM, handle warm restart // August 1, 1979 11:11 AM, pass output bin status back and forth thru inMsg/outMsg // August 1, 1979 12:06 PM, reset needsPassword bit when printed // August 9, 1979 11:54 AM, pass Capabilities thru TOPrinterMsg; PrintFiles says in progress only if it is // August 29, 1979 12:29 PM, use LEVReport // March 4, 1980 9:41 AM, use static firstDCB to prevent loss of display stream // October 25, 1982 2:02 PM add LEVEternalStatics checkpointing calls (635)\120b18B403b12B84b9B280b10B29b11B1707b38B108b58B4021b2B27b45B26b22B35b63B44b162B39b2B2793b2B38b2B61b73B332b2B37b27B114b49B149b1B199b31B20b3B1402b2B60b2B