Go to the previous, next section.

# The ILU Common Lisp Lightweight Process System

## Introduction

Although it is not required by the specification, most Common Lisp implementations include a facility for multiple, independent threads of control (often called lightweight processes) within a single Lisp environment. Unfortunately, this facility is not standardized across the various implementations. Although the capabilities provided are very similar across implementations, the details of lightweight processes and the interface to them differ significantly. This situation makes it difficult to write programs that use lightweight processes and yet are portable across Common Lisp implementations.

Common Lisp ILU does not requires lightweight processes in order to function, but they are useful. In particular, servers typically make heavy use of lightweight process facilities. The purpose of the ILU CL Process Interface is to provide a standardized, portable interface to lightweight processes for use within the ILU environment. This interface isolates ILU users from the differences in the various lightweight process implementations and allows them to write programs that are portable across all implementations to which the ILU CL Process Interface has been ported. At present, these implementations include Franz Allegro CL 4.1, and Lucid Common Lisp 3.0 (a.k.a., Sun Common Lisp).

This chapter explains how the ILU CL Process Interface works for ILU users. It begins with an overview that describes the ILU CL Process model, followed by a listing of some functional capabilities of this model. After brief discussions of the implementation architecture and general limitations of the ILU CL Process Interface, the chapter presents an example of how to use the interface to define a simple shared FIFO queue. Next, it lists all of the functions and macros necessary to use lightweight processes in the ILU environment. The chapter concludes with a brief list of references.

To use the information in this chapter, you should be familiar with Common Lisp and with the notion of processes and threads in an operating system. Familiarity with the UNIX process model would also be helpful. (See the References section for recommendations on further reading.)

## Overview Of The ILU CL Process Model

The ILU CL Process Interface features an interface to lightweight processes similar to that on the Symbolics Lisp machine. In particular, within a single Lisp environment (which on stock hardware runs as a single heavyweight UNIX process) there are multiple threads of control that can be scheduled independently. These threads are called lightweight processes (or sometimes just processes). Each lightweight process contains its own run-time control and binding stack, but it shares the global data and program address space with all other processes in the Lisp environment. Note that this arrangement differs from that of the UNIX heavyweight process facility, where each process has its own address space as well as its own run-time stack.

### The Scheduler Process

Each lightweight process represents an independent thread of control. The multiple threads within the Lisp environment are managed by a special scheduler process. The ILU CL Process Interface makes no assumptions about the nature of this scheduler process. However, most implementations use a time-slice, priority-based scheduler. In such a scheduler, an interrupt occurs once every so often (called the scheduler's quantum). When the interrupt occurs, the process that is currently running is stopped and its state is saved. The scheduler then examines all processes that are runnable (that is, waiting to run) and restarts the process that has the highest priority. This process runs until the next interrupt or until it gives up control to the scheduler, whichever comes first. At any given time, the one process that is "currently" running is known as the current process.

### States Of Processes

In the ILU CL Process model, each lightweight process is represented by a single Lisp object that maintains the information about that process. Also, each process is always in one of three states: active, inactive, or killed. A process maintains two lists of objects called, respectively, the run reasons and the arrest reasons for the process. For a process to be active, it must have at least one run reason and no arrest reasons. A process with no run reasons or at least one arrest reason is considered inactive. The ILU CL Process Interface provides functions for adding and removing run and arrest reasons for a process. Thus, the user (or a program) can move a process between the active and inactive states.

The scheduler runs only active processes. Until an inactive process is reactivated, it cannot run. A killed process is one that has been explicitly killed (using the `ilu-process:process-kill` function). A killed process can never be run again (that is, it can never be made active).

An active process can in turn be in one of two substates: runnable and waiting. A runnable process is ready to be restarted by the scheduler, which determines whether and when a process will actually be restarted based on its status (that is, priority) and the status of the other runnable processes. A waiting process is a process that has a wait function and a list of wait arguments. These two items are supplied to the process using the `ilu-process:process-wait` function. Periodically, the scheduler will `apply` the process's wait function to its wait arguments (in the context of the scheduler). If the result is a non-`nil` value, the wait function and wait arguments are removed from the process, and the process thereby becomes runnable. Usually, the scheduler evaluates the wait functions for all waiting processes every time around the scheduler loop. Therefore, it is important that wait functions be fast and very efficient.

### Removing Or Killing Processes

You can reversably remove a process from a runnable state either by entering a wait or by making it inactive. In general, it is more efficient to make a process inactive because this removes it from the scheduler's active process list. Thus, the scheduler does not incur the cost of periodically evaluating its wait function. However, an inactive process cannot make itself active. It must depend on some other process to recognize when it is ready to run again and to reactivate it at that time. Although a waiting process is initially more costly than an inactive one, it is automatically returned to a runnable state by the scheduler whenever its wait function returns non-`nil`. Hence, no second process is needed to restart a waiting function. Thus, the choice between waiting a process and rendering it inactive depends on the architecture of the application being written.

When a process is first started, it is given a Lisp function and a set of arguments to this function. These are known as the process's initial-function and inital-arguments, respectively. A newly created process, `apply`s its initial-function to its inital-arguments. When the initial-function returns, the process is automatically killed. Once killed it can never be restarted. You can also kill the process before the inital-function returns using the `ilu-process:process-kill` function, which causes the process to execute a `throw` in its current context. This `throw` causes the stack to unwind (executing unwind-protect forms along the way) and the initial-function to return, thereby killing the process.

### Properties Of Processes

Every process has a number of properties. Specifically, a process has an arbitrary process name that identifies it in displays and in certain operations. Process names need not be unique. A process also has a priority that the scheduler uses optionally to determine when to schedule the process. Priorities are small integers and default to zero (0). In most implementations, processes with higher priorities are given scheduling preference. Negatives are used to indicate that a process should run as a background task when nothing else is running. Finally, a process has a quantum, which is the amount of time (measured in seconds) that the process wishes to run each time before it is interrupted. In some implementations, the scheduler uses a process's quantum to help determine the actual length of the time-slice given to the process. Many implementations ignore the quantum altogether.

### Process Locks

The ILU CL Process Interface also includes a facility called process locks that supports exclusion-based sharing of a common resource (that is, a common object or data structure) or a critical region of code by two or more concurrent processes. A process lock is an object that a process can lock in order to claim exclusive access to the shared resource corresponding to the lock. Process locks are essentially a semaphore mechanism specialized for use with the ILU CL Process interface.

Each process lock has a name and a locker. A lock's name is for display purposes only. Processes can ask to gain or relinquish exclusive rights to the lock (called locking and unlocking the lock, respectively). While a process has rights to the lock, the lock's locker is (generally) the process object for that process. When a process asks to lock a lock that is already locked, the asking process blocks and "waits" until the lock is free. Waiting does not necessarily use the standard wait mechanism. Some implementations use process deactivation to implement the "wait" in this case. Some implementations may also maintain a queue of processes waiting for a lock to be freed, thereby ensuring fair access to the lock. Other implementations may not maintain such a queue, and therefore fair access to the lock is not guaranteed.

Process locks are contractual in nature. The various processes sharing a resource (or critical section of code) must all agree not to access the common resource while the process lock corresponding to that resource is held by another process. Furthermore, they must agree to lock the process lock whenever they need exclusive access to the resource, thereby notifying the other processes of their intent. Moreover, the correspondence between a process lock and the shared resource is a matter of agreement between the cooperating processes. The system does not provide any direct support for this correspondence (although it may be added on at a higher level built on top of the basic process lock mechanism).

Process locks provide a code-centered "sharing" mechanism where the access control is built into the programs that access the shared resource. Process locks are suited for closed, or non-extensible, applications where the shared resource is a standard Lisp data structure (that is, not a CLOS object) and where efficiency is a major concern. For applications not meeting these criteria, a mechanism in which a CLOS object itself controls simultaneous access to its internal data structures may be more appropriate.

## Functional Overview

The ILU CL Process Interface provides all of the functions and macros necessary to use lightweight processes in the ILU environment. The functionality provided by these functions and macros includes:

- Starting new processes and killing processes

- Displaying status information, such as the current process, all active processes, or all known processes

- Accessing and modifying the properties of a process (for example, its name or priority)

- Adding/Removing arrest and run reasons for a process

- Allowing a process to give up control to the scheduler or enter into a wait state

- Temporarily turning off the scheduler so that the current process cannot be interrupted

- Creating, locking, unlocking, and modifying process locks

## Implementation Architecture

The ILU CL Process Interface is implemented as a veneer over the existing process interfaces for a number of Common Lisp implementations (currently Franz Allegro CL and Lucid Common Lisp). In many cases, the implementation's functions are simply imported and then exported from the `ilu-process` package. In other cases, a new function is wrapped around the implementation's native function to change the name, arguments, or semantics of the function so that they match those required by the ILU CL Process Interface specification. In a few cases, whole new functions have been written to achieve functionality not provided by the original implementation.

The nature of the process object in the ILU CL Process Interface is not specified. The process object is inherited from the underlying implementation and may therefore be a list, a structure, a flavor object, or even a CLOS object. Because of this lack of specification, process objects cannot be specialized. Moreover, they cannot be accessed or modified in any way other than through the functional interface described in this chapter.

## General Limitations

The ILU CL Process Interface assumes that the scheduler is loaded and running in the ILU environment. Procedures for starting the scheduler are not included in the ILU CL Process Interface. Some implementations, however, may require you to actually load and start up the scheduler. For example, in Franz Allegro CL, you need to evaluate `(mp:start-scheduler)` either at the top-level or in your ``.clinit.cl'` file in order to load and start up the scheduler.

The ILU CL Process Interface is subject to all of the limitations of its underlying implementations. In particular, one problem with most Common Lisp implementations on stock hardware is that the smallest scheduler quantum possible is one second. This means that each process gets to run for one second uninterrupted. For applications that involve real-time response, waiting for one second before an event can be handled is problematic. In practice, this problem can be lessened if all processes release control to the scheduler at regular, short intervals (that is, each few times around a tight inner loop), thereby making the effective quantum significantly less than one second. Note that this practice effectively reduces the scheduler to a prioritized, cooperative scheduler rather than the preemptive scheduler intended.

Most Common Lisp implementations build their process mechanism on top of a very powerful mechanism called stack groups. Stack groups provide for alternative run-time stacks in the Lisp environment that can be used for various purposes beyond implementing processes. For example, stack groups are an ideal substrate for implementing co-routines. Unfortunately, not all implementations provide an interface to stack groups (if indeed they have stack groups). Hence, an interface to stack groups is not a part of the ILU CL Process Interface.

## How To Use The ILU CL Process Interface

The ILU CL Process Interface is intended as a programmer's interface; the functions and macros provided should be used to implement programs that run in the ILU environment. Although you can use any of the functions and macros directly from a Lisp listener, the interface is not designed particularly well for interactive use. The two exceptions to this rule are the functions `ilu-process:show-process` and `ilu-process:show-all-processes`, both of which are designed to print out status information in the Lisp listener window. Because it is a user-oriented function, `ilu-process:show-process` accepts either the process name or a process object to identify the process whose status is to be displayed.

Most implementations include an interactive interface to multiple processes and the scheduler. For example, Franz Allegro CL has a special top-level command language that is operative in every Lisp listener. This command language includes the following commands that deal specifically with lightweight processes (see Chapter 4 of [Franz-92] for more information):

- `:processes`

  Lists all processes (see `ilu-process:all-processes`)

- `:kill`

  Kills a process (see `ilu-process:process-kill`)

- `:arrest`

  Adds an arrest reason to a process (see `ilu-process:add-arrest-reason`)

- `:unarrest`

  Removes any arrest reason that was added to a process by `:arrest` (see `ilu-process:process-revoke-arrest-reason`)

- `:focus`

  Performs an `:arrest` on a process and arranges for all user keyboard input to be sent to the arrested process (usually to the debugger).

## How To Program The ILU CL Process Interface

The following example illustrates how to use the ILU CL Process Interface to define a shared FIFO queue. Two processes will utilize this queue. A producer process will read input items from the user and place them on the shared queue. A consumer process will wake up every five seconds and read items from the shared queue, printing them on the standard output stream as they are taken off the queue. Access to the shared queue will be controlled using a process lock associated with the queue.

    ;;;________________________________________________
    ;;; the shared queue, its process-lock, and its accessors/mutators
    ;;;

    (defvar queue (list t) "The shared queue")

    (defvar queue-lock (ilu-process:make-process-lock :name "queue lock")
      "process lock for queue")

    (defun queue-pop (queue)
      "Pop an item off of the shared FIFO queue.
       Use ilu-process:with-process-lock to prevent collisions between processes.
      "
      (ilu-process:with-process-lock (queue-lock)
        (prog1
    	(cadr queue)
          (rplacd queue (cddr queue)))
        ))

    (defun queue-push (queue item)
      "Push an item onto the shared FIFO queue.
       Use ilu-process:with-process-lock to prevent collisions between processes.
      "
      (ilu-process:with-process-lock (queue-lock)
        (nconc queue (list item))
        ))

    (defun queue-empty-p (queue)
      "Is queue empty?
       Use ilu-process:with-process-lock to prevent collisions between processes.
      "
      (ilu-process:with-process-lock (queue-lock) (null (cdr queue)) ))

    ;;;________________________________________________
    ;;;  The producer function
    ;;;

    (defun produce ()
      "Loop reading an item from the user and pushing it onto the shared queue."
      (let (Item)
        (loop
          ;; Wait until there is something on the input stream.
          (ilu-process:process-wait "Waiting for input" #'listen *standard-input*)

          ;; Read the input.
          (setq Item (read *standard-input*))

          ;; Check to see if it is the EOF marker and exit if so.
          (when (eq Item :EOF) (return nil))

          ;; Push the item onto the queue.
          (queue-push queue Item)
          )))

    ;;;________________________________________________
    ;;; The consumer function
    ;;;

    (defun consume ()
      "Wake up every five seconds and see if there is something on the shared
       queue.  If there is, pop it off and print it on standard output.
       If the queue is empty and the producer process is not alive, terminate.
      "
      (loop
        ;; Check to see if there is anything on the queue.
        (if (not (queue-empty-p queue))
    	;; There is an item on the queue; pop and print all items.
    	(do ()((queue-empty-p queue))
    	  (fresh-line t)
    	  (princ "Output: ")
    	  (prin1 (queue-pop queue))
    	  (fresh-line t)
    	  (finish-output t))

          ;; Queue is empty; check to see if the producer is still alive.
          (if (null (ilu-process:find-process "Producer Process"))

    	  ;; Producer not alive; terminate.
    	  (return nil)))

        ;; Sleep for five seconds; this gives up control immediately
        ;; so some other process can run.
        (sleep 5)
        ))

    ;;;________________________________________________
    ;;;   Main function; starts consumer and producer processes
    ;;;

    (defun test-queue ()
      "Start consumer and producer processes. Wait in an idle loop until
       both the producer and the consumer processes die.  This function is
       meant to be evaluated in the Lisp listener.  Waiting until both
       processes die ensures that the Lisp listener does not interfere
       with user input to the producer.
      "
      (let (Producer Consumer)
        ;; Start the producer first; the consumer needs the producer to run.
        (setq Producer (ilu-process:fork-process "Producer Process" #'produce))
        ;; Start the consumer.
        (setq Consumer (ilu-process:fork-process "Consumer Process" #'consume))
        ;; Show processes on the standard ouput.
        (ilu-process:show-all-processes)
        ;; Wait until both consumer and producer are dead.
        (ilu-process:process-wait "Waiting for godot"
    	#'(lambda (P1 P2)
    	    (not
    	     (or (ilu-process:process-alive-p P1)
    		 (ilu-process:process-alive-p P2))))
    	Consumer Producer)
        ))

The following is a transcript of this test program in operation:

    ;;;________________________________________________
    #73: (test-queue)
    -------------Data on all processes follows---------
    Process: "Consumer Process"
      Process-alive-p: T
      Process-active-p: T
      Process-quantum: 2
      Process-priority: 0
      Process-run-reasons: (:START :START)
    Process: "Producer Process"
      Process-alive-p: T
      Process-active-p: T
      Process-quantum: 2
      Process-priority: 0
      Process-run-reasons: (:START :START)
    Process: "Initial Lisp Listener"
      Process-alive-p: T
      Process-active-p: T
      Process-quantum: 2
      Process-priority: 0
      Process-run-reasons: (:ENABLE)
    123
    Output: 123
    456
    789
    Output: 456
    Output: 789
    444
    555
    666
    Output: 444
    Output: 555
    Output: 666
    :eof
    NIL
    #74:

## The ILU CL Process Interface

The following sections detail the functions and macros that make up the ILU CL Process Interface. All are assumed to be in the `ilu-process` package unless otherwise specified. Arguments are shown with their type, if they have any restrictions on their type. Return types are shown if the function returns a value. Optional arguments are shown with their type and their default value.

### The Process Object

The following listings describe the object that is used to represent each lightweight process.

Type: ilu-process:process

A Lisp object representing a single process. This object is to be used only as a handle for the process. To alter the state or characteristics of a process, use the external function interface defined below. The exact nature of the process object differs between implementations. In particular, it may or may not be a flavor or a CLOS object. Hence, it is not safe to specialize processes.

Function: find-process (NAME `string`) => `process`

Returns the process object whose name is NAME. Only `ilu-process:process-alive-p` processes (that is, processes on the list returned from `ilu-process:all-processes`) are searched. This function returns `nil` if there is no matching process.

Function: processp OBJECT => `boolean`

Returns non-`nil` if OBJECT is an object of type `process` for this implementation. This function returns `nil` otherwise.

### Querying The Status Of The Scheduler And All Processes

The following functions and macros provide status information about the general state of processes and the scheduler in the Lisp environment.

Macro: active-processes => `list`

Returns a list of all active processes; that is, processes that have at least one run reason and no arrest reasons. Note, however, that these processes are not necessarily runnable because they may be in a process-wait.

Macro: all-processes => `list`

Returns a list of all processes currently known by the scheduler, including active and inactive processes but not processes that have been killed.

Macro: current-process => `process`

Returns the process object for the current thread of control.

Function: show-all-processes &optional (STREAM `streamp` `cl:*standard-output*`) (VERBOSE `boolean` `nil`)

Displays information about all processes known by the scheduler (that is, the processes returned by `ilu-process:all-processes`). Output is to STREAM, which defaults to the value of `cl:*standard-output*`. This function shows only non-`nil` fields unless VERBOSE is non-`nil`; the default is `nil`.

### Starting And Killing Processes

Function: fork-process (NAME-OR-KEY-LIST `(or string proplist)` (FUNCTION `function`) &rest ARGS => `process`

Creates a new process and returns the `process` object for this process. In this process, FUNCTION is `apply`ed to ARGS. If FUNCTION ever returns, the process is automatically killed. The FUNCTION is known as the initial-function of the process (see `ilu-process:process-initial-form`).

The new process is activated by default, although you can create it in a deactivated state by giving it a run reasons list with a value of `nil` or by giving it one or more arrest reasons as detailed below.

NAME-OR-KEY-LIST is either a string, in which case it serves as the name of the process, or it is a property list with one or more of the following property-value pairs:

- `:name` (`string`)

  A string to be used as the name of the process.

- `:priority` (`integer`)

  Sets the priority of the process to the given value (see `ilu-process:process-priority`).

- `:quantum` (`(or numberp nil)`)

  Sets the quantum of the process to the given value (see `ilu-process:process-quantum`). Defaults to `1`.

- `:stack-size` (`fixnum`)

  Sets the stack-size of the process (if possible in this implementation).

- `:run-reasons` (`list`)

  Sets the run reasons of this process to the given list. Unless run-reasons is non-`nil`, the forked process does not run until a `ilu-process:process-add-run-reason` is done. This property defaults to `(quote (:start))`.

- `:arrest-reasons` (`list`)

  Sets the arrest reasons of this process to the given list. If arrest-reasons is non-`nil`, the forked process does not run until a `ilu-process:process-revoke-arrest-reason` is done. This property defaults to `nil`.

- `:bindings` (`list`)

  A list of bindings (as in let) that are done in the context of the forked process before the function is run. This property defaults to `ilu-process:*default-process-bindings*`.

Function: process-kill (PROCESS `process`)

Terminates PROCESS and removes it from the scheduler's consideration altogether. It is an error if PROCESS is not `ilu-process:processp` and `ilu-process:process-alive-p`.

A process may not terminate immediately. In particular, the process is first activated and scheduled. It is then forced to `throw` out of its initial-function, thereby properly unwinding and executing any unwind forms.

A killed process cannot be reactivated.

### Waiting A Process

Function: process-wait (WHOSTATE `string`) (FUNCTION `function`) &rest ARGS

The current process is suspended until FUNCTION applied to ARGS returns non-`nil`. During this time, the process's whostate (see `ilu-process:process-whostate`) is set to WHOSTATE.

Note that the current process is not deactivated. It is simply not scheduled to run until its wait-function returns non-`nil`. The scheduler re-evaluates the wait-function periodically. In general, the re-evaluation occurs whenever the waited process would be scheduled to run if it were not suspended. However, in some implementations it is run during every scheduler break.

### Activating And Deactivating Processes

Function: process-add-arrest-reason (PROCESS `process`) OBJECT

Adds OBJECT to the list of arrest reasons for PROCESS. The OBJECT argument can be any Lisp object. It is an error if PROCESS is not `ilu-process:processp`.

Adding an arrest reason may cause a process to become deactivated. In particular, if this is the first arrest reason, then the process becomes deactivated (if it was previously activated).

Function: process-add-run-reason (PROCESS `process`) OBJECT

Adds OBJECT to the list of run reasons for PROCESS. The OBJECT argument can be any Lisp object. It is an error if PROCESS is not `ilu-process:processp`.

Adding a run reason may cause a process to become activated. In particular, if there are no arrest reasons and the added run reason is first, the process goes from a deactivated state to an activated state.

Function: process-arrest-reasons (PROCESS `process`) => `list`

Returns the list of arrest reasons for PROCESS. It is an error if PROCESS is not `ilu-process:processp`.

Function: process-disable (PROCESS `process`)

Causes PROCESS to become inactive by removing all of its arrest reasons and all of its run reasons. It is an error if PROCESS is not `ilu-process:processp`.

Function: process-enable (PROCESS `process`)

Causes PROCESS to become active by removing all of its arrest reasons and all of its run reasons and then giving it a single run reason (usually `:enable`). It is an error if PROCESS is not `ilu-process:processp`.

Function: process-revoke-arrest-reason (PROCESS `process`) OBJECT

Removes OBJECT from the list of arrest reasons for PROCESS. It is an error if PROCESS is not `ilu-process:processp`. OBJECT is compared to the existing arrest reasons using an `eq` test.

Revoking an arrest reason may cause a process to become activated. In particular, when the last arrest reason for a process is removed, the process is (re-)activated if it has at least one run reason.

Function: process-revoke-run-reason (PROCESS `process`) OBJECT

Removes OBJECT from the list of run reasons for PROCESS. It is an error if PROCESS is not `ilu-process:processp`. The OBJECT argument is compared to the existing run reasons using an `eq` test.

Revoking a run reason may cause a process to become inactive. In particular, when the last run reason for a process is removed, the process is made inactive (if it was previously activate).

Function: process-run-reasons (PROCESS `process`)

Returns the list of run reasons for PROCESS. It is an error if PROCESS is not `ilu-process:processp`.

### Accessing And Modifying The Properties Of A Process

Function: process-active-p (PROCESS `process`) => `boolean`

Returns non-`nil` if PROCESS is an active process object; that is, a process with no arrest reasons and at least one run reason. Otherwise, this function returns `nil`. It is an error if PROCESS is not `ilu-process:processp`.

Function: process-alive-p (PROCESS `process`) => `boolean`

Returns non-`nil` if PROCESS is alive (that is, has been created but has not been killed). Essentially, a process is alive if it is on the list returned by `ilu-process:all-processes`. It is an error if PROCESS is not `ilu-process:processp`.

Function: process-initial-form (PROCESS `process`) => `consp`

Returns the initial-form of the `process` object PROCESS. It is an error if PROCESS is not `ilu-process:processp`. Note that the returned value is not an `eval`able form. It is merely the `cons` of the process's intial function onto a list of the initial arguments passed to the function. (See `ilu-process:fork-process`.)

Function: process-name (PROCESS `process`) => `string`

Returns the name of the `process` object PROCESS. It is an error if PROCESS is not `ilu-process:processp`. The `ilu-process:process-active-p` function can be used with `setf` to change the name of a process.

Function: process-priority (PROCESS `process`) => `integer`

Returns the scheduling priority for the `process` object PROCESS. It is an error if PROCESS is not `ilu-process:processp`. The `ilu-process:process-priority` function can be used with `setf` to change the priority of a process.

When the priorities are set, a small integer is generally used. Process priorities default to zero (0). Processes with higher priorities are given scheduling preference. Priorities can be negative if a process should run as a background task when nothing else is running.

Note that an implementation is free to ignore process priorities. Setting a process's priority is merely advisory. For this reason, the value returned by `ilu-process:process-priority` may not match the most recent `setf` on `ilu-process:process-priority`.

Function: process-quantum (PROCESS `process`) => `(or numberp nil)`

Returns the quantum, which is the amount of time the scheduler allows a process to run each time its is rescheduled, for the `process` object PROCESS. It is an error if PROCESS is not `ilu-process:processp`. The `ilu-process:process-quantum` function can be used with `setf` to change the quantum of a process.

The quantum is measured in seconds (not necessarily integral).

Note that an implementation is free to ignore process quantums. Setting a quantum is merely advisory. For this reason, the value returned by `ilu-process:process-quantum` may not match the most recent `setf` on `ilu-process:process-quantum`.

The default process quantum is 1.

Function: process-wait-args (PROCESS `process`) => `list`

Returns a list of the arguments being passed to the wait-function of the `process` object PROCESS. It is an error if PROCESS is not `ilu-process:processp`. (See `ilu-process:process-wait`.)

Function: process-wait-function (PROCESS `process`) => `(or functionp nil)`

Returns the wait-function of the `process` object PROCESS. It is an error if PROCESS is not `ilu-process:processp`. (See `ilu-process:process-wait`.)

Function: process-whostate (PROCESS `process`) => `string`

Returns the whostate of the `process` object PROCESS. It is an error if PROCESS is not `ilu-process:processp`. The `ilu-process:process-whostate` function can be used with `setf` to change the whostate of a process. (See also `ilu-process:fork-process` and `ilu-process:process-wait`.)

Function: show-process &optional (PROCESS `process`) (STREAM `streamp` `cl:*standard-output*`) (VERBOSE `boolean` `nil`)

Displays information about process PROCESS, which may be a `process` object or the name of a process known to the scheduler. If PROCESS is a symbol, it is downcased and converted to a string. PROCESS defaults to the current process. Output is is to STREAM, which defaults to the value of `cl:*standard-output*`. If VERBOSE is `nil` (defaults to non-`nil`), then only non-`nil` fields are displayed and the process's initial-form is not shown.

### Miscellaneous Process/Scheduler Functions And Macros

Function: process-allow-schedule

Suspends the current process and returns to the scheduler. All other processes of equal or higher priority have a chance to run before control returns to the current process.

Function: process-interrupt (PROCESS `process`) (FUNCTION `function`) &rest ARGS

Forces PROCESS to apply FUNCTION to ARGS when it is next scheduled. When FUNCTION returns, PROCESS resumes execution where it was interrupted.

In general, `ilu-process:process-interrupt` is run immediately (that is, when PROCESS is next scheduled) if PROCESS is active, even if PROCESS is a process-wait. If PROCESS is not active, `ilu-process:process-interrupt` may wait until PROCESS is reactivated before FUNCTION is executed.

Macro: without-scheduling &body BODY

Evaluates the forms in BODY with scheduling turned off. While the current-process is within the scope of `ilu-process:without-scheduling`, no other process will run. However, scheduling may be resumed if a `ilu-process:process-wait` or `ilu-process:process-allow-schedule` is executed within the scope of `ilu-process:without-scheduling`. Most Common Lisp I/O functions as well as the function `sleep` usually call some form of `ilu-process:process-allow-scheduling` and hence will resume scheduling if called within the scope of a `ilu-process:without-scheduling`.

### Process Locks Interface

Type: ilu-process:process-lock

The process lock object. You should access fields of this lock using only the functional interface listed in this section.

Function: make-process-lock &key (NAME `(or nil string)` `nil`) => `process-lock`

Creates and returns a `process-lock` object with NAME as the name of the lock.

Function: process-lock (LOCK `ilu-process:process-lock`) &optional (LOCK-VALUE `process` `(ilu-process:current-process)`) (WHOSTATE `(or nil string)` `<undefined>`)

Grabs LOCK, entering LOCK-VALUE as the lock's locker. LOCK-VALUE defaults to the current process. It is an error if LOCK is not `ilu-process:process-lock-p`.

If LOCK is already locked, then the current process waits until it is unlocked. The waiting is done using `ilu-process:process-wait`. The WHOSTATE argument is a string that is used as the whostate of the process if the process is forced to wait; defaults to an implementation-dependent string.

Function: process-lock-locker (LOCK `ilu-process:process-lock`) => `t`

Returns the current locker of LOCK. It is an error if LOCK is not `ilu-process:process-lock-p`. This function returns `nil` if LOCK is currently unlocked, that is, has no locker. This value is not setfable. You should use `ilu-process:process-lock` to set the locker.

Setf-able Function: process-lock-name (LOCK `ilu-process:process-lock`) => `(or nil string)`

Returns the name associated with LOCK. It is an error if LOCK is not `ilu-process:process-lock-p`. The `ilu-process:process-lock-locker` function can be used with `setf` to change the name of a process lock.

Function: process-lock-p OBJECT => `boolean`

Returns non-`nil` if OBJECT is a `ilu-process:process-lock`. Otherwise, this function returns `nil`.

Function: process-unlock (LOCK `ilu-process:process-lock`) &optional (LOCK-VALUE `t` `(ilu-process:current-process)`) (ERROR-P `boolean` `nil`)

Releases LOCK. It is an error if LOCK is not `ilu-process:process-lock-p`.

If LOCK's locker is not `eq` to LOCK-VALUE, which defaults to the current process, then an error is signalled unless ERROR-P is `nil` (it defaults to `t`).

Macro: with-process-lock (LOCK `ilu-process:process-lock`) &key (NORECURSIVE `boolean` `nil`) &body BODY

Locks LOCK for the current process and evaluates the forms in BODY. It is an error if LOCK is not `ilu-process:process-lock-p`.

If NORECURSIVE is `t` (the default), and if the current process already owns the lock (determined dynamically), then no action is taken. If NORECURSIVE is non-`nil`, then an error is signalled if the current process already owns LOCK.

If LOCK is held by another process, then the current process waits as in `ilu-process:process-lock`, which is described earlier in this section.

## Handling Errors

Errors in most of the process functions will cause a break. There are no special tricks to handling these errors.

The `:focus` command is an important tool for using the Allegro CL debugger in a multiple-process environment. In particular, in Allegro CL a new process by default shares its standard input/output (I/O) stream with the Lisp listener. Generally, this is not a problem because the process runs in the background and does no I/O. However, if the process enters a break, the debugger needs to use the process's standard I/O stream to interact with the user. This could lead to a problem because the debugger I/O from the broken process will be interleaved with the Lisp listener's normal I/O. Specifically, the system will not be able to determine to which process user input is directed.

To avoid this situation, Allegro CL has the notion of a focus process. Input coming from the shared Lisp listener I/O stream is always sent to the focused process. Usually this is the Lisp listener process. However, if a background process breaks, you can use the `:focus` command to focus on the broken process and allow you to send input to the debugger running in that process. When the debugging is complete, `:focus` is automatically returned to the Lisp listener process.

The following transcript illustrates the use of the `:focus` command in Allegro CL:

    ;;;_______________________________________________________________________
    ;;; Start out focused on the Lisp listener process. List all processes.
    <cl 71> :processes
    "Initial Lisp Listener" is active.
    ;;;_______________________________________________________________________
    ;;; Second, start a test process that will enter a break immediately.
    <cl 72> (ilu-process:fork-process "test" #'error "test break")
    #<process test  #x13e92b1>
    <cl 73>
    ;;;________________________________________________
    ;;; Process test enters a break.

    Error: test break

    ;;; Still speaking to the Lisp listener process, list the processes.
    [1] <cl 1> :processes
    "test" is waiting for terminal input.
    "Initial Lisp Listener" is active.
    <cl 74>
    ;;;________________________________________________
    ;;; Now refocus on the test process.
    <cl 74> :focus "test"
    Focus is now on the "test" process.
    ;;;  Look at stack of test process.
    <cl 75> :zoom
    Evaluation stack of process "test":
     ->(EXCL::STM-SY-READ-CHAR #<synonym stream for *TERMINAL-IO*  #x13e9619>)
       (PEEK-CHAR NIL #<synonym stream for *TERMINAL-IO*  #x13e9619> NIL :EOF NIL)
       (ERROR "test break")
       (ILU-PROCESS::PROCESS-INITIALIZATION-FUNCTION NIL
          #<Function ERROR  #x219ab9> ("test break"))
    ;;;________________________________________________
    ;;; Kill the test process (which is the current process).
    <cl 76> :kill
    Do you really want to kill process "test" [n]? y

    ;;; Automatic refocus to Lisp listener. Ask listener to list all processes.
    Focus is now on the "listener" process.
    <cl 77> :processes
    "Initial Lisp Listener" is active.

For more information on the Lisp listener interface and the Lisp debugger, see the manual for the implementation of Common Lisp that you are using. For Allegro CL, refer to chapters 4 and 5 of [Franz-92].

## Notes

It is possible for a process to do a non-blocking attempt to lock a process lock using the following idiom:

     (ilu-process:without-scheduling    ; Make sure this is not interrupted.
      (if (ilu-process:process-lock-locker LOCK) ; Is lock free?
          (ilu-process:process-lock LOCK)))      ; Lock is free, grab it.
     (if (eq                            ; Did we get the lock for this process?
          (ilu-process:process-lock-locker LOCK)
          (ilu-process:current-process))
         (prog1                         ; Yes, do A, releasing lock on way out.
           ...A...
           (ilu-process:process-unlock LOCK))
       ...B...                          ; No, do B, which does not depend on lock.
       )

## References

[Franz-92]: Allegro Common Lisp User Guide. Release 4.1. Berkeley, CA: Franz Incorporated, March 1992.

Bach, M.J., The Design of the UNIX Operating System. Englewood Cliffs, NJ: Prentice-Hall, 1986. Especially read Chapters 6, 7, and 8.

Deitel, H.M. An Introduction to Operating Systems. Reading, MA: Addison-Wesley, 1984. Especially read Part 2.

Kernighan, B.W. and R. Pike., The UNIX Programming Environment. Englewood Cliffs, NJ: Prentice-Hall, 1984. Especially read Sections 1.4 and 7.4.

Tanenbaum, A.S. Operating Systems: Design and Implementation. Englewood Cliffs, NJ: Prentice-Hall, 1987. Especially read Chapter 2.

Go to the previous, next section.