{Begin SubSec The Interlisp-D Process Mechanism} {Title The Interlisp-D Process Mechanism} {Text {Begin Note} Revised: July 25, 1983 translated into IM format: Thu, 21 Jul 83 15:47 PDT by Sannella {End Note} {index Process Mechanism} The Interlisp-D Process mechanism provides an environment in which multiple Lisp processes can run in parallel. Each executes in its own stack space, but all share a global adress space. The current process implementation is cooperative; i.e., process switches happen voluntarily, either when the process in control has nothing to do or when it is in a convenient place to pause. There is no preemption or guaranteed service, so you cannot run something demanding (e.g., Chat) at the same time as something that runs for long periods without yielding control. Keyboard input and network operations block with great frequency, so processes currently work best for highly interactive tasks (editing, making remote files). In Interlisp-D, the process mechanism is already turned on, and is expected to stay on during normal operations, as some system facilities (in particular, most network operations) require it. However, under exceptional conditions, the following function can be used to turn the world off and on: {FnDef {Name PROCESSWORLD} {Args FLG} {Text Starts up the process world, or if {arg FLG} = {lisp OFF}, kills all processes and turns it off. Normally does not return. The environment starts out with two processes: a top-level {lisp EVALQT} (the initial "tty" process) and the "background" process, which runs the window mouse handler and other system background tasks. Note: {fn PROCESSWORLD} is intended to be called at the top level of Interlisp, not from within a program. It does not toggle some sort of switch; rather, it constructs some new processes in a new part of the stack, leaving any callers of {fn PROCESSWORLD} in a now inaccessible part of the stack. Calling {lisp (PROCESSWORLD 'OFF)} is the only way the call to {fn PROCESSWORLD} ever returns. }} {FnDef {Name HARDRESET} {Args} {Text Resets the whole world, and rebuilds the stack from scratch. This is "harder" than doing {fn RESET} to every process, because it also resets system internal processes (such as the keyboard handler). {fn HARDRESET} automatically turns the process world on (or resets it if it was on), unless the variable {var AUTOPROCESSFLG}{index AUTOPROCESSFLG Var} is {lisp NIL}. }} {Begin SubSec Creating and Destroying Processes} {Title Creating and Destroying Processes} {Text {FnDef {Name ADD.PROCESS} {Args FORM PROP{sub 1} VALUE{sub 1} {ellipsis} PROP{sub N} VALUE{sub N}} {Type NOSPREAD} {Text Creates a new process evaluating {arg FORM}, and returns its process handle. The process's stack environment is the top level, i.e., the new process does not have access to the environment in which {fn ADD.PROCESS} was called; all such information must be passed as arguments in {arg FORM}. The process runs until {arg FORM} returns or the process is explicitly deleted. An untrapped error within the process also deletes the process (unless its {lisp RESTARTABLE} property is {lisp T}), in which case a message is printed to that effect. The remaining arguments are alternately property names and values. Any property/value pairs acceptable to {fn PROCESSPROP} may be given, but the following two are directly relevant to {fn ADD.PROCESS}: {Begin LabeledList Interesting properties in ADD.PROCESS} {Indent 10percent} {Label {lisp NAME}{index NAME (Process Property)}} {Text Value should be a litatom; if not given, the process name is taken from {lisp (CAR {arg FORM})}. {fn ADD.PROCESS} may pack the name with a number to make it unique. This name is solely for the convenience of manipulating processes at Lisp typein; e.g., the name can be given as the {arg PROC} argument to most process functions, and the name appears in menus of processes. However, programs should normally only deal in process handles, both for efficiency and to avoid the confusion that can result if two processes have the same defining form. } {Label {lisp SUSPEND}{index SUSPEND (Process Property)}} {Text If the value is non-{lisp NIL}, the new process is created but then immediately suspended; i.e., the process does not actually run until woken by a {fn WAKE.PROCESS} (below). } {End LabeledList Interesting properties in ADD.PROCESS} }} {FnDef {Name PROCESSPROP} {Args PROC PROP NEWVALUE} {Type NOSPREAD} {Text Used to get or set the values of certain properties of process {arg PROC}, in a manner analogous to {fn WINDOWPROP}. If {arg NEWVALUE} is supplied (including if it is {lisp NIL}), property {arg PROP} is given that value. In all cases, returns the old value of the property. The following properties have special meaning for processes; all others are uninterpreted: {Begin LabeledList properties in PROCESSPROP} {Indent 10percent} {Label {lisp NAME}{index NAME (Process Property)}} {Text Value is a litatom used for identifying the process to the user. } {Label {lisp FORM}{index FORM (Process Property)}} {Text Value is the Lisp form used to start the process (readonly). } {Label {lisp RESTARTABLE}{index RESTARTABLE (Process Property)}} {Text Value is a flag indicating the disposition of the process following errors or hard resets: {Begin LabeledList Values for RESTARTABLE} {Indent 10percent} {Label {lisp NIL} or {lisp NO}} {Text (the default) If an untrapped error (or control-E or control-D) causes its form to be exited, the process is deleted. The process is also deleted if a {fn HARDRESET} (or control-D from {fn RAID}) occurs, causing the entire Process world to be reinitialized.} {Label {lisp T} or {lisp YES}} {Text The process is automatically restarted on errors or {fn HARDRESET}. This is the normal setting for persistent "background" processes, such as the mouse process, that can safely restart themselves on errors.} {Label {lisp HARDRESET}} {Text The process is deleted as usual if an error causes its form to be exited, but it {it is} restarted on a {fn HARDRESET}. This setting is preferred for persistent processes for which an error is an unusual condition, one that might repeat itself if the process were simply blindly restarted.} {End LabeledList Values for RESTARTABLE} } {Label {lisp RESTARTFORM}{index RESTARTFORM (Process Property)}} {Text If the value is non-{lisp-NIL}, it is the form used if the process is restarted (instead of the value of the {lisp FORM} property). Of course, the process must also have a non-{lisp NIL} {lisp RESTARTABLE} prop for this to have any effect. } {Label {lisp BEFOREEXIT}{index BEFOREEXIT (Process Property)}} {Text If the value is the atom {lisp DON'T}, it will not be interrupted by a {fn LOGOUT}. If {fn LOGOUT} is attempted before the process finishes, a message will appear saying that Interlisp is waiting for the process to finish. If you want the {fn LOGOUT} to proceed without waiting, you must use the process status window (from the background menu) to delete the process. } {Label {lisp AFTEREXIT}{index AFTEREXIT (Process Property)}} {Text Value indicates the disposition of the process following a resumption of Lisp after some exit ({fn LOGOUT}, {fn SYSOUT}, {fn MAKESYS}). Possible values are: {Begin LabeledList Values of AFTEREXIT prop} {Indent 10percent} {Label {lisp DELETE}} {Text Delete the process.} {Label {lisp SUSPEND}} {Text Suspend the process; i.e., do not let it run until it is explicitly woken.} {Label <an event>} {Text Cause the process to be suspended waiting for the event ({PageRef Fn AWAIT.EVENT}).} {End LabeledList Values of AFTEREXIT prop} } {Label {lisp INFOHOOK}{index INFOHOOK (Process Property)}} {Text Value is a function or form used to provide information about the process, in conjunction with the process status window ({PageRef Fn PROCESS.STATUS.WINDOW}). } {Label {lisp WINDOW}{index WINDOW (Process Property)}} {Text Value is a window associated with the process, the process's "main" window. Used in conjunction with switching the tty process ({PageRef Fn TTY.PROCESS}). } {Label {lisp TTYENTRYFN}{index TTYENTRYFN (Process Property)}} {Text Value is a function that is applied to the process when the process is made the tty process ({PageRef Fn TTY.PROCESS}). } {Label {lisp TTYEXITFN}{index TTYEXITFN (Process Property)}} {Text Value is a function that is applied to the process when the process ceases to be the tty process ({PageRef Fn TTY.PROCESS}). } {End LabeledList properties in PROCESSPROP} {Note PRIORITY -- Value is the priority at which the process runs, an integer from 0 to 4. Priorities are not yet fully implemented, so the reader is discouraged from setting this property. } }} {FnDef {Name THIS.PROCESS} {Args } {Text Returns the handle of the currently running process, or {lisp NIL} if the Process world is turned off. }} {FnDef {Name DEL.PROCESS} {Args PROC {anonarg}} {Text Deletes process {arg PROC}. {arg PROC} may be a process handle (returned by {fn ADD.PROCESS}), or its name. Note that if {arg PROC} is the currently running process, {fn DEL.PROCESS} does not return! }} {FnDef {Name PROCESS.RETURN} {Args VALUE} {Text Terminates the currently running process, causing it to "return" {arg VALUE}. There is an implicit {fn PROCESS.RETURN} around the {arg FORM} argument given to {fn ADD.PROCESS}, so that normally a process can finish by simply returning; {fn PROCESS.RETURN} is supplied for earlier termination. }} {FnDef {Name PROCESS.RESULT} {Args PROCESS WAITFORRESULT} {Text If {arg PROCESS} has terminated, returns the value, if any, that it returned. This is either the value of a {fn PROCESS.RETURN} or the value returned from the form given to {fn ADD.PROCESS}. If the process was aborted, the value is {lisp NIL}. If {arg WAITFORRESULT} is true, {fn PROCESS.RESULT} blocks until {arg PROCESS} finishes, if necessary; otherwise, it returns {lisp NIL} immediately if {arg PROCESS} is still running. Note that {arg PROCESS} must be the actual process handle returned from {fn ADD.PROCESS}, not a process name, as the association between handle and name disappears when the process finishes (and the process handle itself is then garbage collected if no one else has a pointer to it). }} {FnDef {Name PROCESS.FINISHEDP} {Args PROCESS} {Text True if {arg PROCESS} has terminated. The value returned is an indication of how it finished: {lisp NORMAL} or {lisp ERROR}. }} {FnDef {Name PROCESSP} {Args PROC} {Text True if {arg PROC} is the handle of an active process, i.e., one that has not yet finished. }} {FnDef {Name RELPROCESSP} {Args PROCHANDLE} {Text True if {arg PROCHANDLE} is the handle of a deleted process. This is analogous to {fn RELSTKP}. It differs from {fn PROCESS.FINISHEDP} in that it never causes an error, while {fn PROCESS.FINISHEDP} can cause an error if its {arg PROC} argument is not a process at all. }} {FnDef {Name RESTART.PROCESS} {Args PROC} {Text Unwinds {Arg PROC} to its top level and reevaluates its form. This is effectively a {fn DEL.PROCESS} followed by the original {fn ADD.PROCESS}. }} {FnDef {Name MAP.PROCESSES} {Args MAPFN} {Text Maps over all processes, calling {arg MAPFN} with three arguments: the process handle, its name, and its form. }} {FnDef {Name FIND.PROCESS} {Args PROC ERRORFLG} {Text If {arg PROC} is a process handle or the name of a process, returns the process handle for it, else {lisp NIL}. If {arg ERRORFLG} is {lisp T}, generates an error if {arg PROC} is not, and does not name, a live process. }} }{End SubSec Creating and Destroying Processes} {Begin SubSec Process Control Constructs} {Title Process Control Constructs} {Text {FnDef {Name BLOCK} {Args MSECSWAIT TIMER} {Text Yields control to the next waiting process, assuming any is ready to run. If {arg MSECSWAIT} is specified, it is a number of milliseconds to wait before returning, or {lisp T}, meaning wait forever (until explicitly woken). Alternatively, {arg TIMER} can be given as a millisecond timer (as returned by {fn SETUPTIMER}, {PageRef Fn SETUPTIMER}) of an absolute time at which to wake up. In any of those cases, the process enters the {it waiting} state until the time limit is up. {fn BLOCK} with no arguments leaves the process in the {it runnable} state, i.e., it returns as soon as every other runnable process of the same priority has had a chance. {fn BLOCK} can be aborted by interrupts such as control-D, control-E, or control-B. Note that {fn BLOCK} will return before its timeout is completed, if the process is woken by {fn WAKE.PROCESS}, {fn PROCESS.EVAL}, or {PROCESS.APPLY}. }} {FnDef {Name DISMISS} {Args MSECSWAIT TIMER NOBLOCK} {Text {fn DISMISS} is used to dismiss the current process for a given period of time. Similar to {fn BLOCK}, except that (1) {fn DISMISS} is guaranteed not to return until the specified time has elapsed; (2) {arg MSECSWAIT} cannot be {lisp T} to wait forever; and (3) If {arg NOBLOCK} is {lisp T}, {fn DISMISS} will not allow other processes to run, but will busy-wait until the amount of time given has elapsed. }} {FnDef {Name WAKE.PROCESS} {Args PROC STATUS} {Text Explicitly wakes process {arg PROC}, i.e., makes it {it runnable}, and causes its call to {fn BLOCK} (or other waiting function) to return {arg STATUS}. This is one simple way to notify a process of some happening; however, note that if {fn WAKE.PROCESS} is applied to a process more than once before the process actually gets its turn to run, it sees only the latest {arg STATUS}. }} {FnDef {Name SUSPEND.PROCESS} {Args PROC} {Text Blocks process {arg PROC} indefinitely, i.e., {arg PROC} will not run until it is woken by a {fn WAKE.PROCESS}. }} The following three functions allow access to the stack context of some other process. They require a little bit of care, and are computationally non-trivial, but they do provide a more powerful way of manipulating another process than {fn WAKE.PROCESS} allows. {FnDef {Name PROCESS.EVALV} {Args PROC VAR} {Text Performs {lisp (EVALV {arg VAR})} in the stack context of {arg PROC}. }} {FnDef {Name PROCESS.EVAL} {Args PROC FORM WAITFORRESULT} {Text Evaluates {arg FORM} in the stack context of {arg PROC}. If {arg WAITFORRESULT} is true, blocks until the evaluation returns a result, else allows the current process to run in parallel with the evaluation. Any errors that occur will be in the context of {arg PROC}, so be careful. In particular, note that {lisp (PROCESS.EVAL {arg PROC} '(NLSETQ (FOO)))} and {lisp (NLSETQ (PROCESS.EVAL {arg PROC} '(FOO)))} behave quite differently if {lisp FOO} causes an error. And it is quite permissible to intentionally cause an error in proc by performing {lisp (PROCESS.EVAL {arg PROC} '(ERROR!))} If errors are possible and {arg WAITFORRESULT} is true, the caller should almost certainly make sure that {arg FORM} traps the errors; otherwise the caller could end up waiting forever if {arg FORM} unwinds back into the pre-existing stack context of {arg PROC}. Note: After {arg FORM} is evaluated in {arg PROC}, the process {arg PROC} is woken up, even if it was running {fn BLOCK} or {fn AWAIT.EVENT}. This is necessary because an event of interest may have occurred while the process was evaluating {arg FORM}. }} {FnDef {Name PROCESS.APPLY} {Args PROC FN ARGS WAITFORRESULT} {Text Performs {lisp (APPLY {arg FN} {arg ARGS})} in the stack context of {arg PROC}. Note the same warnings as with {fn PROCESS.EVAL}. }} }{End SubSec Process Control Constructs} {Begin SubSec Events} {Title Events} {Text An "event" is a synchronizing primitive used to coordinate related processes, typically producers and consumers. Consumer processes can "wait" on events, and producers "notify" events. {FnDef {Name CREATE.EVENT} {Args NAME} {Text Returns an instance of the {lisp EVENT} datatype, to be used as the event argument to functions listed below. {arg NAME} is arbitrary, and is used for debugging or status information. }} {FnDef {Name AWAIT.EVENT} {Args EVENT TIMEOUT TIMERP} {Text Suspends the current process until {arg EVENT} is notified, or until a timeout occurs. If {arg TIMEOUT} is {lisp NIL}, there is no timeout. Otherwise, timeout is either a number of milliseconds to wait, or, if {arg TIMERP} is {lisp T}, a millisecond timer set to expire at the desired time using {fn SETUPTIMER} (see {PageRef Tag Timers}). }} {FnDef {Name NOTIFY.EVENT} {Args EVENT ONCEONLY} {Text If there are processes waiting for {arg EVENT} to occur, causes those processes to be placed in the running state, with {arg EVENT} returned as the value from {fn AWAIT.EVENT}. If {arg ONCEONLY} is true, only runs the first process waiting for the event (this should only be done if the programmer knows that there can only be one process capable of responding to the event at once). }} The meaning of an event is up to the programmer. In general, however, the notification of an event is merely a hint that something of interest to the waiting process has happened; the process should still verify that the conceptual event actually occurred. That is, {it the process should be written so that it operates correctly even if woken up before the timeout and in the absence of the notified event.} In particular, the completion of {fn PROCESS.EVAL} and related operations in effect wakes up the process in which they were performed, since there is no secure way of knowing whether the event of interest occurred while the process was busy performing the {fn PROCESS.EVAL}. There is currently one class of system-defined events, used with the network code. Each Pup and NS socket has associated with it an event that is notified when a packet arrives on the socket; the event can be obtained by calling {lisp (PUPSOCKETEVENT {arg PUPSOCKET})} or {lisp (NSOCKETEVENT {arg NSOCKET})}, respectively. }{End SubSec Events} {Begin SubSec Monitors} {Title Monitors} {Text It is often the case that cooperating processes perform operations on shared structures, and some mechanism is needed to prevent more than one process from altering the structure at the same time. Some languages have a construct called a monitor, a collection of functions that access a common structure with mutual exclusion provided and enforced by the compiler via the use of monitor locks. Interlisp-D has taken this implementation notion as the basis for a mutual exclusion capability suitable for a dynamically-scoped environment. A monitorlock is an object created by the user and associated with (e.g., stored in) some shared structure that is to be protected from simultaneous access. To access the structure, a program waits for the lock to be free, then takes ownership of the lock, accesses the structure, then releases the lock. The functions and macros below are used: {FnDef {Name CREATE.MONITORLOCK} {Args NAME {anonarg}} {Text Returns an instance of the {lisp MONITORLOCK} datatype, to be used as the lock argument to functions listed below. {arg NAME} is arbitrary, and is used for debugging or status information. }} {MacDef {Name WITH.MONITOR} {Args LOCK {lisp .} FORMS} {Text Evaluates {lisp (PROGN . {arg FORMS})} while owning {arg LOCK}. Value is the last of {arg FORMS}. This construct is implemented so that the lock is released even if the form is exited via error (currently implemented with {fn RESETLST}). Ownership of a lock is dynamically scoped: if the current process already owns the lock (e.g., if the caller was itself inside a {mac WITH.MONITOR} for this lock), {mac WITH.MONITOR} is a noop. }} {MacDef {Name WITH.FAST.MONITOR} {Args LOCK {lisp .} FORMS} {Text Like {mac WITH.MONITOR}, but implemented without the {fn RESETLST}. User interrupts (e.g., control-E) are inhibited during the evaluation of {arg FORMS}. Programming restriction: the evaluation of {arg FORMS} must not error (the lock would not be released). This construct is mainly useful when {arg FORMS} is a small, safe computation that never errors and need never be interrupted. }} {FnDef {Name MONITOR.AWAIT.EVENT} {Args RELEASELOCK EVENT TIMEOUT TIMERP} {Text For use in blocking inside a monitor. Performs {lisp (AWAIT.EVENT {arg EVENT} {arg TIMEOUT} {arg TIMERP})}, but releases {arg RELEASELOCK} first, and reobtains the lock (possibly waiting) on wakeup. }} Typical use for {fn MONITOR.AWAIT.EVENT}: A function wants to perform some operation on {arg Foo}, but only if it is in a certain state. It has to obtain the lock on the structure to make sure that the state of the structure does not change between the time it tests the state and performs the operation. If the state turns out to be bad, it then waits for some other process to make the state good, meanwhile releasing the lock so that the other process can alter the structure. {lispcode (WITH.MONITOR {arg FooLock} (until {arg condition-of-Foo} do (MONITOR.AWAIT.EVENT {arg FooLock} {arg EventFooChanged} {arg timeout})) {arg operate-on-Foo})} It is sometimes convenient for a process to have {mac WITH.MONITOR} at its top level and then do all its interesting waiting using {fn MONITOR.AWAIT.EVENT}. Not only is this often cleaner, but in the present implementation in cases where the lock is frequently accessed, it saves the {fn RESETLST} overhead of {mac WITH.MONITOR}. Programming restriction: there must not be an {fn ERRORSET} between the enclosing {mac WITH.MONITOR} and the call to {fn MONITOR.AWAIT.EVENT} such that the {fn ERRORSET} would catch an {fn ERROR!} and continue inside the monitor, for the lock would not have been reobtained. (The reason for this restriction is that, although {fn MONITOR.AWAIT.EVENT} won't itself error, the user could have caused an error with an interrupt, or a {fn PROCESS.EVAL} in the context of the waiting process that produced an error.) On rare occasions it may be useful to manipulate monitor locks directly. The following two functions are used in the implementation of {mac WITH.MONITOR}: {FnDef {Name OBTAIN.MONITORLOCK} {Args LOCK DONTWAIT UNWINDSAVE} {Text Takes possession of {arg LOCK}, waiting if necessary until it is free, unless {arg DONTWAIT} is true, in which case it returns {lisp NIL} immediately. If {arg UNWINDSAVE} is true, performs a {fn RESETSAVE} to be unwound when the enclosing {fn RESETLST} exits. Returns {arg LOCK} if {arg LOCK} was successfully obtained, {lisp T} if the current process already owned {arg LOCK}. }} {FnDef {Name RELEASE.MONITORLOCK} {Args LOCK} {Text Releases {arg LOCK} if it is owned by the current process, and wakes up the next process, if any, waiting to obtain the lock. }} When a process is deleted, any locks it owns are released. }{End SubSec Monitors} {Begin SubSec Global Resources} {Title Global Resources} {Text The biggest source of problems in the multi-processing environment is the matter of global resources. Two processes cannot both use the same global resource if there can be a process switch in the middle of their use (currently this means calls to {fn BLOCK}, but ultimately with a preemptive scheduler means anytime). Thus, user code should be wary of its own use of global variables, if it ever makes sense for the code to be run in more than one process at a time. "State" variables private to a process should generally be bound in that process; structures that are shared among processes (or resources used privately but expensive to duplicate per process) should be protected with monitor locks or some other form of synchronization. Aside from user code, however, there are many {it system} global variables and resources. Most of these arise historically from the single-process Interlisp-10 environment, and will eventually be changed in Interlisp-D to behave appropriately in a multi-processing environment. Some have already been changed, and are described below. Two other resources not generally thought of as global variables{emdash}the keyboard and the mouse{emdash}are particularly idosyncratic, and are discussed in the next section. The following resources, which are global in Interlisp-10, are allocated per process in Interlisp-D: primary input and output (the streams affected by {fn INPUT} and {fn OUTPUT}), terminal input and output (the streams designated by the name {lisp T}), the primary read table and primary terminal table, and dribble files. Thus, each process can print to its own primary output, print to the terminal, read from a different primary input, all without interfering with another process's reading and printing. {Tag TtyWindowCreation} Each process begins life with its primary and terminal input/output streams set to a dummy stream. If the process attempts input or output using any of those dummy streams, e.g., by calling {lisp (READ T)}, or {lisp (PRINT & T)}, a tty window is automatically created for the process, and that window becomes the primary input/output and terminal input/output for the process. The default tty window is created at or near the region specified in the variable {var DEFAULTTTYREGION}{Index DEFAULTTTYREGION Var}. A process can, of course, call {fn TTYDISPLAYSTREAM} explicitly to give itself a tty window of its own choosing, in which case the automatic mechanism never comes into play. Calling {fn TTYDISPLAYSTREAM} when a process has no tty window not only sets the terminal streams, but also sets the primary input and output streams to be that window, assuming they were still set to the dummy streams. {FnDef {Name HASTTYWINDOWP} {Args PROC} {Text Returns {lisp T} if the process {arg PROC} has a tty window; {lisp NIL} otherwise. If {arg PROC} is {lisp NIL}, it defaults to the current process. }} Other system resources that are typically changed by {fn RESETFORM}, {fn RESETLST}, or {fn RESETVARS} are all global entities. In the multiprocessing environment, these constructs are suspect, as there is no provision for "undoing" them when a process switch occurs. For example, in the current release of Interlisp-D, it is not possible to set the print radix to 8 inside only one process, as the print radix is a global entity. Note that {fn RESETFORM} and similar expressions are perfectly valid in the process world, and even quite useful, when they manipulate things strictly within one process. The process world is arranged so that deleting a process also unwinds any {lisp RESETxxx} expressions that were performed in the process and are still waiting to be unwound, exactly as if a control-D had reset the process to the top. Additionally, there is an implicit {fn RESETLST} at the top of each process, so that {fn RESETSAVE} can be used as a way of providing "cleanup" functions for when a process is deleted. For these, the value of {var RESETSTATE}{index RESETSTATE Var} ({PageRef Var RESETSTATE}) is {lisp NIL} if the process finished normally, {lisp ERROR} if it was aborted by an error, {lisp RESET} if the process was explicitly deleted, and {lisp HARDRESET} if the process is being restarted after a {fn HARDRESET} or a {fn RESTART.PROCESS}. }{End SubSec Global Resources} {Begin SubSec Typein and the TTY Process} {Title Typein and the TTY Process} {Text There is one global resource, the keyboard, that is particularly problematic to share among processes. Consider, for example, having two processes both performing {lisp (READ T)}. Since the keyboard input routines block while there is no input, both processes would spend most of their time blocking, and it would simply be a matter of chance which process received each character of typein. To resolve such dilemmas, the system designates a distinguished process, termed the {it tty process}, that is assumed to be the process that is involved in terminal interaction. Any typein from the keyboard goes to that process. If a process other than the tty process requests keyboard input, it blocks until it becomes the tty process. When the tty process is switched (in any of the ways described further below), any typeahead that occurred before the switch is saved and associated with the current tty process. Thus, it is always the case that keystrokes are sent to the process that is the tty process at the time of the keystrokes, regardless of when that process actually gets around to reading them. It is less immediately obvious how to handle keyboard interrupt characters, as their action is asynchronous and not always tied to typein. Interrupt handling is described on {PageRef Tag ProcessInterrupts}. {Begin SubSec Switching the TTY Process} {Title Switching the TTY Process} {Text Any process can make itself be the tty process by calling {fn TTY.PROCESS}. {FnDef {Name TTY.PROCESS} {Args PROC} {Text Returns the handle of the current tty process. In addition, if {arg PROC} is non-{lisp NIL}, makes it be the tty process. The special case of {arg PROC} = {lisp T} is interpreted to mean the executive process; this is sometimes useful when a process wants to explicitly give up being the tty process. }} {FnDef {Name TTY.PROCESSP} {Args PROC} {Text True if {arg PROC} is the tty process; {arg PROC} defaults to the running process. Thus, {lisp (TTY.PROCESSP)} is true if the caller is the tty process. }} {FnDef {Name WAIT.FOR.TTY} {Args} {Text Efficiently waits until {lisp (TTY.PROCESSP)} is true. {fn WAIT.FOR.TTY} is called internally by the system functions that read from the terminal; user code thus need only call it in special cases. {fn WAIT.FOR.TTY} spawns a new mouse process if called under the mouse process (see {fn SPAWN.MOUSE}, {PageRef Fn SPAWN.MOUSE}). }} In some cases, such as in functions invoked as a result of mouse action or a user's typed-in call, it is reasonable for the function to invoke {fn TTY.PROCESS} itself so that it can take subsequent user type in. In other cases, however, this is too undisciplined; it is desirable to let the user designate which process typein should be directed to. This is most conveniently done by mouse action. The system supports the model that "to type to a process, you click in its window." To cooperate with this model, any process desiring keyboard input should put its process handle as the {lisp PROCESS}{Index PROCESS (Window Property)} property of its window(s). To handle the common case, the function {fn TTYDISPLAYSTREAM} does this automatically when the ttydisplaystream is switched to a new window. A process can own any number of windows; clicking in any of those windows gives the process the tty. This mechanism suffices for most casual process writers. For example, if a process wants all its input/output interaction to occur in a particular window that it has created, it should just make that window be its tty window by calling {fn TTYDISPLAYSTREAM}. Thereafter, it can {fn PRINT} or {fn READ} to/from the {lisp T} stream; if the process is not the tty process at the time that it calls {fn READ}, it will block until the user clicks in the window. For those needing tighter control over the tty, the default behavior can be overridden or supplemented. The remainder of this section describes the mechanisms involved. There is a window property {lisp WINDOWENTRYFN}{index WINDOWENTRYFN (Window Property)} that controls whether and how to switch the tty to the process owning a window. The mouse handler, before invoking any normal {lisp BUTTONEVENTFN}, specifically notices the case of a button going down in a window that belongs to a process (i.e., has a {lisp PROCESS} window property) that is not the tty process. In this case, it invokes the window's {lisp WINDOWENTRYFN} of one argument ({arg WINDOW}). {lisp WINDOWENTRYFN} defaults to {fn GIVE.TTY.PROCESS}: {FnDef {Name GIVE.TTY.PROCESS} {Args WINDOW} {Text If {Arg WINDOW} has a {lisp PROCESS} property, performs {Lisp (TTY.PROCESS (WINDOWPROP {arg WINDOW} 'PROCESS))} and then invokes {Arg WINDOW}'s {lisp BUTTONEVENTFN} function (or {lisp RIGHTBUTTONFN} if the right button is down). }} There are some cases where clicking in a window does not always imply that the user wants to talk to that window. For example, clicking in a text editor window with a shift key held down means to "shift-select" some piece of text into the input buffer of the {it current} tty process. The editor supports this by supplying a {lisp WINDOWENTRYFN} that performs {fn GIVE.TTY.PROCESS} if no shift key is down, but goes into its shift-select mode, without changing the tty process, if a shift key is down. The shift-select mode performs a {fn BKSYSBUF} of the selected text when the shift key is let up, the {fn BKSYSBUF} feeding input to the current tty process. Sometimes a process wants to be notified when it becomes the tty process, or stops being the tty process. For example, Chat ({PageRef Fn CHAT}) turns off all keyboard interrupt characters while it is the tty process, so that they can be passed transparently to the remote host. To support this, there are two process properties, {lisp TTYEXITFN}{index *PRIMARY* TTYEXITFN (Process property)} and {lisp TTYENTRYFN}{index *PRIMARY* TTYENTRYFN (Process property)}. The actions taken by {fn TTY.PROCESS} when it switches the tty to a new process are as follows: the former tty process's {lisp TTYEXITFN} is called with two arguments ({arg OLDTTYPROCESS} {arg NEWTTYPROCESS}); the new process is made the tty process; finally, the new tty process's {lisp TTYENTRYFN} is called with two arguments ({arg NEWTTYPROCESS} {arg OLDTTYPROCESS}). Normally the {lisp TTYENTRYFN} and {lisp TTYEXITFN} need only their first argument, but the other process involved in the switch is supplied for completeness. In the present system, most processes want to interpret the keyboard in the same way, so it is considered the responsibility of any process that changes the keyboard interpretation to restore it to the normal state by its {lisp TTYEXITFN}. {note All that will be much cleaner if/when we use tip tables.} A window is "owned" by the last process that anyone gave as the window's {lisp PROCESS} property. Ordinarily there is no conflict here, as processes tend to own disjoint sets of windows (though, of course, cooperating processes can certainly try to confuse each other). The only likely problem arises with that most global of windows, {Var PROMPTWINDOW}{index PROMPTWINDOW Var}. Programs should not be tempted to read from {Var PROMPTWINDOW}. This is not usually necessary anyway, as the first attempt to read from {lisp T} in a process that has not set its {lisp TTYDISPLAYSTREAM} to its own window causes a tty window to be created for the process (see {PageRef Tag TtyWindowCreation}). }{End SubSec Switching the TTY Process} {Begin SubSec Handling of Interrupts} {Title Handling of Interrupts} {Text {Tag ProcessInterrupts} At the time that a keyboard interrupt character ({PageRef Tag InterruptChars}) is struck, any process could be running, and some decision must be made as to which process to actually interrupt. To the extent that keyboard interrupts are related to typein, most interrupts are taken in the tty process; however, the following are handled specially: {Begin LabeledList Interrupt actions} {Indent 15percent} {Label {Lisp RESET}, {Lisp ERROR}} {Text (normally control-D and control-E) These interrupts are taken in the mouse process, if the mouse is not in its idle state; otherwise they are taken in the tty process. Thus, control-E can be used to abort some mouse-invoked window action, such as the Shape command. As a consequence, note that if the mouse invokes some lengthy computation that the user thinks of as "background", control-E still aborts it, even though that may not have been what the user intended. Such lengthy computations, for various reasons, should generally be performed by spawning a separate process to perform them. The {Lisp RESET} interrupt in a process other than the executive is interpreted exactly as if an error unwound the process to its top level: if the process was designated {lisp RESTARTABLE} = {lisp T}, it is restarted; otherwise it is killed. } {Label {lisp HELP}} {Text (Initially control-H) A menu of processes is presented to the user, who is asked to select which one the interrupt should occur in. The current tty process appears with a * next to its name at the top of the menu. The menu also includes an entry "[Spawn Mouse]", for the common case of needing a mouse because the mouse process is currently tied up running someone's {lisp BUTTONEVENTFN}; selecting this entry spawns a new mouse process, and no break occurs. } {Label {Lisp BREAK}} {Text (Initially control-B) Performs the {lisp HELP} interrupt always in the tty process. } {Label {Lisp RUBOUT}} {Text (Initially <del>) This interrupt clears typeahead in {it all} processes. } {Label {Lisp RAID}, {lisp STACK OVERFLOW}, {Lisp STORAGE FULL}} {Text These interrupts always occur in whatever process was running at the time the interrupt struck. In the cases of {lisp STACK OVERFLOW}{Index STACK OVERFLOW Error} and {Lisp STORAGE FULL}{Index STORAGE FULL Error}, this means that the interrupt is more likely to strike in the offending process (especially if it is a "runaway" process that is not blocking). Note, however, that this process is still not necessarily the guilty party; it could be an innocent bystander that just happened to use up the last of a resource prodigiously consumed by some other process. } {End LabeledList Interrupt actions} }{End SubSec Handling of Interrupts} }{End SubSec Typein and the TTY Process} {Begin SubSec Keeping the Mouse Alive} {Title Keeping the Mouse Alive} {Text Since the window mouse handler runs in its own process, it is not available while a window's {lisp BUTTONEVENTFN} function (or any of the other window functions invoked by mouse action) is running. This leads to two sorts of problems: (1) a long computation underneath a {lisp BUTTONEVENTFN} deprives the user of the mouse for other purposes, and (2) code that runs as a {lisp BUTTONEVENTFN} cannot rely on other {lisp BUTTONEVENTFN}s running, which means that there some pieces of code that run differently from normal when run under the mouse process. These problems are addressed by the following functions: {FnDef {Name SPAWN.MOUSE} {Args {anonarg}} {Text Spawns another mouse process, allowing the mouse to run even if it is currently "tied up" under the current mouse process. This function is intended mainly to be typed in at the Lisp executive when the user notices the mouse is busy. }} {FnDef {Name ALLOW.BUTTON.EVENTS} {Args } {Text Performs a {lisp (SPAWN.MOUSE)} only when called underneath the mouse process. This should be called (once, on entry) by any function that relies on {lisp BUTTONEVENTFN}s for completion, if there is any possibility that the function will itself be invoked by a mouse function. }} It never hurts, at least logically, to call {fn SPAWN.MOUSE} or {fn ALLOW.BUTTON.EVENTS} needlessly, as the mouse process arranges to quietly kill itself if it returns from the user's {lisp BUTTONEVENTFN} and finds that another mouse process has sprung up in the meantime. (There is, of course, some computational expense.) }{End SubSec Keeping the Mouse Alive} {Begin SubSec Debugging Processes} {Title Debugging Processes} {Text {FnDef {Name PROCESS.STATUS.WINDOW} {Args WHERE} {Text Puts up a window that provides several debugging commands for manipulating running processes. If the window is already up, {fn PROCESS.STATUS.WINDOW} refreshes it. If {arg WHERE} is a position, the window is placed in that position; otherwise, the user is prompted for a position. The window consists of two menus. The first is a menu of all the processes at the moment. Commands in the second menu operate on the process selected in the first menu. The commands are: {Begin LabeledList PROCESS.STATUS.WINDOW commands} {Indent 10percent} {Label {lisp BT}, {lisp BTV}, {lisp BTV*}, {lisp BTV!}} {Text Performs a backtrace of the selected process. The first time, it prompts for a window in which to display the backtrace. } {Label {lisp WHO?}} {Text Changes the selection to the tty process, i.e., the one currently in control of the keyboard. } {Label {lisp KBD←}} {Text Associates the keyboard with the selected process; i.e., makes the selected process be the tty process. } {Label {lisp INFO}} {Text If the selected process has an {lisp INFOHOOK}{Index *PRIMARY* INFOHOOK (Process property)}, calls it. The hook may be a function, which is then applied to two arguments, the process and the button ({lisp LEFT} or {lisp MIDDLE}) used to invoke {lisp INFO}, or a form, which is simply {fn EVAL}'ed. The {fn APPLY} or {fn EVAL} happens in the context of the selected process, using {fn PROCESS.APPLY} or {fn PROCESS.EVAL}. The info hook can be set using {fn PROCESSPROP}. } {Label {lisp KILL}} {Text Deletes the selected process. } {Label {lisp RESTART}} {Text Restarts the selected process. } {Label {lisp WAKE}} {Text Wakes the selected process. Prompts for a value to wake it with (see {fn WAKE.PROCESS}). } {Label {lisp SUSPEND}} {Text Suspends the selected process; i.e., causes it to block indefinitely (until explicitly woken). } {Label {lisp BREAK}} {Text Enter a break under the selected process. This has the side effect of waking the process with the value returned from the break. } {End LabeledList PROCESS.STATUS.WINDOW commands} }} Currently, the process status window runs under the mouse process, like other menus, so if the mouse is unavailable (e.g., a mouse function is performing an extensive computation), you may be unable to use the process status window (you can try {fn SPAWN.MOUSE}, of course). }{End SubSec Debugging Processes} {Begin SubSec Non-Process Compatibility} {Title Non-Process Compatibility} {Text This section describes some considerations for authors of programs that ran in the old single-process Interlisp-D environment, and now want to make sure they run properly in the Multi-processing world. The biggest problem to watch out for is code that runs underneath the mouse handler. Writers of mouse handler functions should remember that in the process world the mouse handler runs in its own process, and hence (a) you cannot depend on finding information on the stack (stash it in the window instead), and (b) while your function is running, the mouse is not available (if you have any non-trivial computation to do, spawn a process to do it, notify one of your existing processes to do it, or use {fn PROCESS.EVAL} to run it under some other process). The following functions are meaningful even if the process world is not on: {fn BLOCK} (invokes the system background routine, which includes handling the mouse); {fn TTY.PROCESS}, {fn THIS.PROCESS} (both return {lisp NIL}); and {fn TTY.PROCESSP} (returns {lisp T}, i.e., anyone is allowed to take tty input). In addition, the following two functions exist in both worlds: {FnDef {Name EVAL.AS.PROCESS} {Args FORM} {Text Same as {lisp (ADD.PROCESS {arg FORM} 'RESTARTABLE 'NO)}, when processes are running, {fn EVAL} when not. This is highly recommended for mouse functions that perform any non-trivial activity. }} {FnDef {Name EVAL.IN.TTY.PROCESS} {Args FORM WAITFORRESULT} {Text Same as {lisp (PROCESS.EVAL (TTY.PROCESS) {arg FORM} {arg WAITFORRESULT})}, when processes are running, {fn EVAL} when not. }} Most of the process functions that do not take a process argument can be called even if processes aren't running. {fn ADD.PROCESS} creates, but does not run, a new process (it runs when {fn PROCESSWORLD} is called). }{End SubSec Non-Process Compatibility} }{End SubSec The Interlisp-D Process Mechanism}