In the previous section, we have seen several predicates that allow XSB to
create other processes. However, these predicates offer only a very limited
way to communicate with these processes. The predicate
spawn_process/5
and friends come to the rescue. It allows to spawn
any process (including multiple copies of XSB) and redirect its standard
input and output to XSB I/O ports. XSB can then write to the process and
read from it. The section of socket I/O describes yet another mode of
interprocess communication.
In addition, the predicate pipe_open/2 described in this section lets one create any number of pipes (that do not need to be connected to the standard I/O port) and talk to child processes through these pipes.
All predicates in this section, except pipe_open/2 and fd2ioport/2, must be imported from module shell. The predicates pipe_open/2 and fd2ioport/2 must be imported from file_io.
The next three parameters of spawn_process
are XSB I/O ports
to the process (leading to the subprocess standard input), from the process
(from its standard output), and a port capturing the
subprocess standard error output. The last parameter is the system process id.
Here is a simple example of how it works.
| ?- import file_flush/2, file_read_line_atom/2 from file_io. | ?- import file_nl/1 , file_write/2 from xsb_writ. | ?- spawn_process([cat, '-'], To, From, Stderr, Pid), file_write(To,'Hello cat!'), file_nl(To), file_flush(To,_), file_read_line_atom(From,Y). To = 3 From = 4 Stderr = 5 Pid = 14328 Y = Hello cat! yes
Here we created a new process, which runs the ``cat'' program with argument ``-''. This forces cat to read from standard input and write to standard output. The next line writes a newline-terminated string to the XSB port To the, which is bound to the standard input of the cat process. The process then copies the input to the standard output. Since standard output of the process is redirected to the XSB port From, the last line in our program is able to read it and return in the variable Y.
Note that in the second line we used file_flush/2. Flushing the output is extremely important here, because XSB I/O ports are buffered. Thus, cat might not see its input until the buffer is filled up, so the above clause might hang. file_flush/2 makes sure that the input is immediately available to the subprocess.
In addition to the above general schema, the user can tell
spawn_process/5
to not open one of the communication ports or to
use one of the existing communication ports. This is useful when you do
not expect to write or read to/from the subprocess or when one process
wants to write to another (see the process plumbing example below).
To tell that a certain port is not needed, it suffices to bind that port to an atom. For instance,
| ?- spawn_process([cat, '-'], To, none, none, _), file_nl(To), file_write(To,'Hello cat!'), file_nl(To), file_flush(To,_). To = 3, Hello cat!reads from XSB and copies the result to standard output. Likewise,
| ?- spawn_process('cat test', none, From, none, _), file_read_line_atom(From, S). From = 4 S = The first line of file `test'In each case, only one of the ports is open. (Note that the shell command is specified as an atom rather than a list.) Finally, if both ports are suppressed, then
spawn_process
reduces to the usual
shell/1 call (in fact, this is how shell/1 is implemented):
| ?- spawn_process([pwd], none, none). /usr/local/foo/barOn the other hand, if any one of the three port variables in
spawn_process
is bound to an already existing file port, then the
subprocess will use that port (see the process plumbing example below).
One of the uses of XSB subprocesses is to create XSB servers that spawn subprocesses and control them. A spawned subprocess can be another XSB process. The following example shows one XSB process spawning another, sending it a goal to evaluate and obtaining the result:
| ?- spawn_process([xsb], To, From,Err,_), file_write(To,'assert(p(1)).'), file_nl(To), file_flush(To,_), file_write(To,'p(X), writeln(X).'), file_nl(To), file_flush(To,_), file_read_line_atom(From,XX). XX = 1 yes | ?-Here the parent XSB process sends ``
assert(p(1)).
'' and then
``p(X), writeln(X).
'' to the spawned XSB subprocess. The latter
evaluates the goal and prints (via ``writeln(X)
'')
to its standard output. The main process reads it through the From
port and binds the variable XX to that output.
Finally, we should note that the port variables in the
spawn_process
predicate can be used to do process plumbing, i.e., redirect output of one subprocess into the input of another. Here
is an example:
| ?- file_open(test,w,IOport), spawn_process([cat, 'data'], none, FromCat1, none, _), spawn_process([sort], FromCat1, IOport, none, _).Here, we first open file test. Then
cat data
is spawned. This
process has the input and standard error ports blocked (as indicated by the
atom none), and its output goes into port FromCat1. Then we spawn
another process, sort, which picks the output from the first process
(since it uses the port FromCat1 as its input) and sends its own
output (the sorted version of data) to its output port IOport.
However, IOport has already been open for output into the file test. Thus, the overall result of the above clause is tantamount to the
following shell command:
cat data | sort > test