;;;; -*- coding:utf-8 -*-
;;;;FILE:               process.lisp
;;;;LANGUAGE:           Common-Lisp
;;;;SYSTEM:             clisp
;;;;    Multiprocessing for clisp.
;;;;    This package doesn't implement threads like in other Common-Lisp
;;;;    implementations.  Rather it uses plain unix processes.
;;;;    This has sever implications for IPC: there is no shared memory,
;;;;    all inter-process communication must go thru pipes or sockets.
;;;;    http://www.franz.com/support/documentation/7.0/doc/multiprocessing.htm
;;;;    <PJB> Pascal J. Bourguignon <pjb@informatimago.com>
;;;;    2004-11-23 <PJB> Added MAKE-PIPE.
;;;;    2004-09-23 <PJB> Added MAKE-XTERM-IO-STREAM.
;;;;    2004-08-03 <PJB> Created.
;;;;    AGPL3
;;;;    Copyright Pascal J. Bourguignon 2004 - 2016
;;;;    This program is free software: you can redistribute it and/or modify
;;;;    it under the terms of the GNU Affero General Public License as published by
;;;;    the Free Software Foundation, either version 3 of the License, or
;;;;    (at your option) any later version.
;;;;    This program is distributed in the hope that it will be useful,
;;;;    but WITHOUT ANY WARRANTY; without even the implied warranty of
;;;;    GNU Affero General Public License for more details.
;;;;    You should have received a copy of the GNU Affero General Public License
;;;;    along with this program.  If not, see <http://www.gnu.org/licenses/>
(eval-when (:compile-toplevel :load-toplevel :execute)
  (setf *readtable* (copy-readtable nil)))
(in-package "COMMON-LISP-USER")
(declaim (declaration also-use-packages)
         (also-use-packages "LINUX"
  (:documentation "Implement a multiprocessing API for clisp.")
  (:use "COMMON-LISP"
  (:export ))

(defclass scheduler-object ()

(defclass process (scheduler-object)
    :accessor process-name
    :initarg :name
    :type string
    :initform nil)
    :accessor process-reset-action
    :initarg :reset-action
    :type string
    :initform nil)
    :accessor process-run-reasons
    :initarg :run-reasons
    :type string
    :initform nil
    :documentation "This function returns the list of run-reasons for
                    process. The list can be changed with setf or
                    related macros and this function or with
                    process-add-run-reason. Any Lisp object can be on
                    the list. A process will not run unless it has at
                    least one run reason and no arrest reasons (see
    :accessor process-arrest-reasons
    :initarg :arrest-reasons
    :type string
    :initform nil
    :documentation "This function returns the list of arrest-reasons
                    for process. The list can be changed with setf or
                    related macros and this function or with
                    process-add-arrest-reason. Any Lisp object can be
                    on the list. A process will not run unless it has
                    at least one run reason (see process-run-reasons)
                    and no arrest reasons.")
    :accessor process-priority
    :initarg :priority
    :type string
    :initform nil
    :documentation   "
    This function returns the priority of process. It defaults to 0
    and may be set to any fixnum with setf.
    Returns the priority of process, which must be an instance of
    PROCESS. Priority may be any real number. It defaults to 0. It
    may be reset with setf and this function.
    ;; So, real or fixnum?
    ;; We could use it to set the nice level.
    :accessor process-quantum
    :initarg :quantum
    :type string
    :initform nil
    :documentation   "
    This function returns the quantum for process.
    The quantum may be specified when the process is created; it
    defaults to the value of *default-process-quantum* and may be set
    to any real value between 0.1 and 20 with setf.
    :accessor process-initial-bindings
    :initarg :initial-bindings
    :type string
    :initform nil
    :documentation "This slot of a process stores an alist of initial
                    special bindings which are established in a process
                    when it is started. The value may be set with setf.")
    :accessor process-message-interrupt-function
    :initarg :message-interrupt-function
    :type string
    :initform nil)
    :accessor process-stack-allocation
    :initarg :stack-allocation
    :type string
    :initform nil)
    :accessor process-run-immediately
    :initarg :run-immediately
    :type string
    :initform nil)
   ;; ----------------------------------------
    :accessor process-property-list
  	:initarg :property-list
  	:type list
    :initform nil
    :documentation "The property-list slot of a process implements
                    a generalized property list as a convenient place
                   to store additional information about a process.")
    :accessor process-resume-hook
    :initarg :resume-hook
    :type (or null function)
    :initform nil
    :documentation "It is normal for execution of a process to be
                    interrupted many times. This is transparent to
                    the process and usually it is not necessary for
                    the process to know when its execution is
                    suspended  and resumed. However, if these slots
                    are non-nil,  they should be functions of no
                    arguments which are called on the process'
                    stack-group or thread each time the execution is
                    suspended or resumed (but not when the process is
                    first started or when it is killed).")
    :accessor process-suspend-hook
    :initarg :suspend-hook
    :type (or null function)
    :initform nil
    :documentation "See documentation of RESUME-HOOK.")
    :accessor process-thread
    :initform nil
    :documentation "The thread associated with process.")
   (running :initform :idle)
   (pid     :accessor process-pid :initform 0 :type integer ))
  (:documentation "")) ;;process

(defun symeval-in-process (symbol process)
    This function returns two values. The first is the value of the
    special symbol in the given thread (which may be the current
    thread). It only looks for actual bindings on the thread; if the
    symbol has a global value but is not bound on the thread, the global
    value is not returned.
    The second returned value describes the status of the binding. t is
    returned if the symbol is bound on the thread, nil if the symbol has
    no binding, and :unbound if there is a binding which has been
    subjected to makunbound. In the latter two cases, the first returned
    value is nil.
  ;; If the process is self, evaluate the symbol here
  ;; Otherwise:
  ;; process-1                 scheduler                     process-2
  ;;    |-------(symeval-req)----->|                             |
  ;;    |                          |-------(symeval-req)-------->|
  ;;    |                          |<------(symeval-res)---------|
  ;;    |<------(symeval-res)------|                             |
  (declare (ignore symbol process))
  (error "not implemented yet")) ;;symeval-in-process

(defun profile-process-p (process)
    This function returns the value of the profile-process-p flag for
    the thread specified by process. If the value of the flag is
    non-nil, then the space and time runtime analyzers will collect
    samples when this process is executing.
  ;; The scheduler can send periodically
  ;; a (progn (room t) (get-runt-time))-req to the profiled process.
  ;; (and perhaps backtrace too!)
  ;; We need the API to start the profiler and to collect statistics.
  (declare (ignore process))
  (error "not implemented yet")) ;;profile-process-p

(defparameter *all-processes* nil
    The value of this variable is a list of all processes that have ever
    been created and have never completed or been killed.
    The scheduler does not use this list; it is for debugging.
    ") ;;*all-processes*
;; The scheduler keeps an authoritative list of all processes.
;; *all-processes* is a symbol-macro that check the length of the list
;; and delete or create new process proxy objects as needed.
;; 1- The process data is kept in shared memory pages and the processes
;;    proxies just read this data.
;; 2- The *all-processes* symbol-macro sends a request to the scheduler
;;    and gets back process data to update the proxies.
;; 1 is better, but implies a shared memory mechanisms (with FFI
;; or print/readabbly).

(defparameter *default-process-quantum* 100
    Default quantum given to each process.
    This is not significant in the current implemetantion
    where the underlying OS does the actual scheduling.
    ") ;;*default-process-quantum*
;; IMPLEMENTATION: Not significant.

(defun start-scheduler ()
    Start the scheduler process and initialize multiprocessing.
    Multiprocessing is not automatically started in the default
    This function starts multiprocessing, which is also started
    automatically the first time a process is started by
    process-reset, directly or indirectly (as by
  (error "not implemented yet")) ;;start-scheduler
;; The initial process forks a child process that continues
;; and becomes the scheduler.
;; The child processes should ignore SIGINTR SIGTERM, etc, and let the
;; scheduler receive them and forward them to the children.

(defun make-process (&key (name "Anonymous")
                     (class 'process)
                     (reset-action nil) (run-reasons '()) (arrest-reasons '())
                     (resume-hook nil) (suspend-hook nil) (initial-bindings nil)
                     ;; not useful:
                     (priority 0) (quantum 2) run-immediately
                     message-interrupt-function stack-allocation)
    This function returns a new process object, but does nothing about
    making it runnable.
  ;; process-1                 scheduler                     process-2
  ;;    |-------(newproc-req)----->|                             |
  ;;    |                          |-----------(fork)----------->|
  ;;    |<------(newproc-rep)------|                             |
  (declare (ignore name class reset-action run-reasons arrest-reasons resume-hook suspend-hook initial-bindings priority quantum run-immediately message-interrupt-function stack-allocation))
  (error "not implemented yet")
  (let ((pid (linux:|fork|)))
      ((= 0 pid) ;; child
       (setf *current-process* (make-process :pid (linux:|getpid|)
                                             :name name))
       (push *current-process* *process-list*)
       (funcall function)
       (ext:exit *process-status*))
      ((< pid 0)
       (error "cannot fork, errno=~D" linux:|errno|))
      (t ;; parent
       (push (make-process :pid pid :name name) *process-list*)
       (car *process-list*))))) ;;make-process

(defparameter *current-process* #-(and)(make-process :pid (linux:|getpid|))
    The value of this variable is the process which is currently running.
    After the process module is loaded (either automatically, or because
    (require :process) is evaluated), the value will be non-nil.
    This should be treated as a read-only variable.
    ") ;;*current-process*

(defun process-run-function (name-or-keywords function &rest args)
    This function does a make-process, then presets the new process
    with function and args. The first argument is either a string,
    which is the name of the process, or is a list of keyword
    arguments accepted by make-process. The new process is
    returned. By default, the process is killed when and if it
  (declare (ignore name-or-keywords function args))
  (error "not implemented yet")) ;;process-run-function

(defun process-run-restartable-function (name-or-keywords function &rest args)
    This function is just like process-run-function (just above), but
    automatically provides a :reset-action argument of t. The process
    thus started will restart if it is reset or completes.
  (declare (ignore name-or-keywords function args))
  (error "not implemented yet")) ;;process-run-restartable-function

(defun process-enable (process)
    Makes process active by removing all its run and arrest reasons,
    then giving it a single run reason of :enable.
  (declare (ignore process))
  (error "not implemented yet")) ;;process-enable

(defun process-disable (process)
    This function makes process inactive by revoking all its run and
    arrest reasons. The effect is immediate if a process disables itself.
  (declare (ignore process))
  (error "not implemented yet")) ;;process-disable

(defun process-reset (process &optional no-unwind kill)
    This function causes process when it next runs to throw out to its
    present computation, if any, then apply its initial function to
    its initial argument. The no-unwind argument controls what happens
    to process' present computation, if it has one. nil (the default)
    or :unless-current cause the process to be unwound unless it is
    the current process. t unconditionally skips the unwind. Any other
    value is equivalent to t, causing an unconditional unwind, which
    will throw out of the caller even if the process being reset is
    The argument kill must be nil. (It is maintained only for backward
    compatibility). An error is signaled if it is not.
  ;; I don't think we will be able to accept no-unwind t
  (declare (ignore process no-unwind kill))
  (error "not implemented yet")) ;;process-reset

(defun process-preset (process function &rest arguments)
    This function sets the initial function and arguments of process,
    then resets any computation in progress in it. This does not make
    process active if it was not already active.
  (declare (ignore process function arguments))
  (error "not implemented yet")) ;;process-preset

(defun process-kill (process &key wait)
    This function resets the process to unwind it, then removes it
    from consideration for sunning and from the *all-processes* list.
    If the wait keyword argument is non-nil, the calling process waits
    until the killed process is really gone. process-kill signals an
    error if the process to be killed is an active immigrant. An
    inactive immigrant is one that was created to handle a lisp call
    from a foreign thread, and has returned from the topmost lisp call
    back into the foreign regime. The thread may still be processing,
    but it has no lisp state. This will kill the lisp process
    associated with that foreign thread, but will not kill the foreign
    thread itself. If it later calls into lisp, a new immigrant
    process will be created for it.
  (declare (ignore process wait))
  (error "not implemented yet")) ;;process-kill

(defun process-interrupt (process function &rest args)
    This function forces process to apply function to args when it
    next executes. When function returns, the original computation of
    process continues. If process is waiting when interrupted, it runs
    the interrupt function and then continues waiting. If process is
    not active, PROCESS-INTERRUPT makes it active for the interrupt
    function, then makes it inactive again. If additional interrupts
    are posted to a process when one is already posted, they are all
    run, but in undetermined order.
    In order for process-interrupt to work as described, function must
    return normally. It cannot execute a non-local exit (via, for
    example, throw). If function does exit in a non-local manner,
    process will not continue its computation.
    Processing an interrupt function can be interrupted by additional
    process interrupts that occur before the current one has finished
  ;; We won't be able to allow an interrupt function to be interrupted
  ;; unless we use a different signal. We could use a range of real-time signals.
  (declare (ignore process function args))
  (error "not implemented yet")) ;;process-interrupt

(defun process-name-to-process (name &key abbrev (error t))
    This function returns the process whose process-name is name. name
    must be a string or a symbol, in which case the print-name is
    used. If the abbrev keyword argument is specified true, then name
    is matched to the beginning of each process-name to find a
    match. The abbrev argument defaults to nil.
    If no process is found whose name is name (or, if abbrev is true,
    whose name begins with name), an error is signaled if error is
    unspecified or true, and nil is returned if error is specified nil.
  (declare (ignore name abbrev error))
  (error "process-name-to-process not implemented yet")
  ) ;;process-name-to-process

(defun process-initial-form (process)
    This function returns a cons of the initial function of process
    and its argument list.
  (declare (ignore process))
  (error "process-initial-form not implemented yet")) ;;process-initial-form

(defun process-wait-function (process)
    This function returns the function used to determine when a
    waiting process becomes runnable. The function is the one
    specified to the currently active call to process-wait on
    process. Wait functions are fully described in the
    process-waitdescription.  (If process is not waiting,
    process-wait-function returns nil.)
  (declare (ignore process))
  (error "process-wait-function not implemented yet")) ;;process-wait-function

(defun process-wait-args (process)
    This function returns the list of arguments passed to the wait
    function (see process-wait-function) when determining when a
    waiting process becomes runnable. See process-wait.
  (declare (ignore process))
  (error "process-wait-args not implemented yet")) ;;process-wait-args

(defun process-add-run-reason (process object)
    This function adds object to the list of run-reasons for process.
  (declare (ignore process object))
  (error "process-add-run-reason not implemented yet")) ;;process-add-run-reason

(defun process-add-arrest-reason (process object)
    This function adds object to the list of arrest-reasons for process.
  (declare (ignore process object))
  (error "process-add-arrest-reason not implemented yet")
  ) ;;process-add-arrest-reason

(defun process-revoke-run-reason (process object)
    This function removes object from the list of run reasons for process.
  (declare (ignore process object))
  (error "process-revoke-run-reason not implemented yet")
  ) ;;process-revoke-run-reason

(defun process-revoke-arrest-reason (process object)
    This function removes object from the list of arrest reasons for process.
  (declare (ignore process object))
  (error "process-revoke-arrest-reason not implemented yet")
  ) ;;process-revoke-arrest-reason

(defun process-runnable-p (process)
    These functions return t if, respectively, process is runnable or
    active. A process is active if it has been reset and not yet
    completed, and has at least one run reason and no arrest
    reasons. It is runnable if it is active and not waiting.
  (declare (ignore process))
  (error "process-runnable-p not implemented yet")) ;;process-runnable-p

(defun process-active-p (process)
    These functions return t if, respectively, process is runnable or
    active. A process is active if it has been reset and not yet
    completed, and has at least one run reason and no arrest
    reasons. It is runnable if it is active and not waiting.
  (declare (ignore process))
  (error "process-active-p not implemented yet")) ;;process-active-p

#|| attributes:
(defun process-priority (process)
  (error "process-priority not implemented yet"));;process-priority

(defun process-quantum (process)
  (error "process-quantum not implemented yet"));;process-quantum

(defun process-whostate (process)
    This function returns the current who-line string of process. This
    string can be used by a window system, for example, as a prompt or
    to indicate something about the status of the process. May be set
    with setf.
  (declare (ignore process))
  (error "process-whostate not implemented yet")) ;;process-whostate

(defmacro without-scheduling (&body body)
    This macro inhibits the system from suspending a process
    involuntarily (asynchronously) during the execution of
    body. However, the system will run another process if the current
    process blocks, waits, or executes a process-allow-schedule. Note
    that without-scheduling returns a single value, not multiple
    values. without-scheduling is intended to be used around short
    critical sections of code, and perhaps to be called frequently, so
    possible overhead of allocating multiple returns is avoided by
    returning a single value.
  ;; We cannot know when the process will block for another reason
  ;; (like waitting for input). Otherwise, we can block the other processes.
  (declare (ignore body))
  (error "without-scheduling not implemented yet")) ;;without-scheduling

(defmacro without-interrupts (&body body)
    This macro executes body, protecting against any handling of
    asynchronous interrupts. Execution of body is guaranteed to
    complete without any other process running, or any asynchronous
    interrupt being dispatched, unless the process does something to
    block or otherwise explicitly yield to the scheduler (e.g. with
    without-interrupts is implemented very efficiently and so may be
    executed frequently. It is generally bad style to wrap a
    without-interrupts around a body of code that takes a significant
    amount of time to execute because that may impose inappropriate
    high interrupt latency on other (possibly unrelated) interrupt
    handlers. without-interrupts is intended to be used around short
    critical sections of code; use of a process-lock may be more
    appropriate for protecting long bodies of code.
    In native threads (:os-threads) implementations of
    multiprocessing, a lisp process calling a foreign function can
    release control of the lisp environment so that another lisp
    process can run. Any attempt to perform such a heap-releasing call
    within the scope of a without-interrupts block signals an error
    and does not release the heap. Whether error processing overrides
    the without-interrupts block depends on the coding of the
    particular application.
  (declare (ignore body))
  (error "without-interrupts not implemented yet")) ;;without-interrupts

(defparameter *disallow-scheduling* t
    This special variable is bound to t whenever multiprocessing
    scheduling is disabled. For example, the system binds this
    variable to t during the execution of the forms within a
    without-scheduling form.
    This variable should be treated as read-only and should never be
    set or bound by user code.
    ") ;;*disallow-scheduling*

(defun process-sleep (seconds &optional whostate)
    process-sleep suspends the current process for at least the number
    of seconds specified. That number may be any non-negative,
    non-complex number. While the process sleeps, other processes are
    allowed to run.)
    The whostate (default ''Sleep'') is a string which temporarily
    replaces the process' whostate during the sleep.
    When multiprocessing is initialized, Common Lisp function sleep is
    changed to be equivalent to process-sleep. Instead of causing the
    entire Lisp world to suspend execution for the indicated time,
    only the executing process is suspended.)
    This is usually the desired action.
  ;; This can be merely SLEEP, but with the temporary binding of the whostate.
  (declare (ignore seconds whostate))
  (error "process-sleep not implemented yet")) ;;process-sleep

(defun process-wait (whostate function &rest arguments)
    This function suspends the current process (the value of
    *current-process*) until applying function to arguments yields
    true. The whostate argument must be a string which temporarily
    replaces the process' whostate for the duration of the wait.)
    This function returns nil.
    See the discussion under the headings Section 4.3 Waiting for
    input from a stream and Section 4.3.1 PROCESS-WAIT vs
  ;; The waiting process will wait (select, socket-status) or will read
  ;; the scheduler message queue.
  ;; gate-open-p     --> scheduler sends message when gate opens.
  ;; read-no-hang-p  --> socket:socket-status (if possible)
  ;; write-no-hang-p --> socket:socket-status (if possible)
  ;; stream-listen   --> socket:socket-status (if possible)
  ;; other           --> scheduler sends periodic messages to
  ;;                     the waiting process.
  ;; TO CHECK: whether socket-status works on message queue?
  (declare (ignore whostate function arguments))
  (error "process-wait not implemented yet")) ;;process-wait

(defun process-wait-with-timeout (whostate seconds function &rest args)
    This function is similar to process-wait, but with a timeout. The
    units of time are seconds. The value of seconds may be any real
    number. Negative values are treated the same as 0.)
    The wait will timeout if function does not return true before the
    timeout period expires.
  ;; Same as process-wait, but the scheduler will send a timeout message.
  (declare (ignore whostate seconds function args))
  (error "process-wait-with-timeout not implemented yet")
  ) ;;process-wait-with-timeout

(defun wait-for-input-available (streams &key wait-function whostate timeout)
    This lower-level function extends the capabilities of process-wait
    and process-wait-with-timeout to allow a process to wait for input
    from multiple streams and to wait for input from a file.
  ;; socket:socket-status should do it.
  ;; TO CHECK: whether socket-status works on message queue?
  (declare (ignore streams wait-function  whostate timeout))
  (error "wait-for-input-available not implemented yet")
  ) ;;wait-for-input-available

(defmacro with-timeout ((seconds . timeout-forms) &body body)
    This macro evaluates the body as a progn body. If the evaluation
    of body does not complete within the specified interval, execution
    throws out of the body and the timeout-forms are evaluated as a
    progn body, returning the result of the last form.)  The
    timeout-forms are not evaluated if the body completes within
  ;; We can use either alarm(2) and SIGALRM or ask the scheduler.
  ;; What could happen if SIGALRM occurs at a wrong time vs. the scheduler?
  (declare (ignore seconds timeout-forms body))
  (error "with-timeout not implemented yet")) ;;with-timeout

(defun process-allow-schedule (&optional other-process)
    This function resumes multiprocessing, allowing other processes to
    run. All other processes of equal or higher priority will have a
    chance to run before the executing process is next run. If the
    optional argument is provided, it should be another process.
  (declare (ignore other-process))
  (error "process-allow-schedule not implemented yet")) ;;process-allow-schedule


(defclass gate (scheduler-object)
  (:documentation "A two-state object that can be
                   process-waitted efficiently."))
;; The simpliest will be to make it only a proxy for a gate
;; in the scheduler.

(defun make-gate (open)
    Allocates and returns a gate object. The gate's initial state will
    be open if open is true and closed if open is nil.
  (declare (ignore open))
  (error "make-gate not implemented yet")) ;;make-gate

(defun open-gate (gate)
    The gate argument must have been allocated with make-gate.
    Makes the state of gate open.
  (declare (ignore gate))
  (error "open-gate not implemented yet")) ;;open-gate

(defun close-gate (gate)
    The gate argument must have been allocated with make-gate.
    Makes the state of gate closed.
  (declare (ignore gate))
  (error "close-gate not implemented yet")) ;;close-gate

(defun gate-open-p (gate)
    The gate argument must have been allocated with make-gate. Returns
    true if gate's state is open and returns nil if it is closed.

    As described in the documentation for gates linked to below,
    gate-open-p is handled specially by process-wait, and so code which
    uses gates runs more efficiently. The speedup can be significant if
    the process often waits.
  (declare (ignore gate))
  (error "gate-open-p not implemented yet")) ;;gate-open-p


(defclass queue (scheduler-object)
  (:documentation "A FIFO. ENQUEUE and DEQUEUE are atomic."))

(defmethod enqueue ((self queue) object)
  (declare (ignore self object))
  (error "enqueue not implemented yet")) ;;gate-open-p

(defmethod dequeue ((self queue) object)
    Dequeuing is provided with a waiting facility, so a process that
    tries to dequeue an object from a queue will (optionally) wait, if
    the queue is empty, until something is placed on it.
  (declare (ignore self object))
  (error "dequeue not implemented yet")) ;;dequeue


(defclass process-lock (scheduler-object)
    A process-lock is a defstruct which provides a mechanism for
    interlocking process execution. Lock objects are created with
    make-process-lock. A process-lock is either free or it is seized
    by exactly one process. When a process is seized, a non-nil value
    is stored in the lock object (in the slot named locker). Usually
    this is the process which seized the lock, but can be any Lisp
    object other than nil. Any process which tries to seize the lock
    before it is released will block. This includes the process which
    has seized the lock; the with-process-lock macro protects against
    such recursion.
    ")) ;;process-lock
;; The simpliest will be to make it only a proxy for a process lock
;; in the scheduler.

(defun make-process-lock (&key name)
    This function creates a new lock object. The value of the :name
    keyword argument should be a string which is used for
    documentation and in the whostate of processes waiting for the
    lock. (There are additional keyword argument for internal use not
    listed. They should not be set by user code.)
  (declare (ignore name))
  (error "make-process-lock not implemented yet")) ;;make-process-lock

(defmethod process-lock ((lock process-lock)
                         &optional lock-value whostate timeout)
    This function seizes lock with the value lock-value (which must be non-nil).
  (declare (ignore lock lock-value whostate timeout))
  (error "process-lock not implemented yet")) ;;process-lock

(defmethod process-unlock ((lock process-lock) &optional lock-value)
    This function unlocks lock, setting the value in the locker slot to nil.)
    The value of the locker slot of the lock must be the same as the
    lock-value argument. If it is not, an error is signaled.
  (declare (ignore lock lock-value))
  (error "process-unlock not implemented yet")) ;;process-unlock

(defmethod process-lock-locker ((lock process-lock))
    This function returns the value of the locker slot of lock.
    This value is usually the process holding the lock, but can be
    any Lisp value. If the value is nil, the lock is not locked.
  (declare (ignore lock))
  (error "process-lock-locker not implemented yet")) ;;process-lock-locker

(defmethod process-lock-p ((object t))
    Returns true if object is a lock (as returned by make-process-lock)
    and returns nil otherwise.
  (declare (ignore object))
  nil) ;;process-lock-p

(defmethod process-lock-p ((object process-lock))
    Returns true if object is a lock (as returned by make-process-lock)
    and returns nil otherwise.
  (declare (ignore object))
  t) ;;process-lock-p

(defmacro with-process-lock ((lock &key norecursive) &body body)
    This macro executes the body with lock seized.
  (declare (ignore lock norecursive body))
  (error "with-process-lock not implemented yet")) ;;with-process-lock


;; (defparameter forks (make-circular
;;                       (loop for i from 0 below 5 collect (make-lock))))
;; (defparameter philosophers
;;   (loop
;;    for i from 0 below 5
;;    for prev = (car forks) then next
;;    for next in (cdr forks)
;;    collect
;;    (make-process
;;     (let ((left prev) (rigth next))
;;       (lambda ()
;;         (loop
;;          (when (with-lock-held (left 5)
;;                  (format t "philosopher ~D took left fork.~%" i)
;;                  (force-output)
;;                  (when (with-lock-held (right 5)
;;                          (format t "philosopher ~D took right fork.~%" i)
;;                          (force-output)
;;                          (format t "philosopher ~D is eating~%" i)
;;                          (force-output)
;;                          (sleep (random 5))
;;                          t)
;;                    (format t "philosopher ~D drop right fork.~%" i))
;;                  (force-output)
;;                  t)
;;            (format t "philosopher ~D drop left fork.~%" i)
;;            (force-output))
;;          (format t "philosopher ~D philosofies.~%" i)
;;          (force-output)))))));;philosophers

;;;;; (defclass process () ())
;;;;; (defun make-process (function &key name))
;;;;; (defgeneric destroy-process (process))
;;;;; (defgeneric suspend-process (process))
;;;;; (defgeneric resume-process (process))
;;;;; (defvar *ALL-PROCESSES* '()
;;;;;   "A list of all alive processes.")
;;;;; (defvar *CURRENT-PROCESS*  nil
;;;;;   "The current process")
;;;;; (defun ALL-PROCESSES ()
;;;;;   "Return a list of all the live processes.")
;;;;; (defmacro ATOMIC-DECF (place &option increment)
;;;;;   "Decrements the reference by delta in a single atomic operation")
;;;;; (defmacro ATOMIC-INCF (place &option increment)
;;;;; "Increaments the reference by delta in a single atomic operation")
;;;;; (defmacro ATOMIC-POP (place)
;;;;; "Atomically pop place.")
;;;;; (defmacro ATOMIC-PUSH (element place)
;;;;; "Atomically push object onto place.")
;;;;; (defun CURRENT-PROCESS ()
;;;;; "Returns the current process.")
;;;;; (defun DESTROY-PROCESS (process)
;;;;; "Destroy a process. The process is sent a interrupt which throws to
;;;;;   the end of the process allowing it to unwind gracefully.")
;;;;; (defun DISABLE-PROCESS (process)
;;;;;   "Disable process from being runnable until enabled.")
;;;;; (defun ENABLE-PROCESS (process)
;;;;; "Allow process to become runnable again after it has been disabled.")
;;;;; (defclass LOCK () ())
;;;;; (defun MAKE-LOCK (...))
;;;;; "Make a process which will run FUNCTION when it starts up.  By
;;;;;   default the process is created in a runnable (active) state.
;;;;;   If FUNCTION is NIL, the process is started in a killed state; it may
;;;;;   be restarted later with process-preset.
;;;;;   :NAME
;;;;; 	A name for the process displayed in process listings.
;;;;;   :RUN-REASONS
;;;;; 	Initial value for process-run-reasons; defaults to (:ENABLE).  A
;;;;; 	process needs a at least one run reason to be runnable.  Together with
;;;;; 	arrest reasons, run reasons provide an alternative to process-wait for
;;;;; 	controling whether or not a process is runnable.  To get the default
;;;;; 	behavior of MAKE-PROCESS in Allegro Common Lisp, which is to create a
;;;;; 	process which is active but not runnable, initialize RUN-REASONS to
;;;;; 	NIL.
;;;;; 	Initial value for process-arrest-reasons; defaults to NIL.  A
;;;;; 	process must have no arrest reasons in order to be runnable.
;;;;; 	An alist of initial special bindings for the process.  At
;;;;; 	startup the new process has a fresh set of special bindings
;;;;; 	with a default binding of *package* setup to the CL-USER
;;;;; 	package.  INITIAL-BINDINGS specifies additional bindings for
;;;;; 	the process.  The cdr of each alist element is evaluated in
;;;;; 	the fresh dynamic environment and then bound to the car of the
;;;;; 	element."
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; "Return the real time elapsed since the given process was last
;;;;;   descheduled. The returned time is a double-float in seconds."
;;;;; NIL
;;;;; "Interrupt process and cause it to evaluate function."
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; "Restart process, unwinding it to its initial state and calls
;;;;;   function with args."
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; "Return the accrued real time elapsed while the given process was
;;;;;   scheduled. The returned time is a double-float in seconds."
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; "Return the accrued run time elapsed for the given process. The returned
;;;;;   time is a double-float in seconds."
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; "Causes the process to wait until predicate returns True. Processes
;;;;;   can only call process-wait when scheduling is enabled, and the predicate
;;;;;   can not call process-wait. Since the predicate may be evaluated may
;;;;;   times by the scheduler it should be relative fast native compiled code.
;;;;;   The single True predicate value is returned."
;;;;; NIL
;;;;; "Wait until FD is usable for DIRECTION and return True. DIRECTION should be
;;;;;   either :INPUT or :OUTPUT. TIMEOUT, if supplied, is the number of seconds to
;;;;;   wait before giving up and returing NIL."
;;;;; NIL
;;;;; "Causes the process to wait until predicate returns True, or the
;;;;;   number of seconds specified by timeout has elapsed. The timeout may
;;;;;   be a fixnum or a float in seconds.  The single True predicate value is
;;;;;   returned, or NIL if the timeout was reached."
;;;;; NIL
;;;;; "Return the process state which is either Run, Killed, or a wait reason."
;;;;; NIL
;;;;; "Allow other processes to run."
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; "Restart process by unwinding it to its initial state and calling its
;;;;;   initial function."
;;;;; NIL
;;;;; "Show the all the processes, their whostate, and state. If the optional
;;;;;   verbose argument is true then the run, real, and idle times are also
;;;;;   shown."
;;;;; NIL
;;;;; NIL
;;;;; NIL
;;;;; "Execute the body with the lock held. If the lock is held by another
;;;;;   process then the current process waits until the lock is released or
;;;;;   an optional timeout is reached. The optional wait timeout is a time in
;;;;;   seconds acceptable to process-wait-with-timeout.  The results of the
;;;;;   body are return upon success and NIL is return if the timeout is
;;;;;   reached. When the wait key is NIL and the lock is held by another
;;;;;   process then NIL is return immediately without processing the body."
;;;;; NIL
;;;;; "Executes body and returns the values of the last form in body. However, if
;;;;;   the execution takes longer than timeout seconds, abort it and evaluate
;;;;;   timeout-forms, returning the values of last form."
;;;;; NIL
;;;;; "Execute the body the scheduling disabled."
;;;;; NIL
;;;;; NIL
;;;;; *

Process for clisp

clisp cannot handle thread (yet), but it has linux:|fork|, (or FFI
fork on unix).  Hence the proposition to implement a PROCESS API for
clisp based on unix processes.

Communication between threads is done with shared variables (a common
address space), and conditions and mutex for synchronization.

Communication between (unix) processes can be done with:

    * shared memory (only a part of the address space) (mmap and SVID shm)
        + connection can be established after forking.
        + no copying need (if the data could be allocated into
                           the shared memory).
        - no embeded synchronization.

    * process messaging (SVID msg)
        + connection can be established after forking.
        + synchronization (reader may blocks untin a message is received).

    * sockets
        + connection can be established after forking.
        + connection can be established between distributed processes.
        + synchronization (reader may blocks untin a message is received).
        - point to point.
        - uses network resources.

    * named pipes
        + connection can be established after forking.
        + synchronization (reader may blocks untin a message is received).
        - mainly point to point.

    * pipes
        + synchronization (reader may blocks untin a message is received).
        - pipes must be established before forking.
        - mainly point to point.

Synchronization between processes can be done with:
    * semaphores (SVID sem)
    * signals (asynchronously).



    The first process becomes the scheduler (and forks immediately
    a main process).

    The scheduler is a server that manages the processes, the locks,
    the shared variables. It communicates with its children thru SVID
    messages, and signals.

    Shared variables are stored as a byte sequence in a shared
    memory. Only values that are printable readably can be stored in
    a shared variable, and only if their types were defined before
    forking the processes.  The value is therefore copied deeply. No
    object. No structure defined after a fork (clisp structures are
    printable readably).

    It seems we cannot have a symbol-macro at the same time as a
    special variable... So we'll need a special API.  

    For other values, such as PROCESS or LOCK objects, the main
    objects are stored in the heap of the scheduler, and the processes
    hold proxies that synchronize their state with their master in the

    File descriptors? Forking doen't duplicates the file structure,
    include the file _marker_.

==> creation of shared variables,
    represented with a symbol macro,
    implemented as shared memory (mmap or shm?)

==> creation of locks
    represented with objects,
    implemented as semaphores.

- fork (for this is the only multiprocessing primitive available in clisp).

- processes use pipe to communicate between them.
  (alternatively, they could use socket if distribution over serveral hosts
                was needed)

- 1-parent/multiple-children.
;; (with-open-file (out "/tmp/test" :direction :output :if-does-not-exist :create :if-exists :supersede) (format out "abcdefghijk") (force-output out) (let ((pid (linux:|fork|))) (if (zerop pid) (format out "ABCDEFGHIJK") (format out "0123456789")) (force-output out) (when (zerop pid) (sleep 4) (format t "child quits~%") (force-output) (ext:quit))))



- queued messages

make-process function &key name
process-interrupt process function
    functions can't be passed as function,
    it must be a _symbol_ denoting a function.
    (because functions are not printable readably).

destroy-process process
processp object
process-name process
process-state process

process-wait reason predicate
process-wait-with-timeout reason timeout predicate
    predicate can be evaluated
    but no sharing of memory ==> IPC must change state.
without-scheduling &body body
    free from interruptions, thru PM, ok, but otherwise needless
    since there's not memory sharing.

    there is no way to really _yield_ the CPU in unix
    pause(2) waits for a signal, we could ask the MP to do some scheduling...

disable-process process
    kill -STOP
enable-process process
    kill -CONT
    but master could still communicate with the process, signaling messages.

restart-process process
atomic-incf reference
atomic-decf reference
    no memory sharing, but the mp could forward global values.

    each fork ==> duplication of file descriptors (and other resources).
              ==> problem with destructors (database, protocols, etc).

make-lock &optional name
with-lock-held (place &optional state) &body body
    We would need the list of global variables that are modified
    and that should be synchronized.

    Shallow references cannot be transmitted, only a deep copy.
    And only objects printable readably!

;; (ffi:def-call-out unix-read  (:name "read")
;;   (:arguments (fd ffi:int) (buf ffi:c-pointer) (nbytes linux:|size_t|))
;;   (:return-type linux:|ssize_t|)
;;   (:library "/lib/libc.so.6")
;;   (:language :stdc))
;; (ffi:def-call-out unix-write  (:name "write")
;;   (:arguments (fd ffi:int) (buf ffi:c-pointer) (nbytes linux:|size_t|))
;;   (:return-type linux:|ssize_t|)
;;   (:library "/lib/libc.so.6")
;;   (:language :stdc))
;; (defun test-unix-pipe-io ()
;;   (multiple-value-bind (res fds) (linux:|pipe|)
;;     (ffi:with-foreign-string (fstr flen fsiz "Hello")
;;       (print `(wrote ,(unix-write (aref fds 1) fstr fsiz)))
;;       (ffi:with-foreign-object (buf '(ffi:c-array ffi:uchar 512))
;;         (let ((rlen (unix-read (aref fds 0) buf 512)))
;;           (print `(read ,rlen))
;;           (dotimes (i rlen)
;;             (princ (code-char (ffi:element (ffi:foreign-value buf)
;;                                            i)))))))));;test-unix-pipe-io
;; (print `(read ,rlen))
;; (dotimes (i rlen)
;;   (princ (code-char (ffi:element (ffi:foreign-value buf)
;;                                  i))))
;; (defun copy-from-c-buffer (buffer buflen sequence start)
;;   (typecase sequence
;;     (cons (let ((current (nthcdr start sequence)))
;;             (dotimes (i buflen)
;;               (setf (car current) (ffi:element (ffi:foreign-value buffer) i)
;;                     current (cdr current)))))
;;     (string (warn "You should give a string to UNIX-READ-SEQUENCE!")
;;             (
;;              (vector sequence)
;;              (setf
;;               )))));;copy-from-c-buffer

(defun valid-operator-name-p (string)
  "Test if STRING names a function, macro, or special-operator."
  (let ((symbol (let ((*read-eval* nil)) (read-from-string string))))
    (or (fboundp symbol)
        (macro-function symbol)
        (special-operator-p symbol)))) ;;valid-operator-name-p

;; (or (ignore-errors (return (ext:arglist fname))))

(defconstant +pipe-buffer-size+ 4096)

(defvar fd)

(defun unix-read (&rest args)
  (declare (ignore args))
  (error "unix-read Not implemented yet"))

(defun copy-from-c-buffer (&rest args)
  (declare (ignore args))
  (error "copy-from-c-buffer Not implemented yet"))

(defun unix-read-sequence (sequence &key (start 0) (end nil))
  (let ((count (if end (- end start) (length sequence))))
    (when (zerop count)
      (return-from unix-read-sequence (values 0 sequence)))
    (ffi:with-foreign-object (buffer
                              '(ffi:c-array ffi:uchar +pipe-buffer-size+))
      (loop named :reader do
           (let ((rlen (unix-read fd buffer (min count +pipe-buffer-size+))))
               ((< 0 rlen)
                (return-from :reader
                  (values rlen (copy-from-c-buffer buffer rlen sequence start))))
               ((= 0 rlen)
                (return-from :reader (values rlen sequence)))
               (t (case linux:|errno|
                    ((linux:|EAGAIN| linux:|EINTR|))
                    ((linux:|EPIPE|) (return-from :reader nil)) ; EOF
                     (error "unix read: ~A" (linux:|strerror| linux:|errno|))
                     ))))))))) ;;unix-read-sequence

;;  returns a buffered input STREAM. function is a FUNCTION of 0
;; arguments that returns either NIL (stands for end-of-stream) or up to
;; three values string, start, end. READ-CHAR returns the CHARACTERs of
;; the current string one after another, as delimited by start and end,
;; which default to 0 and NIL, respectively. When the string is consumed,
;; function is called again. The string returned by function should not
;; be changed, otherwise function should copy the string with COPY-SEQ or
;; SUBSEQ beforehand. mode determines the behavior of LISTEN when the
;; current string buffer is empty:

;;   NIL   the stream acts like a FILE-STREAM, i.e. function is called
;;   T   the stream acts like an interactive stream without
;; end-of-stream, i.e. one can assume that further characters will always
;; arrive, without calling function FUNCTION this FUNCTION tells, upon
;; call, if further non-empty strings are to be expected.
;;   CLEAR-INPUT discards the rest of the current string, so function
;;   will be called upon the next READ-CHAR operation.

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun clisp-version ()
    "Return the major and minor of clisp version as a floating point number."
    (let* ((v (lisp-implementation-version))
           (p (position (character ".") v))
           (p (position-if (complement (function digit-char-p)) v :start (1+ p))))
      (read-from-string v nil nil :end p))))

(defun make-unix-pipe (&key (element-type 'character)
                       (external-format custom:*foreign-encoding*)
                       (buffered t))
  (multiple-value-bind (res fds) (linux:|pipe|)
    (unless (= 0 res)
      (error "unix pipe: ~A" (linux:|strerror| linux:|errno|)))
    (let ((inp (ext:make-buffered-input-stream
                (lambda ()
                  (aref fds 0)
                  :direction :input
                  :element-type element-type
                  :external-format external-format
                  :buffered buffered)
                #+#.(cl:when (cl:<= 2.33 (com.informatimago.susv3.process::clisp-version))
          (out (ext:make-stream (aref fds 1)
                                :direction :output
                                :element-type element-type
                                :external-format external-format
                                :buffered buffered)))
      ;;(print (list  (aref fds 0) inp (aref fds 1) out))
      ;;(linux:|close| (aref fds 0))
      ;;(linux:|close| (aref fds 1))
      (values inp out))))

;;   returns a buffered output STREAM. function is a FUNCTION expecting
;; one argument, a SIMPLE-STRING. WRITE-CHAR collects the CHARACTERs in a
;; STRING, until a newline character is written or
;; FORCE-OUTPUT/FINISH-OUTPUT is called. Then function is called with a
;; SIMPLE-STRING as argument, that contains the characters collected so
;; far. CLEAR-OUTPUT dicards the characters collected so far.

(defun make-unix-pipe/does-not-work (&key (element-type 'character)
                                     (external-format custom:*foreign-encoding*)
                                     (buffered t))
  (multiple-value-bind (res fds) (linux:|pipe|)
    (unless (= 0 res)
      (error "unix pipe: ~A" (linux:|strerror| linux:|errno|)))
    (let ((inp (ext:make-stream (aref fds 0)
                                :direction :input
                                :element-type element-type
                                :external-format external-format
                                :buffered buffered))
          (out (ext:make-stream (aref fds 1)
                                :direction :output
                                :element-type element-type
                                :external-format external-format
                                :buffered buffered)))
      ;;(print (list  (aref fds 0) inp (aref fds 1) out))
      ;;(linux:|close| (aref fds 0))
      ;;(linux:|close| (aref fds 1))
      (values inp out)))) ;;make-unix-pipe/does-not-work

(defparameter *out* nil)
(defparameter *inp* nil)
(multiple-value-setq (*inp* *out*) (make-unix-pipe))
(when (zerop (linux:|fork|))
  (print :child)(force-output)
  (loop for i = (read *inp*) while (< i 10)
     do (format t "~%got ~D~%" i) (force-output))
(loop for i from 0 to 10 do (print i *out*)(force-output *out*)(princ "."))

(loop :repeat 2 :do
      (lambda (signal)  (princ " Got signal ") (throw :hot-potatoe signal)))
     (catch :hot-potatoe
       (princ " Looping ")
       (loop do (sleep 5) (princ ".")))
     (princ " Caught "))

(linux:set-signal-handler linux:SIGALRM
                          (let ((i 0)) (lambda (signal)
                                         (princ ".") (print (incf i) *out*) (force-output *out*))))
(linux:ualarm 1000000 1000000)

(defclass process ()
  ((name :reader name
         :type string
         :initform "Unnamed"
         :initarg :name)
   (pid :reader pid
        :type integer
        :initform 0
        :initarg :pid))
  (:documentation "A process proxy."));;process

(defparameter *parent*
  (make-instance 'process :name "parent" :pid (linux:getppid)))

(defun parent ()
RETURN: The parent process.


(defun make-xterm-io-stream (&key display)
  (declare (ignore display))
  (error "~S: Implemented in com.informatimago.clisp" 'make-xterm-io-stream))

(defun register-worker (pid)
  (declare (ignore pid))
  (error "~S: not implemented yet." 'register-worker))

(defun parse-one-command (pid)
  (declare (ignore pid))
  (error "~S: not implemented yet." 'parse-one-command))

(defun server-main (&key display)
  (if (or display (find-package "SWANK"))
      (let* ((xterm-io (make-xterm-io-stream :display display))
             (*standard-output* xterm-io)
             (*standard-input*  xterm-io)
             (*error-output*    xterm-io)
             (*terminal-io*     xterm-io)
             (*query-io*        xterm-io)
             (*debug-io*        xterm-io))
        (iotask-enqueue *standard-input*
                        (make-buffered-discipline (function server-input))
          (let ((*standard-input* ext:*keyboard-input*))
            (iotask-enqueue ext:*keyboard-input*
                            (make-keyboard-discipline (function server-input))
            (iotask-poll-loop))))) ;;server-main

(defvar *pipe-format* (ext:make-encoding
                       :charset 'charset:utf-8
                       :line-terminator :unix))

(defparameter +server-port+ 15000)

(defun server ()
  (let ((lsock (socket:socket-server +server-port+)))
            (when (socket:socket-wait lsock 0)
              (let ((remote (socket:socket-accept lsock
                                                  :element-type 'character
                                                  ;; :external-format
                                                  :buffered t
                                                  :timeout 1)))
                (when remote
                  ;; got an incoming connection, let's fork a worker
                  ;; but first, create a socket and connect to it to be
                  ;; able to communicate with this worker.
                  (let ((pid (linux:fork)))
                      ((< pid 0) ;; error
                       (error "Could not fork a worker."))
                      ((= pid 0) ;; child
                      (t ;; parent
                       (register-worker pid)
                       (format t "~& "))))
      (close lsock)))) ;;server

(defun list-insert-separator (list separator)
RETURN:  A list composed of all the elements in `list'
         with `separator' in-between.
EXAMPLE: (list-insert-separator '(a b (d e f)  c) 'x)
         ==> (a x b x (d e f) x c)
  (do ((result (if list (list (car list))))
       (list (cdr list) (cdr list)))
      ((null list) (nreverse result))
    (push separator result)
    (push (car list) result)))

(defun char-or-string-p (object)
  (or (characterp object) (stringp object)))

(defun pjb-unsplit-string (string-list &optional (separator " "))
  "Does the inverse than pjb-split-string. If no separator is provided
then a simple space is used."
  (check-type separator (or string char))
  (apply 'concatenate 'string
         (mapcar (lambda (object)
                   (if (stringp object)
                       (format nil "~A" object)))
                 (list-insert-separator string-list separator))))

(defun pjb-split-string (string &optional separators)
note:   current implementation only accepts as separators
        a string containing only one character.
  (setq separators (or separators " ")
        string (string string))
  (let ((sep (aref separators 0))
        (chunks  '())
        (position 0)
        (nextpos  0)
        (strlen   (length string)) )
    (while (<= position strlen)
      (while (and (< nextpos strlen)
                  (char/= sep (aref string nextpos)))
        (setq nextpos (1+ nextpos)))
      (setq chunks (cons (subseq string position nextpos) chunks))
      (setq position (1+ nextpos))
      (setq nextpos  position) )
    (nreverse chunks))) ;;PJB-SPLIT-STRING

(defun ipv4-address-p (address)
PRE:     (or (string address) (symbol address))
RETURN:  Whether ADDRESS as the aaa.bbb.ccc.ddd IPv4 address format.
  (let ((bytes (pjb-split-string (string address) ".")))
    (and (= 4 (length bytes))
         (block :convert
            (mapcar (lambda (byte)
                      (multiple-value-bind (val eaten) (read-from-string byte)
                        (if (and (= eaten (length byte)) (integerp val)
                                 (<= 0 val 255))
                            (return-from :convert nil))))
                    (pjb-split-string address "."))))))) ;;IPV4-ADDRESS-P

(defun server-repl ()
  (do ((hist 1 (1+ hist))
       (+eof+ (gensym)))
    (format t "~%~A[~D]> " (package-name *package*) hist)
     (setf +++ ++   ++ +   + -   - (read *standard-input* nil +eof+))
     (when (or (eq - +eof+)
               (member - '((quit)(exit)(continue)) :test (function equal)))
       (return-from server-repl))
     (setf /// //   // /   / (multiple-value-list (eval -)))
     (setf *** **   ** *   * (first /))
     (format t "~& --> ~{~S~^ ;~%     ~}~%" /)))) ;;server-repl

(defvar +eof+       (gensym))
(defvar *debugging* nil)

(defvar *prompt* "> ")
(defun configuration-repl (&key (debugging *debugging*))
  (catch :configuration-repl-exit
       (format t "~&~A " *prompt*) (finish-output)
       (let ((sexp (read *standard-input* nil +eof+)))
         (if sexp
             (if debugging
                 (parse-one-command sexp)
                 (handler-case (parse-one-command sexp)
                   (error (err)
                     (apply (function format) *error-output*
                            (simple-condition-format-control err)
                            (simple-condition-format-arguments err)))))
             (throw :configuration-repl-exit nil)))))) ;;configuration-repl

(defun configuration-repl-start ()
  (format t "~&~A " *prompt*)

(defun configuration-repl-input (line)
  (let ((sexp (read-from-string line nil +eof+)))
    (unless (eq +eof+ sexp)
      (if *debugging*
          (parse-one-command sexp)
          (handler-case (parse-one-command sexp)
            (error (err)
              (apply (function format) *error-output*
                     (simple-condition-format-control err)
                     (simple-condition-format-arguments err)))))
      (configuration-repl-start)))) ;;configuration-repl-input

        (load "loader.lisp")
        (configuration-repl :debugging t)
        (filter append allow "")
        (filter append deny all)
        (connections max-number 40)
        (connections enable)
        (configuration save "/tmp/server.conf")

(defstruct iotask  stream process-event name)

(defparameter *iotasks*   '())
(defparameter *bon-grain* '()
  "Sublist of *iotask* which can be handled by socket:socket-wait.")
(defparameter *ivray*     '()
  "Sublist of *iotask* which cannot be handled by socket:socket-wait.")

(defun iotask-enqueue (stream process-event &optional name)
  (let ((task (make-iotask :stream stream
                           :process-event process-event
                           :name name)))
    (push task *iotasks*)
    (handler-case (socket:socket-status (iotask-stream task) 0)
      (error     ()                           (push task *ivray*))
      (:no-error (s n) (declare (ignore s n)) (push task *bon-grain*)))
    )) ;;iotask-enqueue

(defun iotask-dequeue (task)
  (setf *iotasks*   (delete task *iotasks*))
  (setf *bon-grain* (delete task *bon-grain*))
  (setf *ivray*     (delete task *ivray*)))

(defun iotask-poll-loop ()
  (loop ;; each 0.1 seconds, see second argument of socket-status.
     (when (null *iotasks*) (return))
     (map nil
          (lambda (task status)
            (when status (funcall (iotask-process-event task) task status)))
          (mapcar (lambda (task)
                    (let ((stream (iotask-stream task)))
                        ((input-stream-p stream)
                         (if (listen stream)
                             (if (output-stream-p stream) :output nil)))
                        ((output-stream-p stream) :output)
                        (t  nil))))
     (map nil
          (lambda (task status)
            (when status (funcall (iotask-process-event task) task status)))
           (mapcar (function iotask-stream) *bon-grain*) 0.1))))

(defun make-buffered-discipline (process-input)
  (lambda (task event)
    (when (member event '(:input :error))
      (funcall process-input task (read-line (iotask-stream task))))))


(defconstant +cr+   13)
(defconstant +bs+    8)
(defconstant +del+ 127)

(defun make-keyboard-discipline (process-input)
  (let ((buffer (make-array '(128) :element-type 'character :fill-pointer 0)))
    (lambda (task event)
      (when (eq :input event)
        (let* ((ich (read-char (iotask-stream task)))
               (ch  (system::input-character-char ich)))
            ((null ch))
            ((= (char-code ch) +cr+)
             (funcall process-input
                      task (subseq buffer 0 (fill-pointer buffer)))
             (setf (fill-pointer buffer) 0))
            ((or (= (char-code ch) +bs+) (= (char-code ch) +del+))
             (when (< 0 (fill-pointer buffer))
               (princ (code-char +bs+))
               (princ " ")
               (princ (code-char +bs+))
               (decf (fill-pointer buffer))))
             (princ ch)
             (vector-push ch buffer))))
        (finish-output))))) ;;make-keyboard-discipline

(defun server-input (task line)
  (if (string-equal "(QUIT)" line)
      (iotask-dequeue task)
      (configuration-repl-input line))) ;;server-input

(progn (ext:with-keyboard
           (socket:socket-status (list ext:*keyboard-input*) nil)
         (unread-char  (system::input-character-char
                        (read-char ext:*keyboard-input*))
       (print (read-line)))

(defun child.send (sexp)
  (print sexp child.pipe)
  (linux:|kill| child.pid linux:|SIGUSR1|))

(child.send '(throw :exit 1))
(child.send '(print test1))

;; set-signal-handler --> ONE signal is queued and processed when the handler exists.

(defmessage symeval-req (symbol process))
(defmessage symeval-res (symbol process status value))

(defmessage profile-req ())
(defmessage profile-res (status profile-data))

(defmessage newproc-req (name class reset-action run-reasons arrest-reasons
                              resume-hook suspend-hook initial-bindings))
(defmessage newproc-res (process status))

(defmessage setproc-req (process attribute value))
(defmessage setproc-res (process status))

(defmessage preset-req (process function args))
(defmessage preset-res (process status))

(defmessage reset-req (process))
(defmessage reset-res (process status))

(defmessage killproc-req (process wait))
(defmessage killproc-req (process status))

(defmessage interrupt-req (process function args))
(defmessage interrupt-res (process status value))
(defmessage disallow-interrupts-req ())
(defmessage disallow-interrupts-res (status))
(defmessage allow-interrupts-req ())
(defmessage allow-interrupts-res (status))

(defmessage disallow-scheduling-req ())
(defmessage disallow-scheduling-res (status))
(defmessage allow-scheduling-req ())
(defmessage allow-scheduling-res (status))

(defmessage start-wait-req (gate period timeout))
(defmessage start-wait-res (status gate counter))
(defmessage stop-wait-req ())
(defmessage stop-wait-res (status))

(defmessage timeout-req (timeout))
(defmessage timeout-res (status)) ;; immediate ; interrupt when timeout.

(defmessage lock-req (lock value timeout))
(defmessage lock-res (status))

(defmessage unlock-req (lock value))
(defmessage unlock-res (status))


;; defmessage
;; scheduler

(defmacro with-timeout ((seconds &body timeout-forms) &body body)
  ;; implement with alarm and
  (declare (ignore seconds timeout-forms body))
  #+(or)(with-signal-handler linux:|SIGALRM|)
  (error "not implemented yet")
  ;; or implement with the scheduler when it's started.