SlackProcessImpl.mesa
Copyright Ó 1986, 1992 by Xerox Corporation. All rights reserved.
Last edited by Pier on April 3, 1991 6:50 pm PST
Bier, June 10, 1993 12:06 pm PDT
Michael Plass, March 25, 1992 11:21 am PST
Christian Jacobi, October 2, 1992 11:51 am PDT
DIRECTORY
Process, Rope, SlackProcess, SlackProcessConcreteTypes, SlackProcessTypes, ViewerClasses;
SlackProcessImpl:
CEDAR
MONITOR
LOCKS handle
USING handle: SlackHandle
IMPORTS Process
EXPORTS SlackProcess, 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
ANY ¬
NIL, 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:
BOOL ¬
TRUE] = {
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:
BOOL ¬
TRUE] = {
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: SlackProcess.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: SlackProcess.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: SlackProcess.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)
ChJ: Commented out ViewerAbort; It was stubbed out and did forbid viewer less usage. October 2, 1992
IF handle.abort.enabled THEN {
ActorWithAbort: PROC = {
DoActorsJob[handle];
};
ViewerAbort.CallWithAbortAndCallback[handle.abort.viewer, ActorWithAbort, CallMe, handle];
}
ELSE DoActorsJob[handle];
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, desiredPriority: Process.Priority;
priority ¬ Process.GetPriority[];
[actionProc, inputAction, clientData, desiredPriority] ¬ NextAction[handle];
WHILE inputAction #
NIL
DO
IF priority # desiredPriority
THEN {
Process.SetPriority[desiredPriority];
priority ¬ desiredPriority;
};
actionProc[clientData, inputAction]; -- actually do the client's action
[actionProc, inputAction, clientData, desiredPriority] ¬ 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: BOOL ¬ TRUE;
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: SlackProcess.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:
BOOL ¬
FALSE] = {
ENABLE UNWIND => NULL;
IF handle.log.loggerEnabled THEN alreadyEnabled ¬ TRUE;
handle.log.loggerEnabled ¬ TRUE;
};
DisableSessionLogging:
PUBLIC ENTRY
PROC [handle: SlackHandle]
RETURNS [alreadyDisabled:
BOOL ¬
FALSE] = {
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:
BOOL ¬
FALSE] = {
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:
BOOL ¬
FALSE] = {
ENABLE UNWIND => NULL;
IF handle.abort.enabled THEN alreadyEnabled ¬ TRUE;
handle.abort.enabled ¬ TRUE;
};
DisableAborts:
PUBLIC ENTRY PROC [handle: SlackHandle]
RETURNS [alreadyDisabled:
BOOL ¬
FALSE] = {
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
ANY ¬
NIL, abortViewer: ViewerClasses.Viewer ¬
NIL]
RETURNS [alreadyRegistered:
BOOL ¬
FALSE] = {
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.