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 a user to spawn
any process (including multiple copies of XSB) and redirect its
standard input and output to XSB streams. 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 stream) and talk to child processes through these pipes. All predicates in this section, except pipe_open/2 and fd2stream/2, must be imported from module shell. The predicates pipe_open/2 and fd2stream/2 must be imported from file_io.
The next three parameters of spawn_process are XSB I/O stream
identifiers for the process (leading to the subprocess standard
input), from the process (from its standard output), and a stream
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),
writeln(To,'Hello cat!'), flush_output(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 an atom and newline to the XSB stream To, which is bound to the standard input of the cat process (proc id 14328). The cat process then copies the input to its standard output. Since standard output of the cat process is redirected to the XSB stream From in the parent process, 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 flush_output/2. Flushing the output is extremely important here, because XSB I/O pipe (file) streams are buffered. Thus, cat might not see its input until the buffer is filled up, so the above clause might hang. flush_output/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 not to open one of the communication streams or to
use one of the existing communication streams. 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 stream is not needed, it suffices to bind that
stream to an atom. For instance,
| ?- spawn_process([cat, '-'], To, none, none, _),
nl(To), writeln(To,'Hello cat!'), flush_output(To).
To = 3,
Hello cat!
reads from XSB and copies the result to standard output. Likewise,
| ?- spawn_process('cat library.tex', none, From, none, _),
file_read_line_atom(From, S).
From = 4
S = \chapter{Library Utilities} \label{library_utilities}
In each case, only one of the streams is open. (Note that the shell
command is specified as an atom rather than a list.) Finally, if both
streams 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 stream variables in
spawn_process is bound to an already existing file stream, then the
subprocess will use that stream (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,_),
write(To,'assert(p(1)).'), flush_output(To,_),
write(To,'p(X), writeln(X).'), flush_output(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
stream and binds the variable XX to that output.
Finally, we should note that the stream 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:
| ?- open(test,write,Stream),
spawn_process([cat, 'data'], none, FromCat1, none, _),
spawn_process([sort], FromCat1,Stream, none, _).
Here, we first open file test. Then cat data is spawned.
This process has the input and standard error stream blocked (as
indicated by the atom none), and its output goes into stream
FromCat1. Then we spawn another process, sort, which
picks the output from the first process (since it uses the stream FromCat1 as its input) and sends its own output (the sorted version
of data) to its output stream Stream. However, Stream 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