Background ProcessingAsynchronous 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:
- A signal may arrive
- A file descriptor may become ready
- A timer may expire
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).
SignalsSignals 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:
- SIGHUP via the *Hup global
- SIGUSR1 via the *Sig1 global
- SIGUSR2 via the *Sig2 global
- SIGIO via the sigio function
- SIGALRM via the alarm function
- SIGWINCH via the *Winch global
- SIGTSTP via the *TStp1 global
- SIGCONT via the *TStp2 global
- SIGTERM via the *Term global
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 -> 14081Then in the second one:
$ kill -HUP 14081 $ kill -USR1 14081You 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:
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 tasksAt certain times, the PicoLisp interpreter is idle. This happens while
- waiting for a key typed in the REPL
- waiting while syncing database changes
- pausing on a wait call
- listening on a socket
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
- its parent process (if it was forked from another PicoLisp process) and
- all its child processes (if any)
+--------------------------+ Mic | | +-----------------+ Tell <Child> | | | +-----------------> Hear <Parent> | | | | Spkr <---+ | | | | | | +-----------------+ Tell | | | +-----------------> Hear <Child> | +--------------------------+ MicEverything 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.