SlackProcessImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last edited by Pier on April 3, 1991 6:50 pm PST
Bier, April 3, 1991 5:14 pm PST
DIRECTORY
Process, Rope, SlackProcess, SlackProcessConcreteTypes, SlackProcessExtras, SlackProcessTypes, ViewerAbort, ViewerClasses;
SlackProcessImpl: CEDAR MONITOR LOCKS handle USING handle: SlackHandle
IMPORTS Process, ViewerAbort
EXPORTS SlackProcess, SlackProcessExtras, SlackProcessTypes = BEGIN
SlackHandle: TYPE = REF SlackHandleObj;
SlackHandleObj: PUBLIC TYPE = SlackProcessConcreteTypes.SlackHandleObj;
QueueEntryGenerator: TYPE = REF QueueEntryGeneratorObj;
QueueEntryGeneratorObj: PUBLIC TYPE = SlackProcessConcreteTypes.QueueEntryGeneratorObj;
ClientDatas: TYPE = REF ClientDatasData;
ClientDatasData: TYPE = SlackProcessConcreteTypes.ClientDatasData;
OptimizeHints: TYPE = REF OptimizeHintsData;
OptimizeHintsData: TYPE = SlackProcessConcreteTypes.OptimizeHintsData;
Actions: TYPE = REF ActionsData;
ActionsData: TYPE = SlackProcessConcreteTypes.ActionsData;
ActionProcs: TYPE = REF ActionProcsData;
ActionProcsData: TYPE = SlackProcessConcreteTypes.ActionProcsData;
Log: TYPE = REF LogData;
LogData: TYPE = SlackProcessConcreteTypes.LogData;
Queue: TYPE = REF QueueData;
QueueData: TYPE = SlackProcessConcreteTypes.QueueData;
Abort: TYPE = REF AbortData;
AbortData: TYPE = SlackProcessConcreteTypes.AbortData;
Bashed: TYPE = REF BashedData;
BashedData: TYPE = SlackProcessConcreteTypes.BashedData;
Events: TYPE = REF EventsData;
EventsData: TYPE = SlackProcessConcreteTypes.EventsData;
Received: TYPE = REF ReceivedData;
ReceivedData: TYPE = SlackProcessConcreteTypes.ReceivedData;
QueueEvents: TYPE = REF QueueEventsData;
QueueEventsData: TYPE = SlackProcessConcreteTypes.QueueEventsData;
ActionProc: TYPE = SlackProcess.ActionProc;
AbortProc: TYPE = SlackProcess.AbortProc;
LoggingProc: TYPE = SlackProcess.LoggingProc;
OptimizeProc: TYPE = SlackProcess.OptimizeProc;
Viewer: TYPE = ViewerClasses.Viewer;
Problem: PUBLIC ERROR[msg: Rope.ROPE] = CODE;
NoOptimization: OptimizeProc = { -- do all of the actions on the queue
skipActions ← 0;
};
Create: PUBLIC PROC [queueSize: NAT ← 50, logSize: NAT ← 50, optimizeProc: OptimizeProc, loggingProc: LoggingProc ← NIL, abortProc: AbortProc ← NIL, abortData: REF ANYNIL, abortViewer: ViewerClasses.Viewer ← NIL, priority: Process.Priority ← Process.priorityNormal] RETURNS [handle: SlackHandle] = {
handle ← NEW[SlackHandleObj ← [slackProcess: NIL,
queue: NEW[QueueData ← [] ],
qeGen: NEW[QueueEntryGeneratorObj],
log: NEW[LogData ← [] ],
abort: NEW[AbortData ← [] ],
priority: priority
]];
handle.queue.clientDatas ← NEW[ClientDatasData[queueSize]];
handle.queue.optimizeHints ← NEW[OptimizeHintsData[queueSize]];
handle.queue.actions ← NEW[ActionsData[queueSize]];
handle.queue.actionProcs ← NEW[ActionProcsData[queueSize]];
handle.queue.events ← NEW[QueueEventsData[queueSize]];
handle.priority ← priority;
handle.queue.size ← queueSize;
IF optimizeProc = NIL THEN handle.optimizeProc ← NoOptimization
ELSE handle.optimizeProc ← optimizeProc;
handle.log.received ← NEW[ReceivedData[logSize]];
handle.log.bashed ← NEW[BashedData[logSize]];
handle.log.events ← NEW[EventsData[logSize]];
handle.log.size ← logSize;
handle.log.logger ← loggingProc;
handle.abort^ ← [enabled: FALSE, viewer: abortViewer, proc: abortProc, data: abortData];
};
ChangePriority: PUBLIC ENTRY PROC [handle: SlackHandle, priority: Process.Priority] = {
handle.priority ← priority;
};
QueueOrTimeout: PUBLIC ENTRY PROC [handle: SlackHandle, callBack: ActionProc, inputAction: REF, clientData: REF, optimizeHint: REF, timeoutTicks: Process.Ticks, priority: Process.Priority ← LAST[CARD32]] RETURNS [success: BOOLTRUE] = {
ENABLE UNWIND => NULL;
queue: Queue ← handle.queue;
qIndex: NAT ← 0;
IF timeoutTicks = LAST[CARD32] THEN {
WHILE (queue.tail+1) MOD queue.size = queue.head DO WAIT queue.notFull ENDLOOP;
}
ELSE {
TRUSTED { Process.SetTimeout[@queue.timedNotFull, timeoutTicks] };
WHILE (queue.tail+1) MOD queue.size = queue.head DO WAIT queue.timedNotFull ENDLOOP;
};
IF (queue.tail+1) MOD queue.size = queue.head THEN RETURN[FALSE]; -- timed out
qIndex ← queue.tail;
queue.clientDatas[qIndex] ← clientData;
queue.optimizeHints[qIndex] ← optimizeHint;
queue.actions[qIndex] ← inputAction;
queue.actionProcs[qIndex] ← callBack;
queue.events[qIndex].priority ← priority;
queue.events[qIndex].readOnly ← FALSE;
queue.tail ← (qIndex + 1) MOD queue.size;
LogReceived[log: handle.log, inputAction: inputAction, bash: FALSE];
IF handle.slackProcess = NIL AND (NOT handle.pauseRequest) THEN Process.Detach[handle.slackProcess ← FORK Actor[handle]];
};
QueueAction: PUBLIC ENTRY PROC [handle: SlackHandle, callBack: ActionProc, inputAction: REF, clientData: REF, optimizeHint: REF, priority: Process.Priority ← LAST[CARD32]] = {
Add an action to the slack processes queue.
ENABLE UNWIND => NULL;
queue: Queue ← handle.queue;
qIndex: NAT ← 0;
WHILE (queue.tail+1) MOD queue.size = queue.head DO WAIT queue.notFull ENDLOOP;
qIndex ← queue.tail;
queue.clientDatas[qIndex] ← clientData;
queue.optimizeHints[qIndex] ← optimizeHint;
queue.actions[qIndex] ← inputAction;
queue.actionProcs[qIndex] ← callBack;
queue.events[qIndex].priority ← priority;
queue.events[qIndex].readOnly ← FALSE;
queue.tail ← (qIndex + 1) MOD queue.size;
LogReceived[log: handle.log, inputAction: inputAction, bash: FALSE];
IF handle.slackProcess = NIL AND (NOT handle.pauseRequest) THEN Process.Detach[handle.slackProcess ← FORK Actor[handle]];
};
QueueAtHead: PUBLIC ENTRY PROC [handle: SlackHandle, callBack: ActionProc, inputAction: REF, clientData: REF, optimizeHint: REF, priority: Process.Priority ← LAST[CARD32]] = {
Add an action to the slack processes queue at the head instead of the tail.
ENABLE UNWIND => NULL;
queue: Queue ← handle.queue;
qIndex: NAT ← 0;
WHILE (queue.tail+1) MOD queue.size = queue.head DO WAIT queue.notFull ENDLOOP;
qIndex ← (queue.head + queue.size - 1) MOD queue.size;
queue.clientDatas[qIndex] ← clientData;
queue.optimizeHints[qIndex] ← optimizeHint;
queue.actions[qIndex] ← inputAction;
queue.actionProcs[qIndex] ← callBack;
queue.events[qIndex].priority ← priority;
queue.events[qIndex].readOnly ← FALSE;
queue.tail ← (qIndex + 1) MOD queue.size;
LogReceived[log: handle.log, inputAction: inputAction, bash: FALSE];
IF handle.slackProcess = NIL AND (NOT handle.pauseRequest) THEN Process.Detach[handle.slackProcess ← FORK Actor[handle]];
};
QueueReadOnly: PUBLIC ENTRY PROC [handle: SlackHandle, callBack: ActionProc, inputAction: REF, clientData: REF, optimizeHint: REF, priority: Process.Priority ← LAST[CARD32]] = {
Like SlackProcess.QueueAction but marks this action as one that will only read data structures, not modify them.
ENABLE UNWIND => NULL;
queue: Queue ← handle.queue;
qIndex: NAT ← 0;
WHILE (queue.tail+1) MOD queue.size = queue.head DO WAIT queue.notFull ENDLOOP;
qIndex ← queue.tail;
queue.clientDatas[qIndex] ← clientData;
queue.optimizeHints[qIndex] ← optimizeHint;
queue.actions[qIndex] ← inputAction;
queue.actionProcs[qIndex] ← callBack;
queue.events[qIndex].priority ← priority;
queue.events[qIndex].readOnly ← TRUE;
queue.tail ← (qIndex + 1) MOD queue.size;
LogReceived[log: handle.log, inputAction: inputAction, bash: FALSE];
IF handle.slackProcess = NIL AND (NOT handle.pauseRequest) THEN Process.Detach[handle.slackProcess ← FORK Actor[handle]];
};
Pause: PUBLIC ENTRY PROC [handle: SlackHandle] = {
handle.pauseRequest ← TRUE;
};
Continue: PUBLIC ENTRY PROC [handle: SlackHandle] = {
handle.pauseRequest ← FALSE;
IF NOT EmptyQ[handle.queue] AND handle.slackProcess = NIL THEN Process.Detach[handle.slackProcess ← FORK Actor[handle]];
};
PauseRequested: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [BOOL] = {
Returns TRUE if Pause has been called on this handle since the last Create or Continue was performed on this handle.
RETURN[handle.pauseRequest];
};
ProcessIsBusy: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [BOOL] = {
RETURNS TRUE if the dequeuing process associated with handle is currently inactive.
RETURN[handle.slackProcess # NIL];
};
WaitUntilPaused: PUBLIC PROC [handle: SlackHandle, timeoutTicks: Process.Ticks ← LAST[CARD32]] RETURNS [success: BOOLTRUE] = {
Causes the calling process to wait on a condition variable until the dequeuing process has stopped processing actions.
IF NOT PauseRequested[handle] THEN Pause[handle];
IF timeoutTicks = LAST[CARD32] THEN {
WaitForPause[handle];
success ← TRUE;
}
ELSE {
success ← WaitForPauseOrTimeout[handle, timeoutTicks];
};
};
WaitUntilPausedOrIdle: PUBLIC PROC [handle: SlackHandle, timeoutTicks: Process.Ticks ← LAST[CARD32]] RETURNS [state: SlackProcessExtras.ProcessState] = {
IF timeoutTicks = LAST[CARD32] THEN {
state ← WaitForPauseOrIdle[handle];
}
ELSE {
state ← WaitForPauseOrIdleOrTimeout[handle, timeoutTicks];
};
};
WaitForPauseOrTimeout: ENTRY PROC [handle: SlackHandle, timeoutTicks: Process.Ticks] RETURNS [success: BOOL] = {
ENABLE UNWIND => NULL;
TRUSTED { Process.SetTimeout[@handle.timedPaused, timeoutTicks] };
WAIT handle.timedPaused;
RETURN[handle.slackProcess = NIL AND handle.pauseRequest]; -- not quite right, since the process going idle will cause this routine to fail, even if the timeout hasn't occurred.
};
WaitForPauseOrIdleOrTimeout: ENTRY PROC [handle: SlackHandle, timeoutTicks: Process.Ticks] RETURNS [state: SlackProcessExtras.ProcessState ← timeout] = {
ENABLE UNWIND => NULL;
TRUSTED { Process.SetTimeout[@handle.timedPaused, timeoutTicks] };
WAIT handle.timedPaused;
IF handle.slackProcess # NIL THEN RETURN[timeout]
ELSE {
RETURN[IF handle.pauseRequest THEN paused ELSE idle];
};
};
WaitForPause: ENTRY PROC [handle: SlackHandle] = {
ENABLE UNWIND => NULL;
UNTIL handle.slackProcess = NIL AND handle.pauseRequest DO WAIT handle.paused ENDLOOP;
};
WaitForPauseOrIdle: ENTRY PROC [handle: SlackHandle] RETURNS [state: SlackProcessExtras.ProcessState] = {
ENABLE UNWIND => NULL;
UNTIL handle.slackProcess = NIL DO WAIT handle.paused ENDLOOP;
state ← IF handle.pauseRequest THEN paused ELSE idle;
};
AllQueuedAreReadOnly: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [BOOL] = {
Returns TRUE if all currently queued actions (including the currently active action, if any) were queued using QueueReadOnly.
ENABLE UNWIND => NULL;
queue: Queue ← handle.queue;
FOR i: NAT ← queue.head, (i+1) MOD queue.size UNTIL i = queue.tail DO
IF NOT queue.events[i].readOnly THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
Restart: PUBLIC ENTRY PROC [handle: SlackHandle] = TRUSTED {
Restarting a SlackProcess that has been ABORTed (e.g. from an ERROR window).
Sometimes the Painter process is aborted because of a bug in one of the dispatched procedures. This procedure allows the user to get things going again.
ENABLE UNWIND => NULL;
Process.Detach[handle.slackProcess ← FORK Actor[handle] ];
};
QueueIsBusy: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [BOOL] = {
RETURN[(NOT EmptyQ[handle.queue]) OR handle.slackProcess # NIL];
};
FlushQueue: PUBLIC ENTRY PROC [handle: SlackHandle] = {
ENABLE UNWIND => NULL;
FlushQueueInternal[handle];
};
FlushQueueInternal: INTERNAL PROC [handle: SlackHandle] = {
Removes all unprocessed actions from the slack queue. CAUTION: If the caller is counting on a certain set of actions to be done atomically, this call could destroy its invariants.
queueSize: NAT ← handle.queue.size;
FOR i: NAT IN [0..queueSize-1] DO
handle.queue.clientDatas[i] ← NIL;
handle.queue.optimizeHints[i] ← NIL;
handle.queue.actions[i] ← NIL;
handle.queue.actionProcs[i] ← NIL;
handle.queue.priorities[i] ← Process.priorityNormal; -- not really necessary
ENDLOOP;
handle.queue.head ← 0;
handle.queue.tail ← 0;
BROADCAST handle.queue.notFull;
BROADCAST handle.queue.timedNotFull;
};
Utilities
Actor: PROC [handle: SlackHandle] = { -- runs in autonomous process(es) to service queue(s)
IF handle.abort.enabled THEN {
ActorWithAbort: PROC = {
DoActorsJob[handle];
};
ViewerAbort.CallWithAbortAndCallback[handle.abort.viewer, ActorWithAbort, CallMe, handle];
}
ELSE DoActorsJob[handle];
};
CallMe: PROC [data: REF] = { -- IMPORTANT: called at high priority.
Part of the abort mechanism (currently broken in PCedar)
handle: SlackHandle ← NARROW[data];
NillAndKillIt[handle];
};
NillAndKillIt: PRIVATE ENTRY PROC [handle: SlackHandle] = {
Part of the abort mechanism (currently broken in PCedar)
ENABLE UNWIND => NULL;
handle.slackProcess ← NIL;
FlushQueueInternal[handle]; -- must be in an ENTRY proc
IF handle.abort.proc#NIL THEN handle.abort.proc[handle.abort.data]; -- notify the user that abort happened. Hold the lock.
};
DoActorsJob: PROC [handle: SlackHandle] = { -- autonomous process(es) to service queue(s)
ENABLE UNWIND => NULL;
inputAction: REF;
clientData: REF ANY;
actionProc: ActionProc;
priority: Process.Priority;
[actionProc, inputAction, clientData, priority] ← NextAction[handle];
WHILE inputAction # NIL DO
Process.SetPriority[priority];
actionProc[clientData, inputAction]; -- actually do the client's action
[actionProc, inputAction, clientData, priority] ← NextAction[handle];
ENDLOOP;
};
EmptyQ: INTERNAL PROC [queue: Queue] RETURNS [BOOL] = {
RETURN[queue.tail = queue.head];
};
NextAction: ENTRY PROC [handle: SlackHandle] RETURNS [actionProc: ActionProc, inputAction: REF, clientData: REF, priority: Process.Priority] = {
ENABLE UNWIND => NULL;
queue: Queue ← handle.queue;
success: BOOLTRUE;
errorMsg: Rope.ROPE;
skipActions: NAT;
qeGen: QueueEntryGenerator;
qeGen ← PrepareGenerator[handle];
skipActions ← handle.optimizeProc[qeGen, qeGen.actionsInQueue
! Problem => {success ← FALSE; errorMsg ← msg; CONTINUE}];
IF NOT success THEN RETURN WITH ERROR Problem[msg: errorMsg];
[actionProc, inputAction, clientData, priority] ← DeQueueAction[handle, skipActions];
IF inputAction = NIL THEN {
handle.slackProcess ← NIL; -- no mouse action. Let process die. Caller will terminate.
BROADCAST handle.paused;
BROADCAST handle.timedPaused;
};
priority ← IF priority = LAST[CARD32] THEN handle.priority ELSE priority;
BROADCAST queue.notFull;
BROADCAST queue.timedNotFull;
};
InspectQueue: PUBLIC ENTRY PROC [handle: SlackHandle, inspectProc: SlackProcessExtras.InspectProc] = {
qeGen: QueueEntryGenerator ← PrepareGenerator[handle];
inspectProc[qeGen, qeGen.actionsInQueue];
};
QueueSize: INTERNAL PROC [queue: Queue] RETURNS [count: NAT] = {
count ← (queue.tail - queue.head + queue.size) MOD queue.size;
};
PrepareGenerator: INTERNAL PROC [handle: SlackHandle] RETURNS [qeGen: QueueEntryGenerator] = {
qeGen ← handle.qeGen;
qeGen.queue ← handle.queue;
qeGen.actionsInQueue ← QueueSize[handle.queue]
};
GetQueueEntry: PUBLIC PROC [qeGen: QueueEntryGenerator, index: NAT] RETURNS [clientData: REF, optimizeHint: REF, inputAction: REF, priority: Process.Priority] = {
queue: Queue ← qeGen.queue;
head: NAT ← queue.head;
realIndex: NAT;
IF index + 1 > qeGen.actionsInQueue THEN ERROR Problem[msg: "GetQueueEntry called with index too high"];
realIndex ← (head + index) MOD queue.size;
clientData ← queue.clientDatas[realIndex];
optimizeHint ← queue.optimizeHints[realIndex];
inputAction ← queue.actions[realIndex];
priority ← queue.events[realIndex].priority;
};
DeQueueAction: INTERNAL PROC [handle: SlackHandle, skipActions: NAT] RETURNS [actionProc: ActionProc, inputAction: REF, clientData: REF, priority: Process.Priority] = {
ENABLE UNWIND => NULL;
queue: Queue ← handle.queue;
queueSize, where: NAT;
IF handle.pauseRequest THEN RETURN[NIL, NIL, NIL, 0]; -- don't do anything more for now
queueSize ← QueueSize[handle.queue];
IF skipActions > queueSize THEN ERROR;
IF skipActions = queueSize THEN RETURN[NIL, NIL, NIL, 0]; -- there is nothing left to do
queue.head ← (queue.head + skipActions) MOD queue.size;
where ← queue.head;
actionProc ← queue.actionProcs[where];
inputAction ← queue.actions[where];
clientData ← queue.clientDatas[where];
priority ← queue.events[where].priority;
queue.head ← (queue.head + 1) MOD queue.size;
LogActed[handle.log, inputAction, clientData];
};
SlackProcess keeps a ring buffer of recent input actions (called the log) for debugging purposes.
LogReceived: INTERNAL PROC [log: Log, inputAction: REF, bash: BOOL] = {
where: NAT ← log.head;
log.received[where] ← TRUE;
log.bashed[where] ← bash;
log.events[where] ← inputAction;
log.head ← (log.head + 1) MOD log.size;
};
LogActed: INTERNAL PROC [log: Log, inputAction: REF, clientData: REF] = {
where: NAT ← log.head;
log.received[where] ← FALSE;
log.bashed[where] ← FALSE;
log.events[where] ← inputAction;
log.head ← (log.head + 1) MOD log.size;
IF log.loggerEnabled THEN log.logger[clientData, inputAction];
};
Creating Session Logs
EnableSessionLogging: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [alreadyEnabled: BOOLFALSE] = {
ENABLE UNWIND => NULL;
IF handle.log.loggerEnabled THEN alreadyEnabled ← TRUE;
handle.log.loggerEnabled ← TRUE;
};
DisableSessionLogging: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [alreadyDisabled: BOOLFALSE] = {
ENABLE UNWIND => NULL;
IF NOT handle.log.loggerEnabled THEN alreadyDisabled ← TRUE;
handle.log.loggerEnabled ← FALSE;
};
RegisterLogger: PUBLIC ENTRY PROC [handle: SlackHandle, loggingProc: LoggingProc] RETURNS [alreadyRegistered: BOOLFALSE] = {
If you didn't register a loggingProc when you called Create, this gives you another chance.
ENABLE UNWIND => NULL;
IF handle.log.logger#NIL THEN alreadyRegistered ← TRUE;
handle.log.logger ← loggingProc;
};
The registered abortProc will be called (if aborts are enabled) with abortData as argument when the slack process is aborted.
EnableAborts: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [alreadyEnabled: BOOLFALSE] = {
ENABLE UNWIND => NULL;
IF handle.abort.enabled THEN alreadyEnabled ← TRUE;
handle.abort.enabled ← TRUE;
};
DisableAborts: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [alreadyDisabled: BOOLFALSE] = {
ENABLE UNWIND => NULL;
IF NOT handle.abort.enabled THEN alreadyDisabled ← TRUE;
handle.abort.enabled ← FALSE;
};
RegisterAbortProc: PUBLIC ENTRY PROC [handle: SlackHandle, abortProc: AbortProc ← NIL, abortData: REF ANYNIL, abortViewer: ViewerClasses.Viewer ← NIL] RETURNS [alreadyRegistered: BOOLFALSE] = {
The registered abortProc will be called (if aborts are enabled) with abortData as argument when the slack process is aborted. If you didn't register an abortProc when you called Create, this gives you another chance.
ENABLE UNWIND => NULL;
IF handle.abort.proc#NIL THEN alreadyRegistered ← TRUE;
handle.abort.proc ← abortProc;
handle.abort.data ← abortData;
handle.abort.viewer ← abortViewer;
};
END.