Processes
The process module starts other programs and holds what belongs to the
running one: its arguments, its exit, its id, its executable, and its working
directory. Bring it in with import process.
On WebAssembly (--target wasm32-wasip1) a program cannot start another:
run, spawn, exec and executable_path return an IoError saying it
is not supported on WASI. Its arguments, exit and environment work (see
WebAssembly).
Running a program
Command describes a program to start. Its setters return the same
Command, so they chain. run() starts the program, waits for it to end,
and returns an Output: the status it ended with, and the text it wrote to
stdout and stderr.
import io;
import process;
# Runs a copy of this program as a child, so the example needs no other
# program on any system: the child's first argument says what to do.
main(args: String[]): int {
if (args.count > 1) {
io.File.stdout().write_all("hello from " + args[1]);
io.File.stderr().write_all("a warning");
return 3;
}
var self = process.executable_path().value().to_string();
var output = process.Command(self).argument("the child").run().value();
assert(output.stdout == "hello from the child", "standard output is collected");
assert(output.stderr == "a warning", "and so is standard error");
assert(!output.status.success(), "the child failed");
assert(output.status.code().value() == 3, "with exit code 3");
# A program that is not found fails to start.
var missing = process.Command("no-such-program").run();
assert(missing.is_err() && missing.error().is_not_found(), "not found");
return 0;
}
- The program is found the way a shell finds it. A bare name is searched for
on the
PATH(on Windows, also next to the running program and in the system directories, with.exeadded when the name has no extension). Anything with a path separator is a path. - Each
argumentreaches the program as one argument, whatever it holds. No shell splits it or expands*or$NAME. On Windows, where a program receives a single command line, each argument is quoted so that the C runtime splits it back out as it was. run()collects standard output and error, both at once, so a child that fills one pipe while the parent reads the other does not stall. Standard input is the null device unless it was set. The output is decoded as UTF-8, with each invalid byte sequence replaced by U+FFFD; read a child’s binary output from a pipe withspawn()instead.- A program that cannot be started is an error (
is_not_found()when it does not exist). A program that runs and fails is a successful result whosestatussays so.
exec() runs the program in place of the running one, as a shell’s exec
does: the program’s exit status becomes this process’s own, and exec()
returns only when the program cannot be started, with the error. On POSIX
the program takes over the process; Windows cannot replace a process, so
there the program is started, waited for, and its exit status passed on.
Output that print buffered is flushed first, and the standard streams must
stay inherited (otherwise the error has is_invalid_input()).
Exit status
ExitStatus.success() is true for exit code 0. code() returns the exit
code, and signal() the POSIX signal that ended the process (such as 9 for
SIGKILL). Each is an optional: a process killed by a signal has no exit
code, and a process that exited has no signal. Windows has no signals, so
there every process has a code; a POSIX parent sees only the low 8 bits of
it.
Talking to a child
spawn() starts the program and returns a Child at once. Each of the three
standard streams is Stream.Inherit (shared with the parent, the default),
Stream.Pipe (connected to the parent), or Stream.Null. A piped stream is
an io.File from child.stdin(), child.stdout() or child.stderr(), so
the file read and write methods apply.
import io;
import process;
main(args: String[]): int {
if (args.count > 1) {
# The child: read standard input to the end, and write it back in
# capitals.
var text = io.File.stdin().read_string().value();
io.File.stdout().write_all(text.upper());
return 0;
}
var self = process.executable_path().value().to_string();
var child = process.Command(self)
.argument("upper")
.stdin(process.Stream.Pipe)
.stdout(process.Stream.Pipe)
.spawn()
.value();
child.stdin().write_all("pear and apple");
child.stdin().close(); # the child reads to the end, so end it
var shouted = child.stdout().read_string().value();
var status = child.wait().value();
assert(shouted == "PEAR AND APPLE", "the child's output");
assert(status.success(), "the child succeeded");
return 0;
}
- Close a piped standard input when you are done writing, or a child that
reads to the end never finishes.
wait()closes it too, before waiting. - Read piped output before waiting. A child that fills a pipe stops until the parent reads it, so waiting first can wait forever.
wait()returns the exit status, and again on later calls.try_wait()returns none while the child runs.kill()ends the child at once (SIGKILL, orTerminateProcesson Windows); wait for it afterwards.- Wait for every child. On POSIX a child that ended stays in the process
table until it is waited for. Releasing a
Childneither waits for it nor kills it.
In a task, run(), wait() and reads from a pipe park the task
rather than block its worker, so other tasks keep running.
Environment and working directory
A child inherits the parent’s environment. environment(name, value) sets a
variable for the child, remove_environment(name) leaves one out, and
clear_environment() starts from an empty environment. On Windows, names
ignore case. working_directory(path) starts the child in another
directory. The running process reads and changes its own environment with
os.environment(name) and os.set_environment(name, value) (see
The operating system).
import io;
import os;
import process;
main(args: String[]): int {
if (args.count > 1) {
io.File.stdout().write_all(os.environment("GREETING").value_or("unset"));
return 0;
}
var self = process.executable_path().value().to_string();
var greeted = process.Command(self).argument("child").environment("GREETING", "hello").run().value();
assert(greeted.stdout == "hello", "the child sees the variable");
var cleared = process.Command(self).argument("child").clear_environment().run().value();
assert(cleared.stdout == "unset", "an empty environment");
# The child starts in another directory; the parent's stays as it was.
var before = process.working_directory().value();
var elsewhere = process.Command(self).argument("child").working_directory(io.Directory.temp()).run();
assert(elsewhere.is_ok(), "started in the temporary directory");
assert(process.working_directory().value() == before, "unchanged");
return 0;
}
The running process
process.arguments()returns the program’s command-line arguments, the same arraymain(args: String[])receives (see Functions).process.exit(code)ends the program at once with statuscode.process.id()is the process’s id.process.executable_path()is the absolute path of the running program’s executable, so a tool can find files installed next to it.process.working_directory()andprocess.change_working_directory(path)read and change the directory relative paths are resolved against, asio.Directory.current()andio.Directory.set_current(path)do.
arguments() and exit() without import process still work, but are
deprecated and warn.
Signals
process.signals([...]) returns an async.Channel<Signal> that receives
each signal it names instead of the signal’s default action, which ends the
program. A server or tool waits on it to shut down cleanly: stop accepting
work, flush, remove temporary files.
Signal | POSIX | Windows |
|---|---|---|
Interrupt | SIGINT (Ctrl-C) | CTRL_C_EVENT, CTRL_BREAK_EVENT |
Terminate | SIGTERM | CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT |
Hangup | SIGHUP | never sent |
import process;
import [Signal] from process;
main(): int {
var stop = process.signals([Signal.Interrupt, Signal.Terminate]);
# Serve until Ctrl-C or a request to terminate arrives: here, for at most
# a tenth of a second.
var signal = stop.receive_for(100);
if (signal.is_ok()) {
print("shutting down cleanly");
}
# Closing the channel gives the signals back their default action.
stop.close();
return 0;
}
No program code runs inside the operating system’s signal handler: on POSIX
the handler only writes the signal’s number to a pipe, and a runtime thread
sends it on the channel. So the channel is the whole interface, and works as
any channel does: receive() waits, a task can select on it alongside its
other channels, and try_receive() polls between steps of a long job.
- Every open channel that names a signal receives it.
- Closing a channel stops it receiving; once no open channel names a signal, its default action returns.
- On Windows the system ends the program a few seconds after
Terminate(the console closing, a log-off or a shutdown) whatever it does, so keep that cleanup short. - WebAssembly has no signals: the channel never receives.
- Writing to a socket whose peer has gone returns an
IoErrorrather than raisingSIGPIPE; the runtime asks for that on every socket.
Finding a program
process.find_executable(name) looks a program up on the PATH as a shell
does, returning the path of the first match that may be run, or none, so a
tool can say what to install before trying to run it.
process.is_executable(path) tells whether a file may be run.
var perf = process.find_executable("perf");
if (perf.is_empty()) {
print("perf is not installed");
}
See the process module reference.