;;; -*- Mode: Lisp; Package: CCL; Coding: utf-8; -*-

(chapter "Programming with Threads"
  (defsection "Threads Overview"
    #:|{CCL} provides facilities which enable multiple threads
      of execution ({emphasis threads}, sometimes called
      {emphasis lightweight processes} or just
      {emphasis processes}, though the latter term shouldn't
      be confused with the OS's notion of a process) within a lisp
      session. This document describes those facilities and issues
      related to multithreaded programming in {CCL}.

      Wherever possible, I'll try to use the term "thread" to
      denote a lisp thread, even though many of the functions in the
      API have the word "process" in their name. A
      {emphasis lisp-process} is a lisp object (of type
      CCL:PROCESS) which is used to control and communicate with an
      underlying {emphasis native thread}. Sometimes, the
      distinction between these two (quite different) objects can be
      blurred; other times, it's important to maintain.

      Lisp threads share the same address space, but maintain
      their own execution context (stacks and registers) and their own
      dynamic binding context.

      Traditionally, {CCL}'s threads have been
      {emphasis cooperatively scheduled}: through a
      combination of compiler and runtime support, the currently
      executing lisp thread arranged to be interrupted at certain
      discrete points in its execution (typically on entry to a
      function and at the beginning of any looping construct). This
      interrupt occurred several dozen times per second; in response,
      a handler function might observe that the current thread had
      used up its time slice and another function ({emphasis the lisp
        scheduler}) would be called to find some other thread
      that was in a runnable state, suspend execution of the current
      thread, and resume execution of the newly executed thread.  The
      process of switching contexts between the outgoing and incoming
      threads happened in some mixture of Lisp and assembly language
      code; as far as the OS was concerned, there was one native
      thread running in the Lisp image and its stack pointer and other
      registers just happened to change from time to time.

      Under {CCL}'s cooperative scheduling model, it was
      possible (via the use of the CCL:WITHOUT-INTERRUPTS construct)
      to defer handling of the periodic interrupt that invoked the
      lisp scheduler; it was not uncommon to use WITHOUT-INTERRUPTS to
      gain safe, exclusive access to global data structures. In some
      code (including much of {CCL} itself) this idiom was very
      common: it was (justifiably) believed to be an efficient way of
      inhibiting the execution of other threads for a short period of
      time.

      The timer interrupt that drove the cooperative scheduler
      was only able to (pseudo-)preempt lisp code: if any thread
      called a blocking OS I/O function, no other thread could be
      scheduled until that thread resumed execution of lisp code. Lisp
      library functions were generally attuned to this constraint, and
      did a complicated mixture of polling and "timed blocking" in an
      attempt to work around it. Needless to say, this code is
      complicated and less efficient than it might be; it meant that
      the lisp was a little busier than it should have been when it
      was "doing nothing" (waiting for I/O to be possible.)

      For a variety of reasons - better utilization of CPU
      resources on single and multiprocessor systems and better
      integration with the OS in general - threads in {CCL} 0.14 and
      later are {emphasis preemptively scheduled. }In this
      model, lisp threads are native threads and all scheduling
      decisions involving them are made by the OS kernel. (Those
      decisions might involve scheduling multiple lisp threads
      simultaneously on multiple processors on SMP systems.) This
      change has a number of subtle effects:|
    (listing :bullet
      (item "it is possible for two (or more) lisp threads to be
	      executing simultaneously, possibly trying to access and/or
	      modify the same data structures. Such access really should
	      have been coordinated through the use of synchronization
	      objects regardless of the scheduling modeling effect;
	      preemptively scheduled threads increase the chance of things
	      going wrong at the wrong time and do not offer
	      lightweight alternatives to the use of those synchronization
	      objects.")
      (item "even on a single-processor system, a context switch
	      can happen on any instruction boundary. Since (in general)
	      other threads might allocate memory, this means that a GC can
	      effectively take place at any instruction boundary. That's
	      mostly an issue for the compiler and runtime system to be
	      aware of, but it means that certain practices(such as trying
	      to pass the address of a lisp object to foreign code)that
	      were always discouraged are now discouraged
	      ... vehemently.")
      (item #:|there is no simple and efficient way to "inhibit the
	      scheduler"or otherwise gain exclusive access to the entire
	      CPU.|)
      (item "There are a variety of simple and efficient ways
	      to synchronize access to particular data
	      structures."))
    "As a broad generalization: code that's been aggressively
      tuned to the constraints of the cooperative scheduler may need
      to be redesigned to work well with the preemptive scheduler (and
      code written to run under {CCL}'s interface to the native
      scheduler may be less portable to other CL implementations, many
      of which offer a cooperative scheduler and an API similar to
      {CCL} (< 0.14) 's.) At the same time, there's a large
      overlap in functionality in the two scheduling models, and it'll
      hopefully be possible to write interesting and useful MP code
      that's largely independent of the underlying scheduling
      details.

      The keyword :OPENMCL-NATIVE-THREADS is on *FEATURES* in
      0.14 and later and can be used for conditionalization where
      required.")
  (defsection "(Intentionally) Missing Functionality"
    (para "Much of the functionality described above is similar to
      that provided by {CCL}'s cooperative scheduler, some other
      parts of which make no sense in a native threads
      implementation.")
    (listing :bullet
      (item #:|PROCESS-RUN-REASONS and PROCESS-ARREST-REASONS were
	      SETFable process attributes; each was just a list of
	      arbitrary tokens. A thread was eligible for scheduling
	      (roughly equivalent to being "enabled") if its arrest-reasons
	      list was empty and its run-reasons list was not. I don't
	      think that it's appropriate to encourage a programming style
	      in which otherwise runnable threads are enabled and disabled
	      on a regular basis (it's preferable for threads to wait for
	      some sort of synchronization event to occur if they can't
	      occupy their time productively.)|)
      (item "There were a number of primitives for maintaining
	      process queues;that's now the OS's job.")
      (item "Cooperative threads were based on coroutining
	      primitives associated with objects of type
	      STACK-GROUP. STACK-GROUPs no longerexist.")))
  (defsection "Implementation Decisions and Open Questions"
    (defsection "Thread Stack Sizes"
      #:|When you use MAKE-PROCESS to create a thread, you can
        specify a stack size. {CCL} does not impose a limit on the stack
        size you choose, but there is some evidence that choosing a
        stack size larger than the operating system's limit can cause
        excessive paging activity, at least on some operating
        systems.

        The maximum stack size is operating-system-dependent. You
        can use shell commands to determine what it is on your
        platform. In bash, use "ulimit -s -H" to find the limit; in
        tcsh, use "limit -h s".

        This issue does not affect programs that create threads
        using the default stack size, which you can do either by
        specifying no value for the :stack-size argument to
        MAKE-PROCESS, or by specifying the value
        CCL::*default-control-stack-size*.

        If your program creates threads with a specified stack size,
        and that size is larger than the OS-specified limit, you may want
        to consider reducing the stack size in order to avoid possible
        excessive paging activity.|)
    (defsection " As of August 2003:"
      (listing :bullet
        (item "It's not clear that exposing
	        PROCESS-SUSPEND/PROCESS-RESUME is a good idea: it's not clear
	        that they offer ways to win, and it's clear that they offer
	        ways to lose.")
        (item #:|It has traditionally been possible to reset and enable
	        a process that's "exhausted" . (As used here, the
	        term "exhausted" means that the process's initial function
	        has run and returned and the underlying native thread has
	        been deallocated.) One of the principal uses of PROCESS-RESET
	        is to "recycle" threads; enabling an exhausted process
	        involves creating a new native thread (and stacks and
	        synchronization objects and ...),and this is the sort of
	        overhead that such a recycling scheme is seeking to avoid. It
	        might be worth trying to tighten things up and declare that
	        it's an error to apply PROCESS-ENABLE to an exhausted thread
	        (and to make PROCESS-ENABLE detect this error.)|)
        (item #:|When native threads that aren't created by {CCL}
	        first call into lisp, a "foreign process" is created, and
	        that process is given its own set of initial bindings and set
	        up to look mostly like a process that had been created by
	        MAKE-PROCESS. The life cycle of a foreign process is
	        certainly different from that of a lisp-created one: it
	        doesn't make sense to reset/preset/enable a foreign process,
	        and attempts to perform these operations should be
	        detected and treated as errors.|))))
  (defsection "Porting Code from the Old Thread Model"
    #:|Older versions of {CCL} used what are often called
      "user-mode threads", a less versatile threading model which does
      not require specific support from the operating system.  This
      section discusses how to port code which was written for that
      mode.

      It's hard to give step-by-step instructions; there are certainly
      a few things that one should look at carefully:|
    (listing :bullet
      (item "It's wise to be suspicious of most uses
	      of WITHOUT-INTERRUPTS; there may be exceptions, but
	      WITHOUT-INTERRUPTS is often used as shorthand for
	      WITH-APPROPRIATE-LOCKING. Determining what type of locking
	      is appropriate and writing the code to implement it is
	      likely to be straightforward and simple most of the
	      time.")
      (item #:|I've only seen one case where a process's "run reasons"
	      were used to communicate information as well as to control
	      execution; I don't think that this is a common idiom, but may
	      be mistaken about that.
	    |)
      (item "It's certainly possible that programs written
	      for cooperatively scheduled lisps that have run reliably for
	      a long time have done so by accident: resource-contention
	      issues tend to be timing-sensitive, and decoupling thread
	      scheduling from lisp program execution affects timing. I know
	      that there is or was code in both {CCL} and commercial MCL
	      that was written under the explicit assumption that certain
	      sequences of open-coded operations were uninterruptable; it's
	      certainly possible that the same assumptions have been made
	      (explicitly or otherwise) by application developers.")))
  (defsection "Background Terminal Input"
    (defsection "Overview"
      #:|
	    Unless and until {CCL} provides alternatives (via window
	    streams, telnet streams, or some other mechanism) all lisp
	    processes share a common *TERMINAL-IO* stream (and therefore
	    share *DEBUG-IO*, *QUERY-IO*, and other standard and
	    internal interactive streams.)

	    It's anticipated that most lisp processes other than
	    the "Initial" process run mostly in the background. If a
	    background process writes to the output side of
	    *TERMINAL-IO*, that may be a little messy and a little
	    confusing to the user, but it shouldn't really be
	    catastrophic. All I/O to {CCL}'s buffered streams goes
	    thru a locking mechanism that prevents the worst kinds of
	    resource-contention problems.

	    Although the problems associated with terminal output
	    from multiple processes may be mostly cosmetic, the question
	    of which process receives input from the terminal is likely
	    to be a great deal more important. The stream locking
	    mechanisms can make a confusing situation even worse:
	    competing processes may "steal" terminal input from each
	    other unless locks are held longer than they otherwise need
	    to be, and locks can be held longer than they need to be (as
	    when a process is merely waiting for input to become
	    available on an underlying file descriptor).

	    Even if background processes rarely need to
	    intentionally read input from the terminal, they may still
	    need to do so in response to errors or other unanticipated
	    situations. There are tradeoffs involved in any solution to
	    this problem. The protocol described below allows background
	    processes which follow it to reliably prompt for and receive
	    terminal input. Background processes which attempt to
	    receive terminal input without following this protocol will
	    likely hang indefinitely while attempting to do so. That's
	    certainly a harsh tradeoff, but since attempts to read
	    terminal input without following this protocol only worked
	    some of the time anyway, it doesn't seem to be an
	    unreasonable one.

	    In the solution described here (and introduced in
	    {CCL} 0.9), the internal stream used to provide terminal
	    input is always locked by some process (the "owning"
	    process.) The initial process (the process that typically
	    runs the read-eval-print loop) owns that stream when it's
	    first created. By using the macro WITH-TERMINAL-INPUT,
	    background processes can temporarily obtain ownership of the
	    terminal and relinquish ownership to the previous owner when
	    they're done with it.

	    In {CCL}, BREAK, ERROR, CERROR, Y-OR-N-P,
	    YES-OR-NO-P, and CCL:GET-STRING- FROM-USER are all defined
	    in terms of WITH-TERMINAL-INPUT, as are the :TTY
	    user-interfaces to STEP and INSPECT.|)
    (defsection "An example"
      (code-block #:|
? Welcome to {CCL} Version (Beta: linux) 0.9!
?

? (process-run-function "sleeper" #'(lambda () (sleep 5) (break "broken")))
#<PROCESS sleeper(1) [Enabled] #x3063B33E>

?
;;
;; Process sleeper(1) needs access to terminal input.
;;
      |)
      (para #:|This example was run under ILISP; ILISP often gets confused if one
	    tries to enter input and "point" doesn't follow a prompt.
	    Entering a "simple" expression at this point gets it back in
	    synch; that's otherwise not relevant to this example.|)
      (code-block #:|
()
NIL
? (:y 1)
;;
;; process sleeper(1) now controls terminal input
;;
> Break in process sleeper(1): broken
> While executing: #<Anonymous Function #x3063B276>
> Type :GO to continue, :POP to abort.
> If continued: Return from BREAK.
Type :? for other options.
1 > :b
(30C38E30) : 0 "Anonymous Function #x3063B276" 52
(30C38E40) : 1 "Anonymous Function #x304984A6" 376
(30C38E90) : 2 "RUN-PROCESS-INITIAL-FORM" 340
(30C38EE0) : 3 "%RUN-STACK-GROUP-FUNCTION" 768
1 > :pop
;;
;; control of terminal input restored to process Initial(0)
;;
?
      |))
    (defsection "A more elaborate example."
      (para #:|If a background process ("A") needs access to the terminal
	    input stream and that stream is owned by another background process
	    ("B"), process "A" announces that fact, then waits until
	    the initial process regains control.|)
      (code-block #:|
? Welcome to {CCL} Version (Beta: linux) 0.9!
?

? (process-run-function "sleep-60" #'(lambda () (sleep 60) (break "Huh?")))
#<PROCESS sleep-60(1) [Enabled] #x3063BF26>

? (process-run-function "sleep-5" #'(lambda () (sleep 5) (break "quicker")))
#<PROCESS sleep-5(2) [Enabled] #x3063D0A6>

?       ;;
;; Process sleep-5(2) needs access to terminal input.
;;
()
NIL

? (:y 2)
;;
;; process sleep-5(2) now controls terminal input
;;
> Break in process sleep-5(2): quicker
> While executing: #x3063CFDE>
> Type :GO to continue, :POP to abort.
> If continued: Return from BREAK.
Type :? for other options.
1 >     ;; Process sleep-60(1) will need terminal access when
;; the initial process regains control of it.
;;
()
NIL
1 > :pop
;;
;; Process sleep-60(1) needs access to terminal input.
;;
;;
;; control of terminal input restored to process Initial(0)
;;

? (:y 1)
;;
;; process sleep-60(1) now controls terminal input
;;
> Break in process sleep-60(1): Huh?
> While executing: #x3063BE5E>
> Type :GO to continue, :POP to abort.
> If continued: Return from BREAK.
Type :? for other options.
1 > :pop
;;
;; control of terminal input restored to process Initial(0)
;;

?
      |))
    (defsection "Summary"
      "This scheme is certainly not bulletproof: imaginative
	    use of PROCESS-INTERRUPT and similar functions might be able
	    to defeat it and deadlock the lisp, and any scenario where
	    several background processes are clamoring for access to the
	    shared terminal input stream at the same time is likely to be
	    confusing and chaotic. (An alternate scheme, where the input
	    focus was magically granted to whatever thread the user was
	    thinking about, was considered and rejected due to technical
	    limitations.)

	    The longer-term fix would probably involve using network or
	    window-system streams to give each process unique instances of
	    *TERMINAL-IO*.

        Existing code that attempts to read from *TERMINAL-IO*
        from a background process will need to be changed to use
        WITH-TERMINAL-INPUT.  Since that code was probably not working
        reliably in previous versions of {CCL}, this requirement
        doesn't seem to be too onerous.

        Note that WITH-TERMINAL-INPUT both requests ownership of
        the terminal input stream and promises to restore that
        ownership to the initial process when it's done with it. An ad
        hoc use of READ or READ-CHAR doesn't make this promise; this
        is the rationale for the restriction on the :Y command."))
  (defsection "The Threads which {CCL} Uses for Its Own Purposes"
    (para #:|
      In the "tty world", {CCL} starts out with 2 lisp-level threads:|)
    (code-block "
? :proc
1 : -> listener     [Active]
0 :    Initial      [Active]
    ")
    #:|If you look at a running {CCL} with a debugging tool,
      such as GDB, or Apple's Thread Viewer.app, you'll see an
      additional kernel-level thread on Darwin; this is used by the
      Mach exception-handling mechanism.

      The initial thread, conveniently named "initial", is the
      one that was created by the operating system when it launched
      {CCL}.  It maps the heap image into memory, does some
      Lisp-level initialization, and, when the Cocoa IDE isn't being
      used, creates the thread "listener", which runs the top-level
      loop that reads input, evaluates it, and prints the
      result.

      After the listener thread is created, the initial thread
      does "housekeeping": it sits in a loop, sleeping most of the
      time and waking up occasionally to do "periodic tasks".  These
      tasks include forcing output on specified interactive streams,
      checking for and handling control-C interrupts, etc.  Currently,
      those tasks also include polling for the exit status of external
      processes and handling some kinds of I/O to and from those
      processes.

      In this environment, the initial thread does these
      "housekeeping" activities as necessary, until
      {code ccl:quit} is called;
      {code quit}ting interrupts the initial thread, which
      then ends all other threads in as orderly a fashion as possible
      and calls the C function {code #_exit}.

      The short-term plan is to handle each external-process in
      a dedicated thread; the worst-case behavior of the current
      scheme can involve busy-waiting and excessive CPU utilization
      while waiting for an external process to terminate in some
      cases.

      The Cocoa features use more threads.  Adding a Cocoa
      listener creates two threads:|
    (code-block "
      ? :proc
      3 : -> Listener     [Active]
      2 :    housekeeping  [Active]
      1 :    listener     [Active]
      0 :    Initial      [Active]
    ")
    #:|The Cocoa event loop has to run in the initial thread;
      when the event loop starts up, it creates a new thread to do the
      "housekeeping" tasks which the initial thread would do in the
      terminal-only mode.  The initial thread then becomes the one to
      receive all Cocoa events from the window server; it's the only
      thread which can.

      It also creates one "Listener" (capital-L) thread for each
      listener window, with a lifetime that lasts as long as the
      thread does.  So, if you open a second listener, you'll see five
      threads all together:|
    (code-block "
      ? :proc
      4 : -> Listener-2   [Active]
      3 :    Listener     [Active]
      2 :    housekeeping  [Active]
      1 :    listener     [Active]
      0 :    Initial      [Active]
    ")
    #:|Unix signals, such as SIGINT (control-C), invoke a handler
      installed by the Lisp kernel.  Although the OS doesn't make any
      specific guarantee about which thread will receive the signal,
      in practice, it seems to be the initial thread.  The handler
      just sets a flag and returns; the housekeeping thread (which may
      be the initial thread, if Cocoa's not being used) will check for
      the flag and take whatever action is appropriate to the
      signal.

      In the case of SIGINT, the action is to enter a break
      loop, by calling on the thread being interrupted.  When there's
      more than one Lisp listener active, it's not always clear what
      thread that should be, since it really depends on the user's
      intentions, which there's no way to divine programmatically.  To
      make its best guess, the handler first checks whether the value
      of {code ccl:*interactive-abort-process*} is a
      thread, and, if so, uses it.  If that fails, it chooses the
      thread which currently "owns" the default terminal input stream;
      see .

      In the bleeding-edge version of the Cocoa support which is
      based on Hemlock, an Emacs-like editor, each editor window has a
      dedicated thread associated with it.  When a keypress event
      comes in which affects that specific window the initial thread
      sends it to the window's dedicated thread.  The dedicated thread
      is responsible for trying to interpret keypresses as Hemlock
      commands, applying those commands to the active buffer; it
      repeats this in a loop, until the window closes.  The initial
      thread handles all other events, such as mouse clicks and
      drags.

      This thread-per-window scheme makes many things simpler,
      including the process of entering a "recursive command loop" in
      commands like "Incremental Search Forward", etc.  (It might be
      possible to handle all Hemlock commands in the Cocoa event
      thread, but these "recursive command loops" would have to
      maintain a lot of context/state information; threads are a
      straightforward way of maintaining that information.)

      Currently (August 2004), when a dedicated thread needs to
      alter the contents of the buffer or the selection, it does so by
      invoking methods in the initial thread, for synchronization
      purposes, but this is probably overkill and will likely be
      replaced by a more efficient scheme in the future.

      The per-window thread could probably take more
      responsibility for drawing and handling the screen than it
      currently does; -something- needs to be done to buffer screen
      updates a bit better in some cases: you don't need to see
      everything that happens during something like indentation; you
      do need to see the results...

      When Hemlock is being used, listener windows are editor
      windows, so in addition to each "Listener" thread, you should
      also see a thread which handles Hemlock command
      processing.

      The Cocoa runtime may make additional threads in certain
      special situations; these threads usually don't run lisp code,
      and rarely if ever run much of it.|)

  (defsection "Threads Dictionary"
    (definition (:function all-processes) "all-processes" nil
      "Returns a fresh list of all lisp processes (threads) known to
       {CCL} as of the precise instant it's called.  Since other
       threads can create and kill threads at any time, there's
       no way to get a perfectly accurate list of all threads.")

    (definition (:function make-process)
     "make-process name &key persistent priority class initargs stack-size vstack-size tstack-size initial-bindings use-standard-initial-bindings"
     "Creates and returns a new process."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param name}" ccldoc::=> "a string, used to identify the process.")
         (item "{param persistent}" ccldoc::=> "if true, requests that information about the process
		        be retained by SAVE-APPLICATION so that an equivalent
		        process can be restarted when a saved image is run.  The
		        default is nil.")
         (item "{param priority}" ccldoc::=> "ignored.  It
		        shouldn't be ignored of course, but there are
		        complications on some platforms.  The default is 0.")
         (item "{param class}" ccldoc::=> "the class of process object to create;
		        should be a subclass of CCL:PROCESS.  The default is
		        CCL:PROCESS.")
         (item "{param initargs}" ccldoc::=> "Any additional initargs to pass to MAKE-INSTANCE. The default is ().")
         (item "{param stack-size}" ccldoc::=> "the size, in bytes, of the newly-created process's
		        control stack; used for foreign function calls and to save
		        function return address context.  The default is
		        CCL:*DEFAULT-CONTROL-STACK-SIZE*.")
         (item "{param vstack-size}" ccldoc::=> "the size, in bytes, of the newly-created process's
		        value stack; used for lisp function arguments, local
		        variables, and other stack-allocated lisp objects.
		        The default is CCL:*DEFAULT-VALUE-STACK-SIZE*.")
         (item "{param tstack-size}" ccldoc::=> "the size, in bytes, of the newly-created process's
		        temp stack; used for the allocation of dynamic-extent
		        objects.  The default is CCL:*DEFAULT-TEMP-STACK-SIZE*.")
         (item "{param use-standard-initial-bindings}" ccldoc::=> #:|when true, the global "standard initial
		        bindings" are put into effect in the new thread before. See
		        DEF-STANDARD-INITIAL-BINDING.  "standard" initial bindings
		        are put into effect before any bindings specified by
		        :initial-bindings are.  The default is t.

                       {emphasis This option is deprecated: the correct
                      behavior of many {CCL} components depends on thread-local
                      bindings of many special variables being in effect.}|)
         (item "{param initial-bindings}" ccldoc::=> "an alist of ({param symbol} .
		        {param valueform}) pairs, which can be
		        used to initialize special variable bindings in the new
		        thread. Each {param valueform} is used to
		        compute the value of a new binding of
		        {param symbol} in the execution environment of
		        the newly-created thread.  The default is nil.")
         (item "{param process}" ccldoc::=> "the newly-created process.")))
     (defsection "Description"
       (para "Creates and returns a new lisp process (thread) with the
	      specified attributes. {param process} will not begin
	      execution immediately; it will need to be
	      {emphasis preset} (given
	      an initial function to run, as by
	      "
        (ref (definition :function process-preset)) ") and
	      {emphasis enabled}
	      (allowed to execute, as by "
        (ref (definition :function process-enable)) ")
	      before it's able to actually do anything.")
       "If {param valueform} is a function, it is
	      called, with no arguments, in the execution environment of the
	      newly-created thread; the primary value it returns is used for
	      the binding of the corresponding {param symbol}.

	      Otherwise, {param valueform} is evaluated in the
	      execution
	      environment of the newly-created thread, and the resulting value
	      is used.")
     (defsection "See Also"
       (clause (ref (definition :function process-preset)) ", " (ref (definition :function process-enable)) ", "
        (ref (definition :function process-run-function)))))
    (definition (:function process-suspend) "process-suspend process" "Suspends a specified process."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param process}" ccldoc::=> "a lisp process (thread).")
         (item "{param result}" ccldoc::=> "T if {param process} had been runnable
		        and is now suspended; NIL otherwise.  That is, T if
		        {param process}'s
		        "
          (ref (definition :function process-suspend-count)) "
		        transitioned from 0 to 1.")))
     (defsection "Description"
       (para "Suspends {param process}, preventing it from
	      running, and stopping it if it was already running. This is a fairly
	      expensive operation, because it involves a few
	      calls to the OS.  It also risks creating deadlock if used
	      improperly, for instance, if the process being suspended owns a
	      lock or other resource which another process will wait for.")
       (para "
	      Each
	      call to {function process-suspend} must be reversed by
	      a matching call to "
        (ref (definition :function process-resume)) "
	      before {param process} is able to run.  What
	      {function process-suspend} actually does is increment
	      the "
        (ref (definition :function process-suspend-count)) " of
	      {param process}.
	    ")
       (para "A process can't suspend itself, though this once
	    worked and this documentation claimed has claimed that it
	    did."))
     (defsection "See Also"
       (clause (ref (definition :function process-resume)) ", " (ref (definition :function process-suspend-count))))
     (defsection "Notes"
       (para "{function process-suspend} was previously called
	      {code process-disable}.
	      "
        (ref (definition :function process-enable)) "
	      now names a function for which there is no
	      obvious inverse, so {code process-disable}
	      is no longer
	      defined.")))
    (definition (:function process-resume) "process-resume process"
     "Resumes a specified process which had previously
	      been suspended by process-suspend."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param process}" ccldoc::=> "a lisp process (thread).")
         (item "{param result}" ccldoc::=> "T if {param process} had been suspended
		        and is now runnable; NIL otherwise.  That is, T if
		        {param process}'s
		        "
          (ref (definition :function process-suspend-count)) "
		        transitioned from  to 0.
		      ")))
     (defsection "Description"
       (para "Undoes the effect of a previous call to
	      "
        (ref (definition :function process-suspend)) "; if
	      all such calls are undone, makes the process runnable. Has no
	      effect if the process is not suspended.  What
	      {function process-resume} actually does is decrement
	      the "
        (ref (definition :function process-suspend-count)) " of
	      {param process}, to a minimum of 0."))
     (defsection "See Also"
       (clause (ref (definition :function process-suspend)) ", " (ref (definition :function process-suspend-count))))
     (defsection "Notes"
       (para "
	      This was previously called PROCESS-ENABLE;
	      "
        (ref (definition :function process-enable)) " now does something slightly
	      different.
	    ")))
    (definition (:function process-suspend-count) "process-suspend-count process"
     "Returns the number of currently-pending suspensions
	      applicable to a given process."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param process}" ccldoc::=> "a lisp process (thread).")
         (item "{param result}" ccldoc::=> #:|The number of "outstanding"
		        |
          (ref (definition :function process-suspend)) " calls on
		        {param process}, or NIL if
		        {param process} has expired.
		      ")))
     (defsection "Description"
       (para #:|An "outstanding" | (ref (definition :function process-suspend)) " call
	      is one which has not yet been reversed by a call to
	      "
        (ref (definition :function process-resume)) ".  A process expires when
	      its initial function returns, although it may later be
	      reset.")
       (para "A process is {emphasis runnable} when it has a
	      {function process-suspend-count} of 0, has been
	      preset as by "
        (ref (definition :function process-preset)) ", and has been
	      enabled as by "
        (ref (definition :function process-enable)) ".  Newly-created
	      processes have a {function process-suspend-count} of
	      0."))
     (defsection "See Also"
       (clause (ref (definition :function process-suspend)) ", " (ref (definition :function process-resume)))))
    (definition (:function process-preset) "process-preset process function {code &rest} args"
     "Sets the initial function and arguments of a specified
	      process."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param process}" ccldoc::=> "a lisp process (thread).")
         (item "{param function}" ccldoc::=> "a function, designated by itself or by a symbol
		        which names it.
		      ")
         (item "{param args}" ccldoc::=> "a list of values, appropriate as arguments to
		        {param function}.")
         (item "{param result}" ccldoc::=> "undefined.")))
     (defsection "Description"
       (para "Typically used to initialize a newly-created or newly-reset
	      process, setting things up so that when {param process}
	      becomes enabled, it will begin execution by
	      applying {param function} to {param args}.
	      {function process-preset} does not enable
	      {param process},
	      although a process must be {function process-preset}
	      before it can be enabled.  Processes are normally enabled by
	      "
        (ref (definition :function process-enable)) ".
	    "))
     (defsection "See Also"
       (clause (ref (definition :function make-process)) ", " (ref (definition :function process-enable)) ", "
        (ref (definition :function process-run-function)))))
    (definition (:function process-enable) "process-enable process {code &optional} timeout"
     "Begins executing the initial function of a specified
	      process."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param process}" ccldoc::=> "a lisp process (thread).")
         (item "{param timeout}" ccldoc::=> "a time interval in seconds.  May be any
		        non-negative real number the {function floor} of
		        which fits in 32 bits.  The default is 1.")
         (item "{param result}" ccldoc::=> "undefined.")))
     (defsection "Description"
       (para "Tries to begin the execution of {param process}.
	      An error is signaled if {param process} has never
	      been "
        (ref (definition :function process-preset)) ".  Otherwise,
	      {param process} invokes its initial function.
	    ")
       "{function process-enable} attempts to
	      synchronize with {param process}, which is presumed
	      to be reset or in the act of resetting itself.  If this attempt
	      is not successful within the time interval specified by
	      {param timeout}, a continuable error is signaled,
	      which offers the opportunity to continue waiting.
	    

A process cannot meaningfully attempt to enable itself.")
     (defsection "See Also"
       (clause (ref (definition :function make-process)) ", " (ref (definition :function process-preset)) ", "
        (ref (definition :function process-run-function))))
     (defsection "Notes"
       (para "It would be nice to have more discussion of what it means
	      to synchronize with the process.")))
    (definition (:function process-run-function)
     "process-run-function process-specifier function {code &rest} args"
     "Creates a process, presets it, and enables it.
	    "
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param process-specifier}" ccldoc::=> "{param name} | 
		        ({code &key} {param name}{param persistent}{param priority}{param class}{param initargs}{param stack-size}{param vstack-size}{param tstack-size})
		      ")
         (item "{param name}" ccldoc::=> "a string, used to identify the process.
		        Passed to {function make-process}.")
         (item "{param function}" ccldoc::=> "a function, designated by itself or by a symbol
		        which names it.  Passed to
		        {function process-preset}.
		      ")
         (item "{param persistent}" ccldoc::=> "a boolean, passed to {function make-process}.
		      ")
         (item "{param priority}" ccldoc::=> "ignored.")
         (item "{param class}" ccldoc::=> "a subclass of CCL:PROCESS.  Passed to
		        {function make-process}.")
         (item "{param initargs}" ccldoc::=> "a list of any additional initargs to pass to
		        {function make-process}.")
         (item "{param stack-size}" ccldoc::=> "a size, in bytes.  Passed to
		        {function make-process}.")
         (item "{param vstack-size}" ccldoc::=> "a size, in bytes.  Passed to
		        {function make-process}.")
         (item "{param tstack-size}" ccldoc::=> "a size, in bytes.  Passed to
		        {function make-process}.")
         (item "{param process}" ccldoc::=> "the newly-created process.")))
     (defsection "Description"
       (para "Creates a lisp process (thread) via
	      "
        (ref (definition :function make-process)) ",
	      presets it via "
        (ref (definition :function process-preset)) ", and
	      enables it via "
        (ref (definition :function process-enable)) ".  This means
	      that {param process} will immediately begin to
	      execute.
	      {function process-run-function} is
	      the simplest way to create and run a process.
	    "))
     (defsection "See Also"
       (clause (ref (definition :function make-process)) ", " (ref (definition :function process-preset)) ", "
        (ref (definition :function process-enable)))))
    (definition (:function process-interrupt) "process-interrupt process function {code &rest} args"
     "Arranges for the target process to invoke a
	      specified function at some point in the near future, and then
	      return to what it was doing."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param process}" ccldoc::=> "a lisp process (thread).")
         (item "{param function}" ccldoc::=> "a function.
		      ")
         (item "{param args}" ccldoc::=> "a list of values, appropriate as arguments to
		        {param function}.")
         (item "{param result}" ccldoc::=> "the result of applying {param function}
		        to {param args} if {param process}
		        is the {variable *current-process*}, otherwise
		        NIL.")))
     (defsection "Description"
       "Arranges for {param process}
	      to apply {param function} to {param args} at
	      some point in the near future (interrupting whatever
	      {param process}
	      was doing.) If {param function} returns normally,
	      {param process} resumes
	      execution at the point at which it was interrupted.

	      {param process} must be in an enabled state in
	      order to respond
	      to a {function process-interrupt} request.  It's
	      perfectly legal for a process to call
	      {function process-interrupt} on itself.

	      {function process-interrupt}
	      uses asynchronous POSIX signals to interrupt threads. If the
	      thread being interrupted is executing lisp code, it can
	      respond to the interrupt almost immediately (as soon as it
	      has finished pseudo-atomic operations like consing and
	      stack-frame initialization.)

	      If the interrupted thread is
	      blocking in a system call, that system call is aborted by
	      the signal and the interrupt is handled on return.
	    

	      It is
	      still difficult to reliably interrupt arbitrary foreign code
	      (that may be stateful or otherwise non-reentrant); the
	      interrupt request is handled when such foreign code returns
	      to or enters lisp.")
     (defsection "See Also" (clause (ref (definition :macro without-interrupts))))
     (defsection "Notes"
       "It would probably be better for {param result}
	      to always be NIL, since the present behavior is inconsistent.
	    

	      {function process-interrupt} works by sending signals
	      between threads, via the C function
	      {code #_pthread_signal}.  It could be argued
	      that it should be done in one of several possible other ways
	      under
	      Darwin, to make it practical to asynchronously interrupt
	      things which make heavy use of the Mach nanokernel.
	    "))

    (definition (:variable *current-process*) "*current-process*" nil
       "Bound separately in each process, to that process itself.
        It may be used when lisp code needs to find out what process
	it is executing in.  It should not be set by user code.")

    (definition (:function process-reset) "process-reset process &optional kill-option" nil
     "This function causes {param process} to cleanly exit from any
      ongoing computation and enter a state wehre it can be {function
      process-preset}.

      This is implemented by signaling a condition of type
      PROCESS-RESET; user-defined condition handlers should generally
      refrain from attempting to handle conditions of this type.

      The {param kill-option} argument is for internal
      use only and should not be specified by user code.

      A process can meaningfully reset itself.

      There is in general no way to know precisely when {param
      process} has completed the act of resetting or killing itself; a
      process which has either entered the limbo of the reset state or
      exited has few ways of communicating either fact.

      The function {function process-enable} can reliably determine
      when a process has entered the limbo of the reset state, but
      can't predict how long the clean exit from ongoing computation
      might take: that depends on the behavior of {function
      unwind-protect} cleanup forms, and of the OS scheduler.

      Resetting a process other than {variable *current-process*}
      involves the use of the function {function process-interrupt}.")

    (definition (:function process-reset-and-enable) "process-reset-and-enable process"
     "Reset and enable the specified process, which
	    may not be the current process."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param process}" ccldoc::=> "a lisp process (thread), which
		      may not be the current process.")
         (item "{param result}" ccldoc::=> "undefined.")))
     (defsection "Description"
       (para "
	      Equivalent to calling (process-reset process) and
	      (process-enable process).
	    "))
     (defsection "See Also"
       (clause (ref (definition :function process-reset)) ", " (ref (definition :function process-enable)))))

    (definition (:function process-kill) "process-kill process" nil
     "Causes {param process} to cleanly exit from any ongoing
      computation, and then exit.  Note that {code unwind-protect}
      cleanup forms will be run with interrupts disabled.")

    (definition (:function process-abort) "process-abort process &optional condition"
     (clause "Causes a specified process to process an abort
	      condition, as if it had invoked
	      {function abort}.")
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param process}" ccldoc::=> "a lisp process (thread).")
         (item "{param condition}" ccldoc::=> "a lisp condition.  The default is NIL.")))
     (defsection "Description"
       (para "Entirely equivalent to calling
	      ("
        (ref (definition :function process-interrupt)) "{param process}
	      ({function lambda} ()
	      ({function abort}{param condition}))).
	      Causes {param process} to transfer control to the
	      applicable handler or restart for {function abort}.")
       (para "If {param condition} is non-NIL,
	      {function process-abort} does not consider any
	      handlers which are explicitly bound to conditions other than
	      {param condition}."))
     (defsection "See Also"
       (clause (ref (definition :function process-reset)) ", " (ref (definition :function process-kill)))))
    (definition (:variable *ticks-per-second*) "*TICKS-PER-SECOND*" "Bound to the clock resolution of the OS
	      scheduler."
     (defsection "Value Type" (para "A positive integer."))
     (defsection "Initial Value"
       (para "The clock resolution of the OS scheduler.  Currently,
	      both LinuxPPC and DarwinPPC yield an initial value of 100.
	    "))
     (defsection "Description"
       (para #:|This value is ordinarily of marginal interest at best,
	      but, for backward compatibility, some functions accept timeout
	      values expressed in "ticks".  This value gives the number of
	      ticks per second.|))
     (defsection "See Also" (clause (ref (definition :function process-wait-with-timeout)))))
    (definition (:function process-whostate) "process-whostate process"
     "Returns a string which describes the status of
	      a specified process."
     (defsection "Description"
       (listing :definition
         (item "{param process}" ccldoc::=> "a lisp process (thread).")
         (item "{param whostate}" ccldoc::=> #:|a string which describes the "state" of
		        {param process}.|))
       (para "This information is primarily for the benefit of
	      debugging tools.  {param whostate} is a terse report
	      on what {param process} is doing, or not doing,
	      and why.")
       (para "If the process is currently waiting in a call to
	      "
        (ref (definition :function process-wait)) " or
	      "
        (ref (definition :function process-wait-with-timeout)) ", its
	      {function process-whostate} will be the value
	      which was passed to that function as {param whostate}.
	    "))
     (defsection "See Also"
       (clause (ref (definition :function process-wait)) ", " (ref (definition :function process-wait-with-timeout))
        ", " (ref (definition :macro with-terminal-input))))
     (defsection "Notes"
       (para "This should arguably be SETFable, but doesn't seem to
	      ever have been.")))
    (definition (:function process-allow-schedule) "process-allow-schedule"
     "Used for cooperative multitasking; probably never
	      necessary."
     (defsection "Description"
       (para "Advises the OS scheduler that the current thread has nothing
	      useful to do and that it should try to find some other thread to
	      schedule in its place. There's almost always a better
	      alternative, such as waiting for some specific event to
	      occur.  For example, you could use a lock or semaphore."))
     (defsection "See Also"
       (clause (ref (definition :function make-lock)) ", " (ref (definition :function make-read-write-lock)) ", "
        (ref (definition :function make-semaphore)) ", " (ref (definition :function process-input-wait)) ", "
        (ref (definition :function process-output-wait)) ", " (ref (definition :macro with-terminal-input))))
     (defsection "Notes"
       (para "This is a holdover from the days of cooperative
	      multitasking.  All modern general-purpose operating systems use
	      preemptive multitasking.")))
    (definition (:function process-wait) "process-wait whostate function {code &rest} args"
     "Causes the current lisp process (thread) to wait for
	      a given
	      predicate to return true."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param whostate}" ccldoc::=> "a string, which will be the value of
		        "
          (ref (definition :function process-whostate)) "
		        while the process is waiting.")
         (item "{param function}" ccldoc::=> "a function, designated by itself or by a symbol
		        which names it.
		      ")
         (item "{param args}" ccldoc::=> "a list of values, appropriate as arguments to
		        {param function}.")
         (item "{param result}" ccldoc::=> "NIL.")))
     (defsection "Description"
       (para "Causes the current lisp process (thread) to repeatedly
	      apply {param function} to
	      {param args} until the call returns a true result, then
	      returns NIL. After
	      each failed call, yields the CPU as if by
	      "
        (ref (definition :function process-allow-schedule)) ".")
       (para "
	      As with "
        (ref (definition :function process-allow-schedule)) ", it's almost
	      always more efficient to wait for some
	      specific event to occur; this isn't exactly busy-waiting, but
	      the OS scheduler can do a better job of scheduling if it's given
	      the relevant information.  For example, you could use a lock
	      or semaphore."))
     (defsection "See Also"
       (clause (ref (definition :function process-whostate)) ", "
        (ref (definition :function process-wait-with-timeout)) ", " (ref (definition :function make-lock)) ", "
        (ref (definition :function make-read-write-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input)))))
    (definition (:function process-wait-with-timeout)
     "process-wait-with-timeout whostate ticks function args" "Causes the current thread to wait for a given
	      predicate to return true, or for a timeout to expire."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param whostate}" ccldoc::=> "a string, which will be the value of
		        "
          (ref (definition :function process-whostate)) "
		        while the process is waiting.")
         (item "{param ticks}" ccldoc::=> #:|either a positive integer expressing a duration
		        in "ticks" (see |
          (ref (definition :variable *ticks-per-second*)) "),
		        or NIL.")
         (item "{param function}" ccldoc::=> "a function, designated by itself or by a symbol
		        which names it.")
         (item "{param args}" ccldoc::=> "a list of values, appropriate as arguments to
		        {param function}.")
         (item "{param result}" ccldoc::=> "T if {function process-wait-with-timeout}
		        returned because its {param function} returned
		        true, or NIL if it returned because the duration
		        {param ticks} has been exceeded.")))
     (defsection "Description"
       (para "If {param ticks} is NIL, behaves exactly like
	      "
        (ref (definition :function process-wait)) ", except for returning T.
	      Otherwise, {param function} will be tested repeatedly,
	      in the same
	      kind of test/yield loop as in "
        (ref (definition :function process-wait)) "
	      until either {param function} returns true,
	      or the duration {param ticks} has been exceeded.
	    ")
       (para " Having already read the descriptions of
	      "
        (ref (definition :function process-allow-schedule)) " and
	      "
        (ref (definition :function process-wait)) ", the
	      astute reader has no doubt anticipated the observation that
	      better alternatives should be used whenever possible."))
     (defsection "See Also"
       (clause (ref (definition :variable *ticks-per-second*)) ", " (ref (definition :function process-whostate)) ", "
        (ref (definition :function process-wait)) ", " (ref (definition :function make-lock)) ", "
        (ref (definition :function make-read-write-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input)))))
    (definition (:macro without-interrupts) "without-interrupts &body body"
     "Evaluates its body in an environment in which
	      process-interrupt requests are deferred."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param body}" ccldoc::=> "an implicit progn.")
         (item "{param result}" ccldoc::=> "the primary value returned by
		        {param body}.")))
     (defsection "Description"
       (para "Executes {param body}
	      in an environment in which "
        (ref (definition :function process-interrupt)) "
	      requests are
	      deferred. As noted in the description of
	      "
        (ref (definition :function process-interrupt)) ", this has nothing to do
	      with the
	      scheduling of other threads; it may be necessary to inhibit
	      "
        (ref (definition :function process-interrupt)) " handling when
	      (for instance) modifying some data
	      structure (for which the current thread holds an appropriate lock)
	      in some manner that's not reentrant."))
     (defsection "See Also" (clause (ref (definition :function process-interrupt)))))
    (definition (:macro with-interrupts-enabled) "with-interrupts-enabled {code &body} body"
     "Evaluates its body in an environment in which
	      process-interrupt requests have immediate effect."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param body}" ccldoc::=> "an implicit progn.")
         (item "{param result}" ccldoc::=> "the primary value returned by
		        {param body}.")))
     (defsection "Description"
       (para "Executes {param body}
	      in an environment in which "
        (ref (definition :function process-interrupt)) "
	      requests have immediate effect.
	    ")))
    (definition (:function make-lock) "make-lock {code &optional} name"
     "Creates and returns a lock object, which can
	      be used for synchronization between threads."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param name}" ccldoc::=> "any lisp object; saved as part of
		        {param lock}.  Typically a string or symbol
		        which may appear in the "
          (ref (definition :function process-whostate)) "s
		        of threads which are waiting for {param lock}.
		      ")
         (item "{param lock}" ccldoc::=> "a newly-allocated object of type CCL:LOCK.")))
     (defsection "Description"
       (para #:|Creates and returns a lock object, which can
	      be used to synchronize access to some shared resource.
	      {param lock} is
	      initially in a "free" state; a lock can also be
	      "owned" by a
	      thread.|))
     (defsection "See Also"
       (clause (ref (definition :macro with-lock-grabbed)) ", " (ref (definition :function grab-lock)) ", "
        (ref (definition :function release-lock)) ", " (ref (definition :function try-lock)) ", "
        (ref (definition :function make-read-write-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input)))))
    (definition (:macro with-lock-grabbed) "with-lock-grabbed (lock) {code &body} body"
     "Waits until a given lock can be obtained, then
	      evaluates its body with the lock held."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param lock}" ccldoc::=> "an object of type CCL:LOCK.")
         (item "{param body}" ccldoc::=> "an implicit progn.")
         (item "{param result}" ccldoc::=> "the primary value returned by
		        {param body}.")))
     (defsection "Description"
       (para "Waits until {param lock} is either free or
	      owned by the calling
	      thread, then executes {param body} with the
	      lock owned by the calling thread. If {param lock}
	      was free when {function with-lock-grabbed} was called,
	      it is restored to a free state after {param body}
	      is executed."))
     (defsection "See Also"
       (clause (ref (definition :function make-lock)) ", " (ref (definition :function grab-lock)) ", "
        (ref (definition :function release-lock)) ", " (ref (definition :function try-lock)) ", "
        (ref (definition :function make-read-write-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input)))))
    (definition (:function grab-lock) "grab-lock lock" "Waits until a given lock can be obtained, then
	      obtains it."
     (defsection "Arguments and Values"
       (listing :definition (item "{param lock}" ccldoc::=> "an object of type CCL:LOCK.")))
     (defsection "Description"
       (para "Blocks until {param lock} is owned by the
	      calling thread.")
       (para "The macro " (ref (definition :macro with-lock-grabbed)) "{emphasis could} be defined in
	      terms of {function grab-lock} and
	      "
        (ref (definition :function release-lock)) ", but it is actually
	      implemented at a slightly lower level."))
     (defsection "See Also"
       (clause (ref (definition :function make-lock)) ", " (ref (definition :macro with-lock-grabbed)) ", "
        (ref (definition :function release-lock)) ", " (ref (definition :function try-lock)) ", "
        (ref (definition :function make-read-write-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input)))))
    (definition (:function release-lock) "release-lock lock" "Relinquishes ownership of a given lock."
     (defsection "Arguments and Values"
       (listing :definition (item "{param lock}" ccldoc::=> "an object of type CCL:LOCK.")))
     (defsection "Description"
       (para "Signals an error of type CCL:LOCK-NOT-OWNER if
	      {param lock}
	      is not already owned by the calling thread; otherwise, undoes the
	      effect of one previous 
	      "
        (ref (definition :function grab-lock)) ".  If this means that
	      {function release-lock} has now been called on
	      {param lock} the same number of times as
	      "
        (ref (definition :function grab-lock)) " has, {param lock}
	      becomes free."))
     (defsection "See Also"
       (clause (ref (definition :function make-lock)) ", " (ref (definition :macro with-lock-grabbed)) ", "
        (ref (definition :function grab-lock)) ", " (ref (definition :function try-lock)) ", "
        (ref (definition :function make-read-write-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input)))))
    (definition (:function try-lock) "try-lock lock" "Obtains the given lock, but only if it is not
	      necessary to wait for it."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param lock}" ccldoc::=> "an object of type CCL:LOCK.")
         (item "{param result}" ccldoc::=> "T if {param lock} has been obtained,
		        or NIL if it has not.")))
     (defsection "Description"
       (para "Tests whether {param lock}
	      can be obtained without blocking - that is, either
	      {param lock} is already free, or it is already owned
	      by "
        (ref (definition :variable *current-process*)) ".  If it can,
	      causes it to
	      be owned by the calling lisp process (thread) and returns T.
	      Otherwise, the lock
	      is already owned by another thread and cannot be obtained without
	      blocking; NIL is returned in this case."))
     (defsection "See Also"
       (clause (ref (definition :function make-lock)) ", " (ref (definition :macro with-lock-grabbed)) ", "
        (ref (definition :function grab-lock)) ", " (ref (definition :function release-lock)) ", "
        (ref (definition :function make-read-write-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input)))))
    (definition (:function make-read-write-lock) "make-read-write-lock-write-lock"
     "Creates and returns a read-write lock, which can
	      be used for synchronization between threads."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param read-write-lock}" ccldoc::=> "a newly-allocated object of type
		        CCL:READ-WRITE-LOCK.")))
     (defsection "Description"
       (para #:|Creates and returns an object of type CCL::READ-WRITE-LOCK.
	      A read-write lock may, at any given time, belong to any number
	      of lisp processes (threads) which act as "readers"; or, it may
	      belong to at most one process which acts as a "writer".  A
	      read-write lock may never be held by a reader at the same time as
	      a writer.  Initially, {param read-write-lock} has
	      no readers and no writers.|))
     (defsection "See Also"
       (clause (ref (definition :macro with-read-lock)) ", " (ref (definition :macro with-write-lock)) ", "
        (ref (definition :function make-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input))))
     (defsection "Notes"
       (para #:|There probably should be some way to
	      atomically "promote" a reader, making it a writer without
	      releasing the lock, which could otherwise cause delay.|)))

    (definition (:macro with-read-lock) "with-read-lock (read-write-lock) &body body"
     "Waits until a given lock is available for
	      read-only access, then evaluates its body with the lock
	      held."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param read-write-lock}" ccldoc::=> "an object of type
		        CCL:READ-WRITE-LOCK.")
         (item "{param body}" ccldoc::=> "an implicit progn.")
         (item "{param result}" ccldoc::=> "the primary value returned by
		        {param body}.")))
     (defsection "Description"
       (para "Waits until {param read-write-lock} has no
	      writer,
	      ensures that "
        (ref (definition :variable *current-process*)) " is a
	      reader of it, then executes {param body}.
	    ")
       (para "After executing {param body}, if
	      "
        (ref (definition :variable *current-process*)) " was not a reader of
	      {param read-write-lock} before
	      {function with-read-lock} was called, the lock is
	      released.  If it was already a reader, it remains one."))
     (defsection "See Also"
       (clause (ref (definition :function make-read-write-lock)) ", " (ref (definition :macro with-write-lock)) ", "
        (ref (definition :function make-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input)))))

    (definition (:macro with-write-lock) "with-write-lock (read-write-lock) &body body"
     "Waits until the given lock is available for write
	      access, then executes its body with the lock held."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param read-write-lock}" ccldoc::=> "an object of type
		        CCL:READ-WRITE-LOCK.")
         (item "{param body}" ccldoc::=> "an implicit progn.")
         (item "{param result}" ccldoc::=> "the primary value returned by
		        {param body}.")))
     (defsection "Description"
       (para "Waits until {param read-write-lock} has no
	      readers and no writer other than "
        (ref (definition :variable *current-process*)) ",
	      then ensures that "
        (ref (definition :variable *current-process*)) " is the
	      writer of it.  With the lock held, executes {param body}.
	    ")
       (para "After executing {param body}, if
	      "
        (ref (definition :variable *current-process*)) " was not the writer of
	      {param read-write-lock} before
	      {function with-write-lock} was called, the lock is
	      released.  If it was already the writer, it remains the
	      writer."))
     (defsection "See Also"
       (clause (ref (definition :function make-read-write-lock)) ", " (ref (definition :macro with-read-lock)) ", "
        (ref (definition :function make-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input)))))

    (definition (:function make-semaphore) "make-semaphore"
     "Creates and returns a semaphore, which can be used
	      for synchronization between threads."
     (defsection "Arguments and Values"
       (listing :definition (item "{param semaphore}" ccldoc::=> "a newly-allocated object of type CCL:SEMAPHORE.")))
     (defsection "Description"
       (para #:|Creates and returns an object of type CCL:SEMAPHORE.
	      A semaphore has an associated "count" which may be incremented
	      and decremented atomically; incrementing it represents sending
	      a signal, and decrementing it represents handling that signal.
	      {param semaphore} has an initial count of 0.|))
     (defsection "See Also"
       (clause (ref (definition :function signal-semaphore)) ", " (ref (definition :function wait-on-semaphore)) ", "
        (ref (definition :function timed-wait-on-semaphore)) ", " (ref (definition :function make-lock)) ", "
        (ref (definition :function make-read-write-lock)) ", " (ref (definition :function process-input-wait)) ", "
        (ref (definition :function process-output-wait)) ", " (ref (definition :macro with-terminal-input)))))
    (definition (:function signal-semaphore) "signal-semaphore semaphore"
     "Atomically increments the count of a given
	      semaphore."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param semaphore}" ccldoc::=> "an object of type CCL:SEMAPHORE.")
         (item "{param result}" ccldoc::=> "an integer representing an error identifier
		        which was returned by the underlying OS call.")))
     (defsection "Description"
       (para #:|Atomically increments {param semaphore}'s
	      "count" by 1; this
	      may enable a waiting thread to resume execution.|))
     (defsection "See Also"
       (clause (ref (definition :function make-semaphore)) ", " (ref (definition :function wait-on-semaphore)) ", "
        (ref (definition :function timed-wait-on-semaphore)) ", " (ref (definition :function make-lock)) ", "
        (ref (definition :function make-read-write-lock)) ", " (ref (definition :function process-input-wait)) ", "
        (ref (definition :function process-output-wait)) ", " (ref (definition :macro with-terminal-input))))
     (defsection "Notes"
       (para "{param result} should probably be interpreted
	      and acted on by {function signal-semaphore}, because
	      it is not likely to be meaningful to a lisp program, and the
	      most common cause of failure is a type error.")))
    (definition (:function wait-on-semaphore) "wait-on-semaphore semaphore"
     "Waits until the given semaphore has a positive
	      count which can be atomically decremented."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param semaphore}" ccldoc::=> "an object of type CCL:SEMAPHORE.")
         (item "{param result}" ccldoc::=> "an integer representing an error identifier
		        which was returned by the underlying OS call.")))
     (defsection "Description"
       (para "Waits until {param semaphore}
	      has a positive count that can be
	      atomically decremented; this will succeed exactly once for each
	      corresponding call to SIGNAL-SEMAPHORE."))
     (defsection "See Also"
       (clause (ref (definition :function make-semaphore)) ", " (ref (definition :function signal-semaphore)) ", "
        (ref (definition :function timed-wait-on-semaphore)) ", " (ref (definition :function make-lock)) ", "
        (ref (definition :function make-read-write-lock)) ", " (ref (definition :function process-input-wait)) ", "
        (ref (definition :function process-output-wait)) ", " (ref (definition :macro with-terminal-input))))
     (defsection "Notes"
       (para "{param result} should probably be interpreted
	      and acted on by {function wait-on-semaphore}, because
	      it is not likely to be meaningful to a lisp program, and the
	      most common cause of failure is a type error.")))
    (definition (:function timed-wait-on-semaphore) "timed-wait-on-semaphore semaphore timeout"
     "Waits until the given semaphore has a positive
	      count which can be atomically decremented, or until a timeout
	      expires."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param semaphore}" ccldoc::=> "An object of type CCL:SEMAPHORE.")
         (item "{param timeout}" ccldoc::=> "a time interval in seconds.  May be any
		        non-negative real number the {function floor} of
		        which fits in 32 bits.  The default is 1.")
         (item "{param result}" ccldoc::=> "T if {function timed-wait-on-semaphore}
		        returned because it was able to decrement the count of
		        {param semaphore}; NIL if it returned because
		        the duration {param timeout} has been
		        exceeded.")))
     (defsection "Description"
       (para "Waits until {param semaphore}
	      has a positive count that can be
	      atomically decremented, or until the duration
	      {param timeout} has
	      elapsed."))
     (defsection "See Also"
       (clause (ref (definition :function make-semaphore)) ", " (ref (definition :function wait-on-semaphore)) ", "
        (ref (definition :function make-lock)) ", " (ref (definition :function make-read-write-lock)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input)))))
    (definition (:function process-input-wait) "process-input-wait fd {code &optional} timeout"
     "Waits until input is available on a given
	      file-descriptor."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param fd}" ccldoc::=> "a file descriptor, which is a non-negative integer
		        used by the OS to refer to an open file, socket, or similar
		        I/O connection.  See "
          (ref (definition :generic-function stream-device)) ".")
         (item "{param timeout}" ccldoc::=>
          "either NIL or a time interval in milliseconds.  Must be a non-negative integer.  The default is NIL.")))
     (defsection "Description"
       "Wait until input is available on {param fd}.
	      This uses the {code select()} system call, and is
	      generally a fairly
	      efficient way of blocking while waiting for input. More
	      accurately, {function process-input-wait}
	      waits until it's possible to read
	      from fd without blocking, or until {param timeout}, if
	      it is not NIL, has been exceeded.


	      Note that it's possible to read without blocking if
	      the file is at its end - although, of course, the read will
	      return zero bytes.")
     (defsection "See Also"
       (clause (ref (definition :function make-lock)) ", " (ref (definition :function make-read-write-lock)) ", "
        (ref (definition :function make-semaphore)) ", " (ref (definition :function process-output-wait)) ", "
        (ref (definition :macro with-terminal-input))))
     (defsection "Notes"
       (para "{function process-input-wait} has a timeout parameter,
	      and
	      "
        (ref (definition :function process-output-wait)) " does not.  This
	      inconsistency should probably be corrected.
	    ")))
    (definition (:function process-output-wait) "process-output-wait fd {code &optional} timeout"
     "Waits until output is possible on a given file
	      descriptor."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param fd}" ccldoc::=> "a file descriptor, which is a non-negative integer
		        used by the OS to refer to an open file, socket, or similar
		        I/O connection.  See "
          (ref (definition :generic-function stream-device)) ".")
         (item "{param timeout}" ccldoc::=>
          "either NIL or a time interval in milliseconds.  Must be a non-negative integer.  The default is NIL.")))
     (defsection "Description"
       "Wait until output is possible on {param fd} or until {param timeout}, if
	      it is not NIL, has been exceeded.
	      This uses the {code select()} system call, and is
	      generally a fairly
	      efficient way of blocking while waiting to output.

	      If {function process-output-wait} is called on
	      a network socket which has not yet established a connection, it
	      will wait until the connection is established.  This is an
	      important use, often overlooked.")
     (defsection "See Also"
       (clause (ref (definition :function make-lock)) ", " (ref (definition :function make-read-write-lock)) ", "
        (ref (definition :function make-semaphore)) ", " (ref (definition :function process-input-wait)) ", "
        (ref (definition :macro with-terminal-input))))
     (defsection "Notes"
       (para (ref (definition :function process-input-wait)) " has a timeout parameter,
	      and
	      {function process-output-wait} does not.  This
	      inconsistency should probably be corrected.
	    ")))
    (definition (:macro with-terminal-input) "with-terminal-input {code &body} body"
     "Executes its body in an environment with exclusive
	      read access to the terminal."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param body}" ccldoc::=> "an implicit progn.")
         (item "{param result}" ccldoc::=> "the primary value returned by
		        {param body}.")))
     (defsection "Description"
       (para "Requests exclusive read access to the standard terminal
	      stream, {variable *terminal-io*}.  Executes
	      {param body} in an environment with that access.
	    "))
     (defsection "See Also"
       (clause (ref (definition :variable *request-terminal-input-via-break*)) ", "
        (ref (definition :toplevel-command ":Y")) ", " (ref (definition :function make-lock)) ", "
        (ref (definition :function make-read-write-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)))))
    (definition (:variable *request-terminal-input-via-break*) "*REQUEST-TERMINAL-INPUT-VIA-BREAK*"
     "Controls how attempts to obtain ownership of
	      terminal input are made."
     (defsection "Value Type" (para "A boolean.")) (defsection "Initial Value" (para "NIL."))
     (defsection "Description"
       (para "Controls how attempts to obtain ownership of terminal input
	      are made. When NIL, a message is printed on *TERMINAL-IO*;
	      it's expected that the user will later yield
	      control of the terminal via the :Y toplevel command. When T, a
	      BREAK condition is signaled in the owning process; continuing from
	      the break loop will yield the terminal to the requesting process
	      (unless the :Y command was already used to do so in the break
	      loop.)"))
     (defsection "See Also"
       (clause (ref (definition :macro with-terminal-input)) ", " (ref (definition :toplevel-command ":Y")) ", "
        (ref (definition :function make-lock)) ", " (ref (definition :function make-read-write-lock)) ", "
        (ref (definition :function make-semaphore)) ", " (ref (definition :function process-input-wait)) ", "
        (ref (definition :function process-output-wait)))))
    (definition (:toplevel-command ":Y") "( :y p)" "Yields control of terminal input to a specified
	      lisp process (thread)."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param p}" ccldoc::=> "a lisp process (thread), designated either by
		        an integer which matches its
		        {function process-serial-number},
		        or by a string which is {function equal} to
		        its {function process-name}.")))
     (defsection "Description"
       (para ":Y is a toplevel command, not a function.  As such, it
	      can only be used interactively, and only from the initial
	      process.")
       (para "The command yields control of terminal input to the
	      process {param p}, which must have used
	      "
        (ref (definition :macro with-terminal-input)) " to request access to the
	      terminal input stream."))
     (defsection "See Also"
       (clause (ref (definition :macro with-terminal-input)) ", "
        (ref (definition :variable *request-terminal-input-via-break*)) ", " (ref (definition :function make-lock))
        ", " (ref (definition :function make-read-write-lock)) ", " (ref (definition :function make-semaphore)) ", "
        (ref (definition :function process-input-wait)) ", " (ref (definition :function process-output-wait)))))
    (definition (:function join-process) "join-process process {code &optional} default"
     "Waits for a specified process to complete and
	returns the values that that process's initial function
	returned."
     (defsection "Arguments and Values"
       (listing :definition
         (item "{param process}" ccldoc::=> "a process, typically created by "
          (ref (definition :function process-run-function)) " or by " (ref (definition :function make-process)))
         (item "{param default}" ccldoc::=> "A default value to be returned if the specified
	      process doesn't exit normally.")
         (item "{param values}" ccldoc::=> "The values returned by the specified process's
	      initial function if that function returns, or the value
	      of the default argument, otherwise.")))
     (defsection "Description"
       (para #:|Waits for the specified process to terminate.  If the
	process terminates "normally" (if its initial function
	returns), returns the values that that initial function
	returnes.  If the process does not terminate normally (e.g.,
	if it's terminated via |
        (ref (definition :function process-kill)) " and a
	default argument is provided, returns the value of that
	default argument.  If the process doesn't terminate normally
	and no default argument is provided, signals an error.")
       (para "A process can't successfully join itself, and only one
	process can successfully receive notification of another process's
	termination.")))))
