Background Processing

Asynchronous events and family IPC

While a PicoLisp program is running in the Unix environment, it may have to react to three kinds of external events: Signals may be sent to a process at any time, and then it is up to the program to handle or ignore them.

On the other hand, file descriptors and timers must be explicitly taken care of, except for two limited cases where also signals can be used (see SIGIO and SIGALRM below).

Signals

Signals are sent from the Unix kernel or from user processes. PicoLisp can sind a signal to another process (or even to itself) with the kill function.

PicoLisp makes some signals available to application programs, and handles some of them by itself. The remaining signals are left with their default action (usually either being ignored or terminating the process).

The signals available to PicoLisp application programs are: They are initially all NIL, and the corresponding signals will be received but have no effect.

SIGHUP, SIGUSR1, SIGUSR2, SIGIO, SIGALRM and SIGWINCH can be used freely for any purpose. You can try them interactively. Open two terminals, and do in the first one:
   $ pil +
   : (de *Hup (msg 'SIGHUP))
   -> *Hup
   : (de *Sig1 (msg 'SIGUSR1))
   -> *Sig1
   : *Pid
   -> 14081
Then in the second one:
   $ kill -HUP 14081
   $ kill -USR1 14081
You will see the messages "SIGHUP" and "SIGUSR1" appear in the first terminal.

SIGTSTP and SIGCONT are handled by PicoLisp directly. A STOP signal is typically sent from the terminal when Ctrl-Z is pressed, to suspend the current process (putting it into the background). Before doing so, the value of *TStp1 is executed, the terminal mode is cleaned up, and PicoLisp drops back into the shell.

Then, when the shell-builtin fg (foreground) is executed, a CONT signal is sent to PicoLisp, causing it to resume, execute possible contents of *TStp2, and restore the terminal mode.

If SIGTERM is received by PicoLisp and the execution of *Term returns non-NIL, it will be ignored (PicoLisp will not terminate). Otherwise, PicoLisp tries to terminate all child processes, and will postpone its termination until all children exited (possibly causing repeated execution of this procedure).

The signals handled by PicoLisp itself are: SIGINT is typically sent from the terminal when Ctrl-C is pressed. It is initially set to the default behavior, causing the process to terminate. In that case, PicoLisp sends SIGTERM to itself (which is then handled as described above). As soon as a REPL is started, however, SIGINT will no longer terminate, but interrupt the running PicoLisp program, and start a debug REPL.

SIGCHLD is set to normal behavior, calling waitpid() to wait for a child process.

SIGPIPE signals are ignored initially, and set to default behavior in read-piped child processes (as started by in and pipe).

SIGTTIN and SIGTTOU are ignored.

Asynchronous tasks

At certain times, the PicoLisp interpreter is idle. This happens while In these cases, PicoLisp should not consume any CPU cycles, but still be able to wake up when a key is typed by the user, data arrive on a network connection, or periodical tasks need to be performed.

On POSIX systems this can be done with a poll(2) system call. It waits for one of a set of file descriptors to become ready for reading or writing (I/O multiplexing), or until a timeout occurs. PicoLip interfaces to poll(2) via the *Run global variable.

*Run holds a normal association list and can be manipulated directly with list functions, but more conveniently with task. Whenever the interpreter waits in one of the above four situations, it continuously polls for all file descriptors and timers specified in *Run, and runs the associated code for whatever occurs next.

Family Inter-Process Communication (IPC)

In addition to the contents of *Run, PicoLip may check further file descriptors (from pipes) to The pipes between parent and children are set up automatically in fork and have the following structure:
            +--------------------------+ Mic
            |
            |  +-----------------+ Tell         <Child>
            |  |
            |  +-----------------> Hear
<Parent>    |  |
            |  |
   Spkr <---+  |
            |  |
            |  |
            |  +-----------------+ Tell
            |  |
            |  +-----------------> Hear         <Child>
            |
            +--------------------------+ Mic
Everything a process writes into its Mic (microphone) pipe goes to the Spkr (speaker) of its parent, and everything a process writes to its Tell pipe goes - through the parent - to the Hear pipes of either all its siblings (broadcast) or just to a single specific one.

In order to do that, a PicoLisp process must read-poll its Spkr and Hear pipes, and the read ends from all children's Tell pipes.

Also, to avoid blocking when writing to a child, it must write-poll the write ends to all children's Hear pipes.

Then, when data are available from a child's Tell pipe or from Spkr, or a pipe to a child's Hear end is ready for writing, all pending data transfers must be properly handled.

Fortunately, application programs don't have to be concerned about these details. The database uses the above mechanisms for the internal synchronization of multi-user access, and explicit family IPC can be done with the tell function.

https://picolisp.com/wiki/?background

08may22    abu
Revision History