LEVIATHAN v962456e · 962456eee1

Standard Library

class Process

Runs another program as a child process and gives you its standard input, standard output and standard error as streams.

since 0.1.0-alpha.1linux

Overview

Process(path, args) starts the child right away. path is used exactly as written, so give the full path of the executable (/bin/cat); no search of the PATH is made. The child receives path followed by args as its argument list. Feed it with write and closeStdin, receive its output with onStdout and onStderr, and learn how it ended with exitCode. Everything runs on the event loop, so none of these calls blocks.

Check ok() after constructing. A path that does not exist or cannot be executed still counts as started: the failure shows up later as exit code 127. A process that could not be started at all (an empty path, or the operating system refused to create the pipes) reports false from ok() and also resolves exitCode() with 127.

A program that never calls exitCode() may finish while its children are still running; they are not waited for.

Description

Process(path, args) starts a child program. The child's argument vector is [path] + args. The path is used exactly as given, with no search of PATH: write /bin/cat, not cat. (A helper that searches PATH can be written in the language itself, from env::get and a file-existence check.)

The child's three standard streams are connected to pipes, and the object gives you the stream surface for each of them:

  • ok() is true when the child could be started. A path that does not exist does not make ok() false: the child starts, fails to run the program, and exits with code 127.
  • write(text) sends text to the child's standard input. Like a socket send, it queues what the pipe cannot take yet and drains it as the child reads. closeStdin() closes the child's input; for programs such as cat that read until end-of-input, closing it is how you say "that is everything".
  • onStdout(callback) and onStderr(callback) deliver the child's output in chunks as it arrives. A chunk is whatever was available, not necessarily a whole line.
  • exitCode() returns a Promise<int> that resolves when the child has exited. Waiting for a child never blocks the program: other timers, sockets and tasks keep running. The code is the child's exit status; 128 plus the signal number if a signal ended the child (the shell convention); 127 if it never ran.
  • kill() asks the child to terminate (SIGTERM). A pending exitCode() then resolves with 143.

When exitCode() resolves, any output still sitting in the child's pipes has been delivered first, and every descriptor the object owned has been closed, so starting many short-lived children does not leak descriptors.

A program that never calls exitCode() sets up no wait for the child and may finish while the child is still running; the child is not reaped (standard Unix behavior).

Rules

  • Linux only. Starting processes is a Linux feature. A program that uses Process is rejected at compile time when it is built for a Windows target.
  • The path is explicit; there is no PATH lookup.
  • A bad path is reported as exit code 127, not as ok() == false. ok() is false only when the child could not be created at all, and exitCode() then also resolves 127.
  • Call exitCode() once the child should be waited for; its result is cached, so calling it again returns the same promise.
  • The output callbacks run on the event loop, one at a time, never concurrently with your other code.
  • kill() sends SIGTERM to this object's own child and to nothing else.

Examples

Feeding a child through its standard input and waiting for it. cat echoes its input and exits when the input is closed:

Process cat = Process("/bin/cat", []);
if (!cat.ok()) {
    console.writeln("could not start the child");
} else {
    cat.onStdout((chunk) => console.writeln("child said: ${chunk}"));
    cat.write("hello\n");
    cat.closeStdin();
    int code = await cat.exitCode();
    console.writeln("exit code ${code}");
}
not run — spawns a child process

This prints child said: hello and then exit code 0.

Notes

For a child that expects a terminal (a shell, an editor, anything that checks isatty), use Pty instead. A pseudo-terminal merges standard output and standard error into one stream.

Examples

Running a command and reading its output

Process p = Process("/bin/cat", []);
if (p.ok()) {
    p.onStdout((chunk) => console.write(chunk));
    p.write("hello\n");
    p.closeStdin();
    int code = await p.exitCode();
    console.writeln("exit code: ${code}");
}
not run — spawns a child process

Collecting both output streams

Process p = Process("/bin/ls", ["-l"]);
p.onStdout((chunk) => console.write(chunk));
p.onStderr((chunk) => console.write(chunk));
p.exitCode().then((code) => console.writeln("exit code: ${code}"));

Constructors

new

new(string path, Array<string> args)

Starts path as a child process with the given arguments.

The child's argument list is path followed by args. path is not searched for on the PATH. Call ok() to find out whether the child was created; an executable that does not exist is reported through the exit code (127), not through ok().

Parameters

path
The full path of the executable to run. An empty path starts nothing.
args
The arguments passed to the program, not counting the program name itself.

Fields

pid

int pid

The operating-system process id of the child, or -1 when it could not be started.

Methods

closeStdin

closeStdin() -> void

Closes the child's standard input.

Programs such as cat read until their input ends, so closing it is how you tell them that you are done writing. Calling it more than once is harmless, and so is calling it on a process that was not started.

exitCode

exitCode() -> Promise<int>

Returns a promise that resolves with the child's exit code once it has ended.

The code is the value the child passed to its exit call (0 to 255), 128 plus the signal number when a signal ended it, or 127 when the process could not be started or run. Before it resolves, any output still waiting in the pipes is delivered to the output handlers, and then the pipes are closed. Calling exitCode again returns the same promise. Waiting for the child uses a non-blocking wait on the event loop, so it needs no signal handler.

Returns

A promise of the exit code.

See also: kill

kill

kill() -> void

Asks the child to stop by sending it the termination signal (SIGTERM), and closes its standard input.

Does nothing when the process was not started or has already ended. If exitCode is pending it resolves with 143 (128 plus the signal number) unless the child handles the signal itself.

See also: exitCode

ok

ok() -> bool

Tells whether the child process was created.

A path that does not exist still gives true here, because the failure to run it is only seen by the child; it surfaces as exit code 127 from exitCode. Use this check before attaching handlers.

Returns

true when the child was created, false when nothing was started.

onStderr

onStderr((string) => void cb) -> void

Sets the function that receives the child's standard error output.

It works exactly like onStdout, for the error stream. Only call it when ok() is true.

Parameters

cb
The function called with each chunk of error output.

See also: onStdout

onStdout

onStdout((string) => void cb) -> void

Sets the function that receives the child's standard output.

cb is called with each piece of output as it arrives. A piece is whatever the pipe held at that moment, not necessarily a whole line. Calling onStdout again replaces the previous function. Any output still waiting in the pipe when the child ends is delivered before exitCode resolves.

Only call this when ok() is true. On a process that was not started the registration keeps the event loop alive and the program never ends on its own.

Parameters

cb
The function called with each chunk of output.

See also: onStderr

write

write(string s) -> void

Sends text to the child's standard input.

The text is queued and written as the pipe has room, so a large string never blocks the program. Writing to a process that was not started, or after closeStdin, does nothing.

Parameters

s
The text to send.

See also

  • Pty — Runs another program on a pseudo-terminal, so the child believes it is talking to a real terminal.
  • Promise — A value that arrives later.
  • TcpStream — A connected network socket that reads and writes text.