<<>> <> <> <> <> <> <> <> DIRECTORY Basics USING [BITXOR], CedarProcess USING [CheckAbort, Fork, Process], Commander USING [Handle], CommandTool USING [CurrentWorkingDirectory, DoCommand], IO USING [PutF, PutFR, PutRope, refAny, rope, STREAM], List USING [AList, DotCons], MakeDo, MakeDoBasics, MakeDoPrivate, MessageWindow USING [Append, Blink], Process USING [Abort, ConditionPointer, EnableAborts, GetCurrent, InitializeCondition, Pause, SecondsToTicks], ProcessProps USING [AddPropList, GetPropList], RefTab, Rope USING [Cat, Equal, ROPE], UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangeReason]; MakeDoBasicImpl: CEDAR MONITOR IMPORTS Basics, CedarProcess, CommandTool, IO, List, MakeDo, MakeDoBasics, MakeDoPrivate, MessageWindow, Process, ProcessProps, RefTab, Rope, UserProfile EXPORTS MakeDo, MakeDoBasics <> < some process in Basic monitor.>> <> <> <> <> <> <> < node not ready or waiting.>> < node in some queue.>> <> < make nodes inactive regardless of currency.>> < queues not all empty.>> = BEGIN OPEN MakeDoBasics, MakeDo, MakeDoPrivate; ROPE: TYPE = Rope.ROPE; RefTable: TYPE = MakeDo.RefTable; NodeRep: PUBLIC TYPE = MakeDoPrivate.NodeRep; ActionRep: PUBLIC TYPE = MakeDoPrivate.ActionRep; NodeClassRep: PUBLIC TYPE = MakeDoPrivate.NodeClassRep; in: BOOL _ FALSE; <> whosIn: UNSAFE PROCESS; whatIn: PROC; monitorChange: CONDITION; entryTimeout: INT _ 60; <> DoIn: PUBLIC PROC [p: PROC] = { CedarProcess.CheckAbort[]; Enter[p]; p[!UNWIND => Exit[]]; Exit[]; RETURN}; Enter: ENTRY PROC [p: PROC] = { ENABLE UNWIND => NULL; WHILE in DO WAIT monitorChange ENDLOOP; in _ TRUE; whosIn _ Process.GetCurrent[]; TRUSTED {whatIn _ p}; RETURN}; Exit: ENTRY PROC = { ENABLE UNWIND => NULL; in _ FALSE; whosIn _ NIL; whatIn _ NIL; NOTIFY monitorChange; RETURN}; TrackProfile: PROC [reason: UserProfile.ProfileChangeReason] --UserProfile.ProfileChangedProc-- = { SELECT reason FROM rollBack, firstTime, newUser => { forkParms: ForkParms _ ForkParmsFromProfile[]; SetForkParms[forkParms]; }; edit => { reason _ reason; }; ENDCASE => ERROR; alwaysGush _ UserProfile.Boolean["MakeDo.AlwaysGush", FALSE]; deleteEmptyAuxBox _ UserProfile.Boolean["MakeDo.DeleteEmptyAuxBox", TRUE]; RETURN}; SetForkParms: PUBLIC --ENTRY-- PROC [forkParms: ForkParms] = { SPAWork: --INTERNAL-- PROC = { InnerSetForkParms[forkParms]; BroadcastQueue[]; RETURN}; DoIn[SPAWork]; RETURN}; EndFork: PUBLIC --ENTRY-- PROC [resources: JobResources] = { InnerEndFork: --INTERNAL-- PROC = { RecordAFork[resources]; BroadcastQueue[]; RETURN}; DoIn[InnerEndFork]; RETURN}; halt: BOOL _ FALSE; readyQueue: NodeList _ NIL; workCount: INTEGER _ 0; waitQueue: RefTable _ MakeRefTable[]; queueChange: CONDITION; queueTimeout: INT _ 60; BroadcastQueue: ENTRY PROC = { ENABLE UNWIND => NULL; BROADCAST queueChange; RETURN}; NotifyQueue: ENTRY PROC = { ENABLE UNWIND => NULL; NOTIFY queueChange; RETURN}; WaitQueue: ENTRY PROC = { ENABLE UNWIND => NULL; WAIT queueChange; RETURN}; SetState: --INTERNAL-- PROC [n: Node, new: NodeState] = { ENABLE UNWIND => NULL; SELECT n.state FROM inactive => NULL; ready => {IF n#readyQueue.first THEN ERROR; readyQueue _ readyQueue.rest}; working => workCount _ workCount - 1; waiting => IF NOT DeleteFromRefTable[n, waitQueue] THEN ERROR; ENDCASE => ERROR; n.state _ new; SELECT n.state FROM inactive => NULL; ready => { readyQueue _ CONS[n, readyQueue]; }; working => workCount _ workCount + 1; waiting => { IF NOT waitQueue.Insert[n, $T] THEN ERROR; }; ENDCASE => ERROR; NotifyQueue[]; RETURN}; InnerEnqueue: --INTERNAL-- PROC [job: Job, n: Node, inServiceOf: Node _ NIL] = { IF n.state = inactive THEN { n.someoneWhoCares _ job; SetState[n, ready]; }; IF inServiceOf # NIL THEN { n.waitingOnCurrent _ CONS[inServiceOf, n.waitingOnCurrent]; inServiceOf.waitCount _ inServiceOf.waitCount + 1; SELECT inServiceOf.state FROM inactive, ready => ERROR; working => SetState[inServiceOf, waiting]; waiting => n _ n; ENDCASE => ERROR; }; RETURN}; InnerEnsureMembership: --INTERNAL-- PROC [job: Job, ns: NodeSet, n: Node] = { IF (NOT ns.nodes.Fetch[n].found) THEN { IF NOT ns.nodes.Insert[n, $T] THEN ERROR; IF NOT n.memberships.Insert[ns, $T] THEN ERROR; CountNode[ns, n, 1]; IF n.current#true AND n.state = inactive THEN InnerEnqueue[job, n, NIL]; }; RETURN}; DestroyNodeSet: --ENTRY-- PROC [ns: NodeSet] = { DNSWork: --INTERNAL-- PROC = { UnMemberize: PROC [data: REF ANY] RETURNS [stop: BOOL _ FALSE] --RedBlackTree.EachNode-- = { n: Node = NARROW[data]; IF NOT n.memberships.Delete[ns] THEN ERROR; RETURN}; StatelessEnumerateRefTable[ns.nodes, UnMemberize]; ns^ _ [nodes: ns.nodes]; ns.nodes.Erase[]; RETURN}; DoIn[DNSWork]; RETURN}; CountNode: --INTERNAL-- PROC [ns: NodeSet, n: Node, SELECT n.current FROM notComputed, false => ns.nonCurrents _ ns.nonCurrents + true => { ns.currents _ ns.currents + SELECT n.broken FROM FALSE => ns.ok _ ns.ok + TRUE => ns.broken _ ns.broken + ENDCASE => ERROR; }; ENDCASE => ERROR; RETURN}; SetCurrency: PUBLIC --INTERNAL-- PROC [n: Node, current: ExpensiveBool] = { Discount: PROC [data: REF ANY] RETURNS [stop: BOOL _ FALSE] --RedBlackTree.EachNode-- = { ns: NodeSet = NARROW[data]; CountNode[ns, n, -1]; RETURN}; Count: PROC [data: REF ANY] RETURNS [stop: BOOL _ FALSE] --RedBlackTree.EachNode-- = { ns: NodeSet = NARROW[data]; CountNode[ns, n, 1]; RETURN}; IF n.current = current THEN RETURN; StatelessEnumerateRefTable[n.memberships, Discount]; n.current _ current; StatelessEnumerateRefTable[n.memberships, Count]; RETURN}; SetBroken: PUBLIC --INTERNAL-- PROC [n: Node, broken: BOOL] = { Discount: PROC [data: REF ANY] RETURNS [stop: BOOL _ FALSE] --RedBlackTree.EachNode-- = { ns: NodeSet = NARROW[data]; CountNode[ns, n, -1]; RETURN}; Count: PROC [data: REF ANY] RETURNS [stop: BOOL _ FALSE] --RedBlackTree.EachNode-- = { ns: NodeSet = NARROW[data]; CountNode[ns, n, 1]; RETURN}; IF n.broken = broken THEN RETURN; StatelessEnumerateRefTable[n.memberships, Discount]; n.broken _ broken; StatelessEnumerateRefTable[n.memberships, Count]; RETURN}; NoteAchievement: --INTERNAL-- PROC [goal: Node] = { SetCurrency[goal, true]; IF goal.state # working THEN ERROR; InnerGiveUp[goal]; RETURN}; GiveUp: --ENTRY-- PROC [goal: Node] = { GUWork: --INTERNAL-- PROC = {InnerGiveUp[goal]}; DoIn[GUWork]; RETURN}; InnerGiveUp: --INTERNAL-- PROC [goal: Node] = { IF goal.state = working THEN { SetState[goal, inactive]; goal.someoneWhoCares _ NIL; InnerWakeWaiters[goal]; }; IF readyQueue=NIL AND workCount=0 AND waitQueue.GetSize[]=0 THEN halt _ FALSE; RETURN}; InnerWakeWaiters: --INTERNAL-- PROC [n: Node] = { WHILE n.waitingOnCurrent # NIL DO waiter: Node = n.waitingOnCurrent.first; n.waitingOnCurrent _ n.waitingOnCurrent.rest; IF waiter.state # waiting THEN ERROR; waiter.waitCount _ waiter.waitCount - 1; SELECT waiter.waitCount FROM <0 => ERROR; =0 => SetState[waiter, ready]; >0 => NULL; ENDCASE => ERROR; ENDLOOP; RETURN}; Ensure: PUBLIC PROC [goals: RefTable, modifiabilitySpec: ModifiabilitySpec, supportFiles: RefTable, PerMissedSupportFile: PROC [Node], parent: Commander.Handle] RETURNS [okGoalCount, nonOKGoalCount, nSteps: NAT, failedSteps: ActionList, nonOKGoalList: NodeList] = { callingJob: Job _ NEW [JobRep _ [ wDir: CommandTool.CurrentWorkingDirectory[], goals: goals, processes: MakeRefTable[], otherModifiable: modifiabilitySpec ]]; goalNS: NodeSet = NEW [NodeSetPrivate _ [ nodes: MakeRefTable[] ]]; Initialize: --INTERNAL-- PROC = { StatelessEnumerateRefTable[goals, NoteGoal]; IF modifiabilitySpec#NIL THEN StatelessEnumerateRefTable[modifiabilitySpec, NoteMod] ELSE goals _ goals; RETURN}; NoteGoal: --INTERNAL-- PROC [data: REF ANY] RETURNS [stop: BOOL _ FALSE] --RedBlackTree.EachNode-- = { n: Node = NARROW[data]; IF uncurrentGoals THEN InnerUncurrentNode[n]; InnerSetModifiability[n, callingJob.goals, callingJob.otherModifiable]; InnerEnsureMembership[callingJob, goalNS, n]; RETURN}; NoteMod: --INTERNAL-- PROC [data: REF ANY] RETURNS [stop: BOOL _ FALSE] --RedBlackTree.EachNode-- = { n: Node = NARROW[data]; InnerSetModifiability[n, callingJob.goals, callingJob.otherModifiable]; RETURN}; <> {ENABLE UNWIND => { NoteEnsureUnwind[]; }; DoIn[Initialize]; [okGoalCount, nonOKGoalCount, nonOKGoalList] _ HelpEmptyQueue[goalNS: goalNS, callingJob: callingJob, parent: parent, supportFiles: supportFiles, PerMissedSupportFile: PerMissedSupportFile]; EnsureWDir[callingJob.wDir, parent]; }; nSteps _ callingJob.nSteps; failedSteps _ callingJob.failedSteps; goals.Erase[]; DestroyNodeSet[goalNS]; <0];>> RETURN}; uncurrentGoals: BOOL _ TRUE; NoteEnsureUnwind: --ENTRY-- PROC = { NEUWork: --INTERNAL-- PROC = { x: BOOL _ FALSE; IF readyQueue#NIL OR workCount#0 OR waitQueue.GetSize[]#0 THEN { halt _ TRUE; WHILE readyQueue # NIL DO victim: Node = readyQueue.first; SetState[victim, working]; InnerGiveUp[victim]; ENDLOOP; x _ x; }; RETURN}; DoIn[NEUWork]; RETURN}; HelpEmptyQueue: PROC [goalNS: NodeSet, callingJob: Job, parent: Commander.Handle, supportFiles: RefTable, PerMissedSupportFile: PROC [Node] ] RETURNS [okGoalCount, nonOKGoalCount: NAT, nonOKGoalList: NodeList] = { nonOKGoalList _ nonOKGoalList; DO ENABLE ABORTED => { KillIt: PROC [data: REF ANY] RETURNS [stop: BOOL _ FALSE] --RedBlackTree.EachNode-- = { process: CedarProcess.Process = NARROW[data]; TRUSTED {Process.Abort[process.process]}; RETURN}; StatelessEnumerateRefTable[callingJob.processes, KillIt]; }; caringJob: Job _ NIL; goal: Node _ NIL; toExecute: Action _ NIL; consistencyReason: ROPE _ NIL; fails: ExpensiveBool; haveWorkers, fork: BOOL; resources: JobResources; [caringJob, goal, toExecute, consistencyReason, fails, haveWorkers, okGoalCount, nonOKGoalCount, nonOKGoalList, fork, resources] _ GetActionToExecute[goalNS, callingJob, parent, supportFiles, PerMissedSupportFile]; IF toExecute = NIL THEN EXIT; <> {ENABLE UNWIND => { ReturnPermission[toExecute]; GiveUp[goal]; IF fork THEN EndFork[resources]; }; e: Execution = NEW [ExecutionPrivate _ [caringJob, toExecute, goal, parent, parent, fork, resources]]; toExecute.reasonWhyLastDone _ consistencyReason; IF fails = false THEN Msg[parent, "%g%g retry of %g\n", [integer[e.a.timesTried.SUCC]], [rope[SELECT e.a.timesTried FROM 0 => "st", 1 => "nd", 2 => "rd", ENDCASE => "th"]], [rope[toExecute.cmd]]]; IF debugging THEN Confirm[e.a.cmd]; IF fork THEN { Buffer[e]; {ENABLE UNWIND => Flush[e, TRUE, FALSE, toExecute.cmd]; IF fork THEN Msg[parent, "%lForking %g%l\n", [rope["e"]], [rope[e.a.cmd]], [rope["E"]]]; e.process _ CedarProcess.Fork[Execute, e, [inheritProperties: TRUE]]; IF NOT caringJob.processes.Insert[e.process, $T] THEN ERROR; }} ELSE { [] _ Execute[e]; }; }; ENDLOOP; RETURN}; processToExecution: PUBLIC RefTab.Ref _ RefTab.Create[equal: EqualProcess, hash: HashProcess]; ProcRef: PUBLIC TYPE ~ REF ProcRefRep; ProcRefRep: PUBLIC TYPE ~ MakeDoBasics.ProcRefRep; <> <> <> <> <<>> <> <> <> <> <> <<};>> <<>> HashProcess: RefTab.HashProc ~ { <> proc: ProcRef = NARROW[key]; val: CARDINAL _ 0; { N: INT ~ SIZE[PROCESS]/SIZE[CARDINAL]; T: TYPE ~ ARRAY [1..N] OF CARDINAL; a: T; a _ LOOPHOLE[proc.process, T]; FOR i: INT IN [1..N] DO val _ Basics.BITXOR[val, a[i]]; ENDLOOP; }; RETURN [val] }; EqualProcess: RefTab.EqualProc ~ { <> proc1: ProcRef = NARROW[key1]; proc2: ProcRef = NARROW[key2]; RETURN [proc1.process = proc2.process]; }; alwaysGush: BOOL _ FALSE; deleteEmptyAuxBox: BOOL _ FALSE; Execute: PROC [data: REF ANY] RETURNS [results: REF ANY _ NIL] --CedarProcess.ForkableProc-- = { me: ProcRef ~ NEW [ProcRefRep _ [process: LOOPHOLE[Process.GetCurrent[]]]]; e: Execution = NARROW[data]; wDir: ROPE = e.job.wDir; oldPropList: List.AList = ProcessProps.GetPropList[]; newPropList: List.AList _ CONS [ List.DotCons[key: $WorkingDirectory, val: wDir], CONS [ List.DotCons[key: $WorkingDirectoryStack, val: wDir], oldPropList]]; innerExecute: PROC ~ { {OPEN e; ENABLE UNWIND => IF e.forked THEN { EndFork[e.resources]; Flush[e, TRUE, FALSE, a.cmd]; [] _ job.processes.Delete[process]; }; failed: BOOL _ FALSE; IF forked THEN { WHILE e.process=NIL OR (NOT job.processes.Fetch[e.process].found) DO Process.Pause[Process.SecondsToTicks[1]] ENDLOOP; IF NOT processToExecution.Insert[me, e] THEN ERROR; }; IncrementStepCount[job]; SetES[e, doing]; IF NOT CommandTool.CurrentWorkingDirectory[].Equal[wDir, FALSE] THEN ERROR; -- HERE -- failed _ CommandTool.DoCommand[a.cmd, bch !ABORTED => {failed _ TRUE; CONTINUE}] = $Failure; IF forked AND NOT processToExecution.Delete[me] THEN ERROR; IF NeedToFinish[e] THEN { a.timesTried _ a.timesTried.SUCC; IF forked THEN Flush[e, alwaysGush OR gushMe OR failed, FALSE, a.cmd]; a.fails _ Expensify[failed]; IF failed THEN AddFailedCmd[job, a]; EnumerateResults[a, InnerSuspectNodeChange]; CheckIn[job, goal, a, e.process]; SetES[e, final]; }; }; IF e.forked THEN EndFork[e.resources]; }; ProcessProps.AddPropList[newPropList, innerExecute]; <> RETURN}; SetES: PUBLIC ENTRY PROC [e: Execution, es: ExecutionState] ~ { ENABLE UNWIND => NULL; e.es _ es; RETURN}; NeedToFinish: PUBLIC ENTRY PROC [e: Execution] RETURNS [BOOL] ~ { ENABLE UNWIND => NULL; SELECT e.es FROM initial, buffered => { MessageWindow.Append["Wait 'till I get started!", TRUE]; MessageWindow.Blink[]}; doing => {e.es _ ending; RETURN [TRUE]}; ending, final => NULL; ENDCASE => ERROR; RETURN [FALSE]}; InnerSetModifiability: PUBLIC --INTERNAL-- PROC [n: Node, goals: RefTable, ms: ModifiabilitySpec] = { new: Modifiability _ SELECT TRUE FROM goals.Fetch[n].found => yes, ms = NIL => guess, ms.Fetch[n].found => yes, ENDCASE => no; IF n.modifiability = new THEN RETURN; IF n.modifiability = no THEN [] _ InnerUnleafen[n]; n.modifiability _ new; InnerUncurrentNode[n]; RETURN}; GetActionToExecute: --ENTRY-- PROC [goalNS: NodeSet, callingJob: Job, parent: Commander.Handle, supportFiles: RefTable, PerMissedSupportFile: PROC [Node] ] RETURNS [job: Job, goal: Node, toExecute: Action _ NIL, consistencyReason: ROPE, fails: ExpensiveBool, haveWorkers: BOOL _ FALSE, okGoalCount, nonOKGoalCount: NAT _ 0, nonOKGoalList: NodeList _ NIL, fork: BOOL _ FALSE, resources: JobResources] = { <> <> <> <> <> <> <> <<}>> GAEWork: --INTERNAL-- PROC = { job _ job; DO a: Action; mandatoryMissing, sourcePresent, inputWanted: BOOL _ FALSE; sourceBroken, waits: BOOL _ FALSE; missedList: NodeList _ NIL; CheckSource: --INTERNAL-- PROC [n: Node, which: ActionDep, optional: BOOL] = { CedarProcess.CheckAbort[]; inputWanted _ TRUE; InnerSetModifiability[n, job.goals, job.otherModifiable]; IF n.modifiability=no AND n.class=fileClass AND supportFiles#NIL AND (NOT supportFiles.Fetch[n].found) AND ((NOT optional) OR InnerExists[n]) THEN { IF NOT supportFiles.Insert[n, $T] THEN ERROR; IF PerMissedSupportFile#NIL THEN PerMissedSupportFile[n]; }; IF n.created # notExistTime THEN sourcePresent _ TRUE ELSE IF NOT optional THEN { mandatoryMissing _ TRUE; missedList _ CONS[n, missedList]; }; SELECT n.current FROM true => { IF n.broken THEN SetBroken[goal, sourceBroken _ TRUE]; RETURN; }; false => SetCurrency[goal, false]; notComputed => NULL; ENDCASE => ERROR; InnerEnqueue[job, n, goal]; waits _ TRUE; }; ComputeResults: PROC ~ { SurveyNode: PROC [data: REF ANY] RETURNS [stop: BOOL _ FALSE] --RedBlackTree.EachNode-- = { n: Node = NARROW[data]; IF n.current=true AND NOT n.broken THEN { okGoalCount _ okGoalCount + 1; } ELSE { nonOKGoalCount _ nonOKGoalCount + 1; nonOKGoalList _ CONS[n, nonOKGoalList]; }; RETURN}; okGoalCount _ nonOKGoalCount _ 0; nonOKGoalList _ NIL; StatelessEnumerateRefTable[goalNS.nodes, SurveyNode]; IF deleteEmptyAuxBox AND readyQueue=NIL AND workCount=0 AND waitQueue.GetSize[]=0 AND NOT AuxBoxDestroyed[] THEN DestroyAuxBox[]; RETURN}; IF goal # NIL AND goal.state = working THEN ERROR; DO CedarProcess.CheckAbort[]; IF goalNS.nonCurrents=0 THEN {ComputeResults; RETURN}; IF readyQueue#NIL THEN EXIT; IF workCount=0 THEN { IF waitQueue.GetSize[]#0 THEN { FindCycles[]; LOOP}; <> { EnsureQueued: PROC [data: REF ANY] RETURNS [stop: BOOL _ FALSE] --RedBlackTree.EachNode-- = { n: Node = NARROW[data]; IF n.state # inactive THEN ERROR; IF n.current#true THEN InnerEnqueue[callingJob, n, NIL]; RETURN}; StatelessEnumerateRefTable[goalNS.nodes, EnsureQueued]; IF readyQueue = NIL THEN ERROR; LOOP; }; }; Exit[]; WaitQueue[ ! <> UNWIND => Enter[GAEWork] <> ]; Enter[GAEWork]; ENDLOOP; goal _ readyQueue.first; IF goal.state # ready THEN ERROR; SetState[goal, working]; IF halt THEN { InnerGiveUp[goal]; IF readyQueue=NIL AND workCount=0 AND waitQueue.GetSize[]=0 THEN {ComputeResults; RETURN}; LOOP}; <> {ENABLE UNWIND => InnerGiveUp[goal]; IF (job _ goal.someoneWhoCares) = NIL THEN ERROR; EnsureWDir[job.wDir, parent]; IF goal.current=true THEN ERROR --shouldn't have current nodes in queue--; IF debugging THEN Log["Examining %g", [rope[goal.name]] ]; SetBroken[goal, goal.modifiability=yes AND goal.created=notExistTime]; IF goal.producer = NIL THEN InnerGetProduced[goal]; IF InnerLeaf[goal] THEN {NoteAchievement[goal]; LOOP}; a _ goal.producer.a; IF a.permissionGranted THEN { a.waitingForPermission _ CONS[goal, a.waitingForPermission]; goal.waitCount _ 1; SetState[goal, waiting]; LOOP}; a.permissionGranted _ TRUE; a.queuedFailsInvalidation _ FALSE; <> {ENABLE UNWIND => InnerReturnPermission[a]; goalExists: BOOL ~ goal.created#notExistTime; IF debugging THEN Log["Examining %g", [rope[a.cmd]] ]; InnerEnumerateSources[a, cmd, CheckSource]; IF waits THEN {InnerReturnPermission[a]; LOOP}; IF sourceBroken THEN {InnerReturnPermission[a]; NoteAchievement[goal]; LOOP}; SELECT a.derivedFromCurrentDeterminers FROM TRUE => NULL; FALSE => { InnerRederive[a]; a.derivedFromCurrentDeterminers _ TRUE; }; ENDCASE => ERROR; mandatoryMissing _ sourcePresent _ inputWanted _ FALSE; InnerEnumerateSources[a, data, CheckSource]; IF waits THEN {InnerReturnPermission[a]; LOOP}; IF sourceBroken THEN {InnerReturnPermission[a]; NoteAchievement[goal]; LOOP}; IF mandatoryMissing AND goal.modifiability=yes THEN {el: ROPE; ec: CARDINAL; [el, ec] _ EnglishList[missedList]; Warning[IO.PutFR["Can't %g because %g %g exist", [rope[a.cmd]], [rope[el]], [rope[IF ec=1 THEN "doesn't" ELSE "don't"]] ]]; SetBroken[goal, TRUE]; InnerReturnPermission[a]; NoteAchievement[goal]; LOOP}; IF NOT goal.consistencyAsked THEN { [goal.consistent, goal.consistencyReason] _ a.class.CheckConsistency[a, goal]; goal.consistencyAsked _ TRUE; }; IF goal.consistent THEN {a.queuedFailsInvalidation _ TRUE; InnerReturnPermission[a]; NoteAchievement[goal]; LOOP}; IF mandatoryMissing THEN {el: ROPE; ec: CARDINAL; [el, ec] _ EnglishList[missedList]; Warning[IO.PutFR["Can't %g because %g %g exist", [rope[a.cmd]], [rope[el]], [rope[IF ec=1 THEN "doesn't" ELSE "don't"]] ]]; SetBroken[goal, TRUE]; InnerReturnPermission[a]; NoteAchievement[goal]; LOOP}; SELECT fails _ a.fails FROM true => {SetBroken[goal, TRUE]; InnerReturnPermission[a]; NoteAchievement[goal]; LOOP}; false => IF a.timesTried >= 3 THEN {SetBroken[goal, TRUE]; InnerReturnPermission[a]; NoteAchievement[goal]; LOOP}; notComputed => NULL; ENDCASE => ERROR; consistencyReason _ goal.consistencyReason; toExecute _ a; SetCurrency[goal, false]; DO valid: BOOL; [fork: fork, valid: valid, resources: resources] _ ShouldFork[]; IF valid THEN EXIT; Exit[]; WaitQueue[ ! <> UNWIND => Enter[GAEWork] <> ]; Enter[GAEWork]; ENDLOOP; RETURN; }; }; ENDLOOP; }; goal _ NIL; DoIn[GAEWork]; RETURN}; CheckIn: PUBLIC --ENTRY-- PROC [job: Job, goal: Node, producer: Action, process: CedarProcess.Process] = { CIWork: --INTERNAL-- PROC = { IF goal.state # working THEN ERROR; SetState[goal, ready]; InnerReturnPermission[producer]; IF process # NIL THEN [] _ job.processes.Delete[process]; IF halt THEN WHILE readyQueue # NIL DO victim: Node = readyQueue.first; SetState[victim, working]; InnerGiveUp[victim]; ENDLOOP; RETURN}; DoIn[CIWork]; RETURN}; ReturnPermission: --ENTRY-- PROC [a: Action] = { RPWork: --INTERNAL-- PROC = {InnerReturnPermission[a]}; DoIn[RPWork]; RETURN}; InnerReturnPermission: --INTERNAL-- PROC [a: Action] = { a.permissionGranted _ FALSE; IF a.queuedFailsInvalidation THEN {a.fails _ notComputed; a.timesTried _ 0}; a.queuedFailsInvalidation _ FALSE; InnerWakePermissionWaiters[a]; RETURN}; InnerWakePermissionWaiters: --INTERNAL-- PROC [a: Action] = { a _ a; WHILE a.waitingForPermission # NIL DO waiter: Node = a.waitingForPermission.first; a.waitingForPermission _ a.waitingForPermission.rest; IF waiter.state # waiting THEN ERROR; IF waiter.waitCount # 1 THEN ERROR; waiter.waitCount _ 0; SetState[waiter, ready]; ENDLOOP; RETURN}; InnerGetProduced: PUBLIC --INTERNAL-- PROC [n: Node] = { n2: Node; IF n.producer # NIL THEN ERROR; SELECT n.current FROM true, false => ERROR --we shouldn't know that much if we haven't tried to produce yet--; notComputed => NULL; ENDCASE => ERROR; n2 _ TryToProduce[n.given, n.class]; IF n # n2 THEN Warning[IO.PutFR["Disagreement on cannonical name for %g or %g", IO.rope[n.name], IO.rope[n2.name]]]; InnerVolunteerLeaf[n]; RETURN}; FindNode: PUBLIC --ENTRY-- PROC [someName: ROPE, class: NodeClass] RETURNS [node: Node] = { FNWork: --INTERNAL-- PROC = { node _ GetNode[someName, class, FALSE]; IF node # NIL THEN RETURN; node _ TryToProduce[someName, class]; InnerVolunteerLeaf[node]; }; DoIn[FNWork]; }; TryToProduce: --INTERNAL-- PROC [resultName: ROPE, class: NodeClass] RETURNS [sought: Node] = BEGIN PerFinder: --INTERNAL-- PROC [f: Finder] RETURNS [stop: BOOL _ FALSE] ~ { found: BOOLEAN; makes, cmdFrom: NodeList; from: From; cmd: ROPE; class: ActionClass; foundData: REF ANY; CedarProcess.CheckAbort[]; [found, sought, makes, cmdFrom, from, cmd, class, foundData] _ f.finderProc[resultName: resultName, finderData: f.finderData]; IF found THEN { first: BOOLEAN _ TRUE; soughtIn: BOOL _ FALSE; already: Action _ NIL; FOR ml: NodeList _ makes, ml.rest WHILE ml # NIL DO this: Action _ IF ml.first.producer = NIL THEN NIL ELSE ml.first.producer.a; soughtIn _ soughtIn OR (sought = ml.first); IF first THEN {already _ this; first _ FALSE} ELSE {IF this # already THEN Warning[IO.PutFR[ "Action %g doesn't precisely cover command %g (e.g., at %g)", IO.refAny[cmd], IO.rope[IF already # NIL THEN already.cmd ELSE NIL], IO.rope[ml.first.name]]]}; ENDLOOP; IF soughtIn THEN { a: Action; IF already # NIL THEN RETURN [TRUE]; a _ NEW [ActionRep _ [cmd: cmd, class: class, foundData: foundData]]; InnerAddAction[a, makes, cmdFrom, from]; RETURN [TRUE]; } ELSE { Warning[Rope.Cat["Finder (", f.name, ") blew it"]]; sought _ NIL; }; } ELSE sought _ NIL; }; sought _ NIL; EnumerateFinders[PerFinder]; IF sought=NIL THEN sought _ GetNode[resultName, class]; END; actionClasses: RefTab.Ref ~ RefTab.Create[]; InnerAddAction: --INTERNAL-- PROC [a: Action, makes, cmdFrom: NodeList, from: From] = { [] _ actionClasses.Insert[a.class, a.class]; FOR nl: NodeList _ makes, nl.rest WHILE nl # NIL DO n: Node _ nl.first; peh: EdgeRingHead _ emptyHead; IF n.producer # NIL THEN ERROR; IF n.current # notComputed THEN ERROR; [a.makes, peh] _ Link[a, a.makes, n, peh, FALSE]; n.producer _ peh.first; ENDLOOP; InnerAddConsumption[a, cmdFrom, cmd, TRUE]; InnerAddConsumption[a, from.mustHave, data, FALSE]; InnerAddConsumption[a, from.optional, data, TRUE]; }; VolunteerLeaf: --ENTRY-- PROC [n: Node] = { VLWork: --INTERNAL-- PROC = {InnerVolunteerLeaf[n]}; DoIn[VLWork]; }; InnerVolunteerLeaf: --INTERNAL-- PROC [n: Node] = { IF n.producer = NIL THEN n.producer _ NEW [EdgeRep _ [ a: leaf, n: n, aNext: NIL, aPrev: NIL, nNext: NIL, nPrev: NIL, optional: FALSE ]] ELSE IF n.producer.a = NIL THEN ERROR; }; InnerRederive: PUBLIC --INTERNAL-- PROC [a: Action] = { from: From; InnerRemoveConsumption[a, data]; [from, a.cmd] _ a.class.Rederive[a]; InnerAddConsumption[a, from.mustHave, data, FALSE]; InnerAddConsumption[a, from.optional, data, TRUE]; <> }; AddConsumption: --ENTRY-- PROC [a: Action, nl: NodeList, which: ActionDep, optional: BOOL] = { ACWork: --INTERNAL-- PROC = {InnerAddConsumption[a, nl, which, optional]}; DoIn[ACWork]; }; InnerAddConsumption: --INTERNAL-- PROC [a: Action, nl: NodeList, which: ActionDep, optional: BOOL] = { FOR nl _ nl, nl.rest WHILE nl # NIL DO n: Node _ nl.first; SELECT which FROM data => { [a.from[data], n.to[data]] _ Link[a, a.from[data], n, n.to[data], optional]; }; cmd => { [a.from[cmd], n.to[cmd]] _ Link[a, a.from[cmd], n, n.to[cmd], optional]; }; ENDCASE => ERROR; ENDLOOP; nl _ nl; }; Link: --INTERNAL-- PROC [a: Action, ah: EdgeRingHead, n: Node, nh: EdgeRingHead, optional: BOOL] RETURNS [ahNew, nhNew: EdgeRingHead] = { e: Edge _ NEW [EdgeRep _ [ optional: optional, n: n, a: a, aNext: ah.first, aPrev: NIL, nNext: nh.first, nPrev: NIL ]]; ah.first _ nh.first _ e; IF e.aNext # NIL THEN e.aNext.aPrev _ e ELSE ah.last _ e; IF e.nNext # NIL THEN e.nNext.nPrev _ e ELSE nh.last _ e; ahNew _ ah; nhNew _ nh; }; InnerRemoveConsumption: --INTERNAL-- PROC [a: Action, which: ActionDep] = { ah: EdgeRingHead _ a.from[which]; aNext: Edge; FOR e: Edge _ ah.first, aNext WHILE e # NIL DO n: Node _ e.n; IF e.a # a THEN ERROR; aNext _ e.aNext; e.aNext _ e.aPrev _ NIL; UnlinkToFrom[e, which]; e.a _ NIL; e.n _ NIL; ENDLOOP; a.from[which] _ emptyHead; }; UnlinkToFrom: --INTERNAL-- PROC [e: Edge--on n.to=a.from--, which: ActionDep] = { n: Node = e.n; IF e.nNext # NIL THEN e.nNext.nPrev _ e.nPrev ELSE SELECT which FROM cmd => n.to[cmd].last _ e.nPrev; data => n.to[data].last _ e.nPrev; ENDCASE => ERROR; IF e.nPrev # NIL THEN e.nPrev.nNext _ e.nNext ELSE SELECT which FROM cmd => n.to[cmd].first _ e.nNext; data => n.to[data].first _ e.nNext; ENDCASE => ERROR; e.nNext _ e.nPrev _ NIL; }; InnerRemoveProduction: --INTERNAL-- PROC [a: Action] = { ah: EdgeRingHead _ a.makes; aNext: Edge; FOR e: Edge _ ah.first, aNext WHILE e # NIL DO n: Node _ e.n; IF e.a # a THEN ERROR; aNext _ e.aNext; e.aNext _ e.aPrev _ NIL; e.nNext _ e.nNext _ NIL; n.producer _ NIL; e.a _ NIL; e.n _ NIL; ENDLOOP; a.makes _ emptyHead; }; Explain: PUBLIC PROC [ch: Commander.Handle, nodes: RefTable] = { to: IO.STREAM = ch.out; needDecode: BOOL _ FALSE; Work: PROC [data: REF ANY] RETURNS [stop: BOOL _ FALSE] --RedBlackTree.EachNode-- = { n: Node = NARROW[data]; needDecode _ ExplainNode[n, to] OR needDecode; }; StatelessEnumerateRefTable[nodes, Work]; IF needDecode THEN to.PutRope["* => not current;\n! => broken;\nleading ? => doesn't exist;\nleading ?? => create time unknown;\ntrailing ? => optional.\n"]; }; ExplainNode: --ENTRY-- PROC [n: Node, to: IO.STREAM] RETURNS [needDecode: BOOL _ FALSE] = { ShowConsumer: --INTERNAL-- PROC [a: Action, which: ActionDep] = { to.PutF["\n\t\t%g", [rope[a.cmd]]]; }; ShowDeterminer: --INTERNAL-- PROC [n: Node, which: ActionDep, optional: BOOL] = { nc: BOOL = n.current # true; broken: BOOL = n.current=true AND n.broken; created: Time ~ InnerGetCreated[n]; to.PutF["\n%g\t%g\t%g", [rope[IF nc THEN "*" ELSE IF broken THEN "!" ELSE ""]], [rope[SELECT created FROM notExistTime => "?", unknownTime => "??", ENDCASE => ""]], IO.rope[n.name] ]; needDecode _ needDecode OR nc OR broken OR created=notExistTime OR created=unknownTime; RETURN}; ShowInput: --INTERNAL-- PROC [n: Node, which: ActionDep, optional: BOOL] = { nc: BOOL = n.current # true; broken: BOOL = n.current=true AND n.broken; created: Time ~ InnerGetCreated[n]; to.PutF["\n%g\t%g\t%g%g", [rope[IF nc THEN "*" ELSE IF broken THEN "!" ELSE ""]], [rope[SELECT created FROM notExistTime => "?", unknownTime => "??", ENDCASE => ""]], IO.rope[n.name], IO.rope[IF optional THEN "?" ELSE ""] ]; needDecode _ needDecode OR nc OR broken OR created=notExistTime OR created=unknownTime; RETURN}; ShowOutput: PROC [n: Node] = { to.PutF["\n\t\t%g", IO.rope[n.name]]; }; ENWork: --INTERNAL-- PROC = { a: Action _ NIL; to.PutF["%g\n", [rope[n.name]] ]; to.PutF["\tCreated %g.\n", [rope[FmtTime[n.created]]] ]; to.PutF["\t%g\n", [rope[SELECT n.current FROM true => "Is Current", false => "Not Current", notComputed => "Not known if Current", ENDCASE => ERROR]]]; IF n.current=true THEN to.PutF["\t%g.\n", [rope[IF n.broken THEN "Broken" ELSE "Not broken"]] ]; to.PutRope["\tNeeded by {"]; InnerEnumerateConsumers[n, data, ShowConsumer]; to.PutRope["};\n\tdetermines {"]; InnerEnumerateConsumers[n, cmd, ShowConsumer]; to.PutRope["}.\n"]; SELECT TRUE FROM n.producer = NIL => to.PutRope["\tNever tried to determine producer.\n"]; n.producer.a = leaf => to.PutRope["\tNothing knows how to produce it.\n"]; ENDCASE => a _ n.producer.a; SELECT n.modifiability FROM yes => to.PutRope["\tCertainly modifiable.\n"]; no => to.PutRope["\tCertainly NOT modifiable.\n"]; guess => to.PutRope["\tModifiability unspecified.\n"]; uninitialized => to.PutRope["\tModifiability undetermined.\n"]; ENDCASE => ERROR; IF (a = NIL) OR (n.modifiability = no) THEN RETURN; IF NOT n.consistencyAsked THEN to.PutRope["\tConsistency not inquired.\n"] ELSE to.PutF[ "\tCurrently %g consistent with inputs (because %g).\n", [rope[IF n.consistent THEN "is" ELSE "not"]], [rope[n.consistencyReason]] ]; to.PutF["%g\n", [rope[a.cmd]] ]; to.PutRope["\tAccording to {"]; InnerEnumerateSources[a, cmd, ShowDeterminer]; to.PutRope["}\n\tMakes {"]; InnerEnumerateResults[a, ShowOutput]; to.PutRope["}\n\tFrom {"]; InnerEnumerateSources[a, data, ShowInput]; to.PutRope["}.\n"]; to.PutF["\t%g from current determiners.\n", [rope[IF a.derivedFromCurrentDeterminers THEN "Derived" ELSE "Not derived"]] ]; to.PutF["\t%g with current inputs", [rope[SELECT TRUE FROM a.queuedFailsInvalidation OR a.fails=notComputed => "Not tried", a.fails=true => "Fails", a.fails=false => "Succeeds", ENDCASE => ERROR]] ]; IF a.fails=false AND NOT a.queuedFailsInvalidation THEN to.PutF[" (tried %g times with current inputs)", [integer[a.timesTried]] ]; to.PutRope[".\n"]; IF a.reasonWhyLastDone # NIL THEN to.PutF["\tLast executed because %g.\n", [rope[a.reasonWhyLastDone]]]; }; DoIn[ENWork]; }; SuspectNodesChange: PUBLIC PROC [ns: RefTable] ~ { EnterEnumerateNodes[ns, InnerSuspectNodeChange]}; UncurrentNodes: PUBLIC PROC [ns: RefTable] ~ { EnterEnumerateNodes[ns, InnerUncurrentNode]}; UncurrentProducers: PUBLIC PROC [ns: RefTable] ~ { EnterEnumerateNodes[ns, InnerUncurrentProducer]}; EnterEnumerateNodes: --ENTRY-- PROC [ns: RefTable, Consume: PROC [Node]] ~ { EENWork: --INTERNAL-- PROC ~ {EnumerateNodes[ns, Consume]}; DoIn[EENWork]; RETURN}; SuspectNodeChange: PUBLIC --ENTRY-- PROC [n: Node] = { SNCWork: --INTERNAL-- PROC = {InnerSuspectNodeChange[n]}; DoIn[SNCWork]; }; InnerSuspectNodeChange: PUBLIC --INTERNAL-- PROC [n: Node] = { lastCreated: Time = n.created; lastLength: INT ~ n.length; [n.created, n.length] _ n.class.GetInfo[n]; IF n.created#lastCreated OR n.length#lastLength THEN InnerNoteContentChange[n]; }; reasonNotAsked: ROPE = "haven't yet asked if consistent"; InnerNoteContentChange: --INTERNAL-- PROC [n: Node] = { InnerUnaskConsistency: --INTERNAL-- PROC [n: Node] = { n.consistencyAsked _ FALSE; n.consistencyReason _ reasonNotAsked; }; PerDirectConsumer: --INTERNAL-- PROC [a: Action, which: ActionDep] = { SELECT which FROM cmd => { a.derivedFromCurrentDeterminers _ FALSE; }; data => { InnerUnknowFails[a]; InnerEnumerateResults[a, InnerUnaskConsistency]; }; ENDCASE => ERROR; }; InnerUnaskConsistency[n]; InnerEnumerateConsumers[n, data, PerDirectConsumer]; InnerEnumerateConsumers[n, cmd, PerDirectConsumer]; InnerUncurrentNode[n]; n _ n; }; InnerUnknowFails: PUBLIC --INTERNAL-- PROC [a: Action] = { SELECT a.permissionGranted FROM FALSE => {a.fails _ notComputed; a.timesTried _ 0}; TRUE => a.queuedFailsInvalidation _ TRUE; ENDCASE => ERROR; }; UncurrentNode: PUBLIC --ENTRY-- PROC [n: Node] = { UNWork: --INTERNAL-- PROC = {InnerUncurrentNode[n]}; DoIn[UNWork]; }; InnerUncurrentNode: --INTERNAL-- PROC [n: Node] = { PerConsumer: --INTERNAL-- PROC [a: Action, which: ActionDep] = { InnerEnumerateResults[a, InnerUncurrentNode]; }; IF n.current = notComputed THEN RETURN; SetCurrency[n, notComputed]; InnerEnumerateConsumers[n, data, PerConsumer]; InnerEnumerateConsumers[n, cmd, PerConsumer]; n _ n; }; UncurrentProducer: PUBLIC --ENTRY-- PROC [n: Node] = { UPWork: --INTERNAL-- PROC = {InnerUncurrentProducer[n]}; DoIn[UPWork]; }; InnerUncurrentProducer: --INTERNAL-- PROC [n: Node] = { IF n.producer = NIL OR n.producer.a = leaf THEN InnerUncurrentNode[n] ELSE { a: Action = n.producer.a; InnerUnknowFails[a]; InnerEnumerateResults[a, InnerUncurrentNode]; }; }; EnumerateConsumers: PUBLIC --ENTRY-- PROC [n: Node, which: ActionDep, to: PROC [Action, ActionDep]] = { ECWork: --INTERNAL-- PROC = {InnerEnumerateConsumers[n, which, to]}; DoIn[ECWork]; }; InnerEnumerateConsumers: --INTERNAL-- PROC [n: Node, which: ActionDep, to: PROC [Action, ActionDep]] = { x: BOOL _ FALSE; x _ x; FOR e: Edge _ n.to[which].first, e.nNext WHILE e # NIL DO to[e.a, which]; ENDLOOP; x _ x; }; EnumerateResults: PUBLIC --ENTRY-- PROC [a: Action, to: PROC [Node]] = { ERWork: --INTERNAL-- PROC = {InnerEnumerateResults[a, to]}; DoIn[ERWork]; }; InnerEnumerateResults: --INTERNAL-- PROC [a: Action, to: PROC [Node]] = { x: BOOL _ FALSE; x _ x; FOR e: Edge _ a.makes.first, e.aNext WHILE e # NIL DO to[e.n]; ENDLOOP; x _ x; }; EnumerateSources: PUBLIC --ENTRY-- PROC [a: Action, which: ActionDep, to: PROC [n: Node, which: ActionDep, optional: BOOL]] = { ESWork: --INTERNAL-- PROC = {InnerEnumerateSources[a, which, to]}; DoIn[ESWork]; }; InnerEnumerateSources: PUBLIC --INTERNAL-- PROC [a: Action, which: ActionDep, to: PROC [n: Node, which: ActionDep, optional: BOOL]] = { which _ which; FOR e: Edge _ a.from[which].first, e.aNext WHILE e # NIL DO IF e.a # a THEN ERROR; to[e.n, which, e.optional]; ENDLOOP; which _ which; }; GetProducer: PUBLIC --ENTRY-- PROC [n: Node] RETURNS [producer: Action _ NIL] = { GPWork: --INTERNAL-- PROC = {IF n.producer#NIL AND n.producer.a#leaf THEN producer _ n.producer.a}; DoIn[GPWork]; RETURN}; <> <> <> <> <> <> <> <> DestroyGraph: PUBLIC --ENTRY-- PROC = { DGWork: --INTERNAL-- PROC = { DestroyNode: --INTERNAL-- PROC [n: Node] = { IF n.producer # NIL AND n.producer.a # leaf THEN { a: Action = n.producer.a; a.class _ NIL; a.foundData _ NIL; InnerRemoveConsumption[a, cmd]; InnerRemoveConsumption[a, data]; InnerRemoveProduction[a]; }; }; ClearClass: PROC [key, val: REF ANY] RETURNS [quit: BOOL _ FALSE] ~ { class: ActionClass ~ NARROW[key]; IF class.ClearCaches#NIL THEN class.ClearCaches[class]; RETURN}; IF workCount > 0 THEN Warning["Hope no other processes in MakeDo..."]; EnumerateAndDestroy[to: DestroyNode, andDestroy: TRUE]; IF actionClasses.Pairs[ClearClass] THEN ERROR; readyQueue _ NIL; workCount _ 0; waitQueue.Erase[]; EmptyCmdUtils[]; halt _ FALSE; }; DoIn[DGWork]; }; ForAll: PUBLIC --ENTRY-- PROC [suspectChange, uncurrent: BOOL] = { FAWork: --INTERNAL-- PROC = { PerNode: INTERNAL PROC [n: Node] = { IF suspectChange THEN InnerSuspectNodeChange[n]; IF uncurrent THEN InnerUncurrentProducer[n]; }; EnumerateAndDestroy[to: PerNode, andDestroy: FALSE]; }; DoIn[FAWork]; }; RetryToProduce: PUBLIC --ENTRY-- PROC [n: Node] = { RPWork: --INTERNAL-- PROC = { n2: Node; IF NOT InnerUnleafen[n] THEN RETURN; n2 _ TryToProduce[n.given, n.class]; IF n # n2 THEN Warning[IO.PutFR["Disagreement on cannonical name for %g or %g", IO.rope[n.name], IO.rope[n2.name]]]; InnerVolunteerLeaf[n]; }; DoIn[RPWork]; }; InnerUnleafen: --INTERNAL-- PROC [n: Node] RETURNS [wasLeaf: BOOL] ~ { IF n.producer = NIL THEN RETURN [TRUE]; IF n.producer.a # leaf THEN RETURN [FALSE]; {e: Edge ~ n.producer; IF e.aNext # NIL OR e.aPrev # NIL THEN ERROR; IF e.nNext # NIL OR e.nPrev # NIL THEN ERROR; n.producer.a _ NIL; n.producer _ NIL; InnerUncurrentNode[n]; RETURN [TRUE]}}; FindCycles: --INTERNAL-- PROC = { out: IO.STREAM = GetCommanderHandle[].out; explored: RefTable _ MakeRefTable[]; inStack: RefTable _ MakeRefTable[]; stack: NodeList _ NIL; cycleCount: INTEGER _ 0; PerElement: PROC [n: Node] = { out.PutF["\t%g\n", [rope[n.name]] ]; InnerWakeWaiters[n]; IF n.producer#NIL AND n.producer.a#leaf THEN InnerWakePermissionWaiters[n.producer.a]; }; NoteNode: --INTERNAL-- PROC [ra: REF ANY] RETURNS [BOOL] = { n: Node ~ NARROW[ra]; IF explored.Fetch[n].found THEN RETURN [FALSE]; IF inStack.Fetch[n].found THEN { cycleCount _ cycleCount + 1; out.PutRope["Found cycle:\n"]; PerElement[n]; FOR nl: NodeList _ stack, nl.rest WHILE nl.first # n DO PerElement[nl.first]; ENDLOOP; out.PutRope["\n"]; RETURN [FALSE]; }; IF NOT inStack.Insert[n, $T] THEN ERROR; stack _ CONS[n, stack]; FOR wl: NodeList _ n.waitingOnCurrent, wl.rest WHILE wl # NIL DO [] _ NoteNode[wl.first]; ENDLOOP; IF n.producer # NIL AND n.producer.a # leaf THEN { a: Action = n.producer.a; FOR wl: NodeList _ a.waitingForPermission, wl.rest WHILE wl # NIL DO [] _ NoteNode[wl.first]; ENDLOOP; }; stack _ stack.rest; IF NOT DeleteFromRefTable[n, inStack] THEN ERROR; IF NOT explored.Insert[n, $T] THEN ERROR; RETURN [FALSE]}; halt _ TRUE; Warning["MakeDo deadlocked... hang on while I look for dependency cycles... "]; StatelessEnumerateRefTable[waitQueue, NoteNode]; Warning[IO.PutFR["... found %g cycle(s) ... halting", [integer[cycleCount]] ]]; }; EnsureWDir: PROC [wDir: ROPE, parent: Commander.Handle] = { IF NOT CommandTool.CurrentWorkingDirectory[].Equal[wDir, FALSE] THEN { [] _ CommandTool.DoCommand[Rope.Cat["CD ", wDir], parent]; IF NOT CommandTool.CurrentWorkingDirectory[].Equal[wDir, FALSE] THEN ERROR; }; }; Start: PROC = { TRUSTED { mcp: Process.ConditionPointer = @monitorChange; qcp: Process.ConditionPointer = @queueChange; Process.InitializeCondition[mcp, Process.SecondsToTicks[entryTimeout]]; Process.EnableAborts[mcp]; Process.InitializeCondition[qcp, Process.SecondsToTicks[queueTimeout]]; Process.EnableAborts[qcp]; }; UserProfile.CallWhenProfileChanges[TrackProfile]; }; Start[]; END.