diff options
Diffstat (limited to 'toolkit/modules/subprocess/docs/index.rst')
-rw-r--r-- | toolkit/modules/subprocess/docs/index.rst | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/toolkit/modules/subprocess/docs/index.rst b/toolkit/modules/subprocess/docs/index.rst new file mode 100644 index 000000000..cb2d439a4 --- /dev/null +++ b/toolkit/modules/subprocess/docs/index.rst @@ -0,0 +1,227 @@ +.. _Subprocess: + +================= +Supbrocess Module +================= + +The Subprocess module allows a caller to spawn a native host executable, and +communicate with it asynchronously over its standard input and output pipes. + +Processes are launched asynchronously ``Subprocess.call`` method, based +on the properties of a single options object. The method returns a promise +which resolves, once the process has successfully launched, to a ``Process`` +object, which can be used to communicate with and control the process. + +A simple Hello World invocation, which writes a message to a process, reads it +back, logs it, and waits for the process to exit looks something like: + +.. code-block:: javascript + + let proc = await Subprocess.call({ + command: "/bin/cat", + }); + + proc.stdin.write("Hello World!"); + + let result = await proc.stdout.readString(); + console.log(result); + + proc.stdin.close(); + let {exitCode} = await proc.wait(); + +Input and Output Redirection +============================ + +Communication with the child process happens entirely via one-way pipes tied +to its standard input, standard output, and standard error file descriptors. +While standard input and output are always redirected to pipes, standard error +is inherited from the parent process by default. Standard error can, however, +optionally be either redirected to its own pipe or merged into the standard +output pipe. + +The module is designed primarily for use with processes following a strict +IO protocol, with predictable message sizes. Its read operations, therefore, +either complete after reading the exact amount of data specified, or do not +complete at all. For cases where this is not desirable, ``read()`` and +``readString`` may be called without any length argument, and will return a +chunk of data of an arbitrary size. + + +Process and Pipe Lifecycles +=========================== + +Once the process exits, any buffered data from its output pipes may still be +read until the pipe is explicitly closed. Unless the pipe is explicitly +closed, however, any pending buffered data *must* be read from the pipe, or +the resources associated with the pipe will not be freed. + +Beyond this, no explicit cleanup is required for either processes or their +pipes. So long as the caller ensures that the process exits, and there is no +pending input to be read on its ``stdout`` or ``stderr`` pipes, all resources +will be freed automatically. + +The preferred way to ensure that a process exits is to close its input pipe +and wait for it to exit gracefully. Processes which haven't exited gracefully +by shutdown time, however, must be forcibly terminated: + +.. code-block:: javascript + + let proc = await Subprocess.call({ + command: "/usr/bin/subprocess.py", + }); + + // Kill the process if it hasn't gracefully exited by shutdown time. + let blocker = () => proc.kill(); + + AsyncShutdown.profileBeforeChange.addBlocker( + "Subprocess: Killing hung process", + blocker); + + proc.wait().then(() => { + // Remove the shutdown blocker once we've exited. + AsyncShutdown.profileBeforeChange.removeBlocker(blocker); + + // Close standard output, in case there's any buffered data we haven't read. + proc.stdout.close(); + }); + + // Send a message to the process, and close stdin, so the process knows to + // exit. + proc.stdin.write(message); + proc.stdin.close(); + +In the simpler case of a short-running process which takes no input, and exits +immediately after producing output, it's generally enough to simply read its +output stream until EOF: + +.. code-block:: javascript + + let proc = await Subprocess.call({ + command: await Subprocess.pathSearch("ifconfig"), + }); + + // Read all of the process output. + let result = ""; + let string; + while ((string = await proc.stdout.readString())) { + result += string; + } + console.log(result); + + // The output pipe is closed and no buffered data remains to be read. + // This means the process has exited, and no further cleanup is necessary. + + +Bidirectional IO +================ + +When performing bidirectional IO, special care needs to be taken to avoid +deadlocks. While all IO operations in the Subprocess API are asynchronous, +careless ordering of operations can still lead to a state where both processes +are blocked on a read or write operation at the same time. For example, + +.. code-block:: javascript + + let proc = await Subprocess.call({ + command: "/bin/cat", + }); + + let size = 1024 * 1024; + await proc.stdin.write(new ArrayBuffer(size)); + + let result = await proc.stdout.read(size); + +The code attempts to write 1MB of data to an input pipe, and then read it back +from the output pipe. Because the data is big enough to fill both the input +and output pipe buffers, though, and because the code waits for the write +operation to complete before attempting any reads, the ``cat`` process will +block trying to write to its output indefinitely, and never finish reading the +data from its standard input. + +In order to avoid the deadlock, we need to avoid blocking on the write +operation: + +.. code-block:: javascript + + let size = 1024 * 1024; + proc.stdin.write(new ArrayBuffer(size)); + + let result = await proc.stdout.read(size); + +There is no silver bullet to avoiding deadlocks in this type of situation, +though. Any input operations that depend on output operations, or vice versa, +have the possibility of triggering deadlocks, and need to be thought out +carefully. + +Arguments +========= + +Arguments may be passed to the process in the form an array of strings. +Arguments are never split, or subjected to any sort of shell expansion, so the +target process will receive the exact arguments array as passed to +``Subprocess.call``. Argument 0 will always be the full path to the +executable, as passed via the ``command`` argument: + +.. code-block:: javascript + + let proc = await Subprocess.call({ + command: "/bin/sh", + arguments: ["-c", "echo -n $0"], + }); + + let output = await proc.stdout.readString(); + assert(output === "/bin/sh"); + + +Process Environment +=================== + +By default, the process is launched with the same environment variables and +working directory as the parent process, but either can be changed if +necessary. The working directory may be changed simply by passing a +``workdir`` option: + +.. code-block:: javascript + + let proc = await Subprocess.call({ + command: "/bin/pwd", + workdir: "/tmp", + }); + + let output = await proc.stdout.readString(); + assert(output === "/tmp\n"); + +The process's environment variables can be changed using the ``environment`` +and ``environmentAppend`` options. By default, passing an ``environment`` +object replaces the process's entire environment with the properties in that +object: + +.. code-block:: javascript + + let proc = await Subprocess.call({ + command: "/bin/pwd", + environment: {FOO: "BAR"}, + }); + + let output = await proc.stdout.readString(); + assert(output === "FOO=BAR\n"); + +In order to add variables to, or change variables from, the current set of +environment variables, the ``environmentAppend`` object must be passed in +addition: + +.. code-block:: javascript + + let proc = await Subprocess.call({ + command: "/bin/pwd", + environment: {FOO: "BAR"}, + environmentAppend: true, + }); + + let output = ""; + while ((string = await proc.stdout.readString())) { + output += string; + } + + assert(output.includes("FOO=BAR\n")); + |