# `nupp.io.process`
Starts a child process and drains its streams without deadlocking.
```nupp
local process = nupp.io.process
local child = new process.Process({args = {"cc", "--version"}})
local result = assert(child:communicate())
print(result.output)
child:close()
```
A child and its streams are owners. See
[ownership.md](../../../../learn/runtime/ownership/borrowing/index.html) for the contract they are handed
out under, and [NEP 5: Suspension](../../../../reference/neps/0005-suspension/index.html) for why
waiting is a suspension rather than a block.
## Types
### `CommunicateOptions` _type_
```nupp
type CommunicateOptions = {
--- Complete standard input.
---
--- Omitted input sends EOF immediately.
input: (string | Buffer | ByteView)?,
--- Maximum stdout and stderr bytes together.
---
--- Defaults to 256 MiB.
maxOutputBytes: integer?
}
```
Controls a complete duplex exchange.
### `ErrorMode` _type_
```nupp
type ErrorMode = "pipe" | "inherit" | "null" | "stdout"
```
Where standard error goes, which adds the option to join standard output.
### `Exit` _record_
```nupp
record Exit
exitCode: integer
killed: boolean
timedOut: boolean
succeeded: function(Exit): boolean
end
```
How a child ended.
#### Methods
##### `succeeded`
```nupp
succeeded: function(process.Exit): boolean
```
Exited on its own with status zero. A killed child never succeeded, whatever
status the platform reported for it.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `process.Exit` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
#### Fields
##### `exitCode`
```nupp
exitCode: integer
```
The status it exited with. Zero conventionally means success, and
`succeeded` is the question worth asking instead.
##### `killed`
```nupp
killed: boolean
```
Whether it was terminated rather than exiting on its own.
##### `timedOut`
```nupp
timedOut: boolean
```
Whether it was terminated because its deadline passed.
### `Options` _type_
```nupp
type Options = {
--- The program in `args[1]`, then its arguments. A program with no separator in
--- it is resolved through `PATH` by the platform, not by this module.
args: {string},
--- The child's working directory, or nil to inherit this one.
cwd: (string | Path)?,
--- Variables overlaid on the inherited environment, or the whole environment
--- when `clearEnv` is set.
env: {[string]: string}?,
--- Whether to start from an empty environment rather than this process's.
clearEnv: boolean?,
--- Where the child's standard input comes from, `"pipe"` by default.
stdin: process.StreamMode?,
--- Where the child's standard output goes, `"pipe"` by default.
stdout: process.StreamMode?,
--- Where the child's standard error goes, `"pipe"` by default.
stderr: process.ErrorMode?,
--- Kill the child after this many milliseconds. The clock starts when it is
--- created, not when it is first waited on.
timeoutMs: integer?
}
```
How a child was asked to be started.
### `Process` _record_
```nupp
record process.Process
drop: nosuspend function(takes self: process.Process): nil
pid: integer
stdin: process.Writer?
stdout: process.Reader?
stderr: process.Reader?
constructor(self, options: process.Options): affine(process.Process, process.Process.destroy) end
function destroy(takes self): nil end
function isRunning(self): boolean end
function wait(self): process.Exit end
function kill(self, force: boolean?): (boolean, string?) end
function communicate(self, options: process.CommunicateOptions?): (process.Result?, string?) end
function close(self): (boolean, string?) end
end
```
A running child.
#### Methods
##### `drop`
```nupp
drop: nosuspend function(takes self: process.Process): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `process.Process` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `constructor`
```nupp
constructor: function constructor(self, options: process.Options): affine(process.Process, process.Process.destroy)
```
Starts a child process.
The child is an owner: closing it ends the process, waits for the exit, and
releases every stream, and the same runs when its scope ends. Reading a stream
or waiting for the exit suspends, so a caller under a scheduler keeps its
frame.
```nupp
local child = new process.Process({
args = {"grep", "-c", "nupp"},
stderr = "stdout",
})
local result = assert(child:communicate({input = text}))
child:close()
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | the process being initialized |
| `options` | `process.Options` | what to run and how to connect it |
###### Returns
| Type | Description |
| --- | --- |
| `affine(process.Process, process.Process.destroy)` | |
###### Raises
- when the options are invalid or the child cannot be started
##### `destroy`
```nupp
destroy: function destroy(takes self): nil
```
Releases the process when its owner leaves scope.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `isRunning`
```nupp
isRunning: function isRunning(self): boolean
```
Whether it is still running.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `wait`
```nupp
wait: function wait(self): process.Exit
```
Waits for it to end and answers how.
Suspends while it runs.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `process.Exit` | |
##### `kill`
```nupp
kill: function kill(self, force: boolean?): boolean, string?
```
Asks the direct child to end; `force` insists. Descendants keep their
ordinary operating-system lifetime. Answers whether the request was made.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `force` | `boolean?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
| `string?` | |
##### `communicate`
```nupp
communicate: function communicate(self, options: process.CommunicateOptions?): process.Result?, string?
```
Writes input, closes stdin, drains stdout and stderr, and waits for the exit.
Answers the complete exchange or a reason it could not be completed.
One combined step per pass: offer some input, take whatever each output has,
suspend only when none of the three moved.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `options` | `process.CommunicateOptions?` | |
###### Returns
| Type | Description |
| --- | --- |
| `process.Result?` | |
| `string?` | |
##### `close`
```nupp
close: function close(self): boolean, string?
```
Closes every stream, ends the child if it is still running, waits for it to
actually finish, and releases it. Idempotent, and what `drop` runs when the
owner goes out of lexical scope.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
| `string?` | |
###### Raises
- when closing one of the child streams fails
#### Fields
##### `pid`
```nupp
pid: integer
```
Operating-system process identifier.
##### `stdin`
```nupp
stdin: process.Writer?
```
Its standard input, when it was piped.
##### `stdout`
```nupp
stdout: process.Reader?
```
Its standard output, when it was piped.
##### `stderr`
```nupp
stderr: process.Reader?
```
Its standard error, when it was piped.
### `Reader` _record_
```nupp
record process.Reader is Reader2
timeoutMs: integer
function isEOF(self): boolean end
function isClosed(self): boolean end
function poll(self, limit: integer?): string? end
function next(self): string? end
function readCompletion(self, limit: integer): (string?, string?) end
function setTimeout(self, timeoutMs: integer): nil end
function read(self, count: integer): (string?, string?) end
function readSpan(self, exclusive destination: span.Writable): (integer?, string?) end
function readInto(self, exclusive destination: Buffer, offset: integer?, count: integer?): (integer?, string?) end
function transferTo(self, exclusive destination: Writer2): (integer?, string?) end
function close(takes self): nil end
end
```
One of a child's readable streams.
Its blocking `read` and its nonblocking `poll` are both here on purpose. A caller
draining several streams at once cannot use the blocking form on any of them,
because waiting on one is exactly what starves the others, which is the whole
reason `communicate` can drain three pipes without deadlocking.
The completion-oriented `nupp.io.Reader` methods are the public tecs-compatible
surface. `poll` remains alongside them as the concrete nonblocking operation the
combined drain needs; generic readers are not widened with readiness operations.
The tolerance that does exist is narrow and worth naming exactly: closing the same
*live opaque handle* twice is harmless, because the second call finds the stream
already released and says so. Destroying a handle while a borrowed reference
still exists leaves that reference reading freed memory, which no amount of care
at the call site can make safe.
Borrowing the record through `asReader` keeps a single owner and makes its lifetime
a question about Nupp values, which the checker can answer.
#### Methods
##### `isEOF`
```nupp
isEOF: function isEOF(self): boolean
```
Whether the far end has finished.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `isClosed`
```nupp
isClosed: function isClosed(self): boolean
```
Whether this end has been closed.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `poll`
```nupp
poll: function poll(self, limit: integer?): string?
```
Takes whatever is available without waiting: the bytes, `""` when nothing is
ready yet, or nil at end of stream.
The non-blocking half of reading, which `communicate` needs so that one quiet
stream does not stop it serving another.
`limit` caps how many bytes to take, defaulting to a whole pipe's worth. It is
here because the shared `nupp.io.Reader` promises "at most `count`". Without the
limit its completion-oriented method would have to keep surplus bytes beside
this record, giving end of stream and closedness two homes. One place decides
both, and the caller says how much it wants.
Zero and negative polling limits read one byte. This concrete nonblocking
operation keeps that defensive behavior even though the shared `Reader` contract
requires a positive count. Settled here rather than at the platform because the
platform is where it stops being a number and becomes a buffer size: a signed
zero or minus one arriving at a native size conversion is either an empty read
that looks like end of stream or an enormous one, and neither is worth being
able to ask for. `read` passes its caller's count straight through.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `limit` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | |
##### `next`
```nupp
next: function next(self): string?
```
Reads the next available bytes, suspending until there are some. Answers nil at
end of stream.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | |
##### `readCompletion`
```nupp
readCompletion: function readCompletion(self, limit: integer): string?, string?
```
The shared Reader operation uses the concrete bounded poll but still waits
through the same state machine as every other completion-oriented call.
Keeping the limit here avoids a surplus buffer and its second notion of EOF.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `limit` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | |
| `string?` | |
##### `setTimeout`
```nupp
setTimeout: function setTimeout(self, timeoutMs: integer): nil
```
Bounds the next completion-oriented read.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `timeoutMs` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
###### Raises
- when timeoutMs is outside 0 through 2147483647
##### `read`
```nupp
read: function read(self, count: integer): string?, string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `count` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `string?` | |
| `string?` | |
##### `readSpan`
```nupp
readSpan: function readSpan(self, exclusive destination: span.Writable): integer?, string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `exclusive destination` | `span.Writable\` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | |
| `string?` | |
##### `readInto`
```nupp
readInto: function readInto(self, exclusive destination: Buffer, offset: integer?, count: integer?): integer?, string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `exclusive destination` | `Buffer` | |
| `offset` | `integer?` | |
| `count` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | |
| `string?` | |
##### `transferTo`
```nupp
transferTo: function transferTo(self, exclusive destination: Writer2): integer?, string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `exclusive destination` | `Writer2` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | |
| `string?` | |
##### `close`
```nupp
close: function close(takes self): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
#### Fields
##### `timeoutMs`
```nupp
timeoutMs: integer
```
Maximum time one completion-oriented read may wait.
### `Result` _record_
```nupp
record Result
exit: process.Exit
output: string
errorOutput: string
function succeeded(self): boolean end
end
```
A completed duplex exchange.
#### Methods
##### `succeeded`
```nupp
succeeded: function succeeded(self): boolean
```
Whether the child exited normally with status zero.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
#### Fields
##### `exit`
```nupp
exit: process.Exit
```
How the child ended.
##### `output`
```nupp
output: string
```
Captured standard output.
##### `errorOutput`
```nupp
errorOutput: string
```
Captured standard error.
### `StreamMode` _type_
```nupp
type StreamMode = "pipe" | "inherit" | "null"
```
Where a stream goes: a pipe this process reads or writes, the parent's own
stream, or nothing at all.
### `Writer` _record_
```nupp
record process.Writer is Writer2
timeoutMs: integer
function isGone(self): boolean end
function isClosed(self): boolean end
function offer(self, data: string): integer end
function send(self, data: string, stopAt: number?, stallFor: integer?): integer end
function setTimeout(self, timeoutMs: integer): nil end
function write(exclusive self, bytes: string): (boolean, string?) end
function writeSpan(exclusive self, borrows source: span.ByteSpan): (integer?, string?) end
function flush(self): (boolean, string?) end
function close(takes self): nil end
end
```
A child's writable stream.
`offer` and `isGone` are the nonblocking half, for the same reason the reader has
`poll`: the prelude's `nupp.io.Writer.write` writes the whole value, which a drain
loop serving three pipes cannot afford to wait for. The completion-oriented `write`
remains the ordinary `nupp.io.Writer` operation; `offer` is concrete and additional.
#### Methods
##### `isGone`
```nupp
isGone: function isGone(self): boolean
```
Whether the far end has gone: the child is no longer reading this, and no
amount of waiting will change that.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `isClosed`
```nupp
isClosed: function isClosed(self): boolean
```
Whether this end has been closed.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `offer`
```nupp
offer: function offer(self, data: string): integer
```
Writes what the pipe will take without waiting. Answers how many bytes went,
which may be none and may be fewer than offered.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `data` | `string` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `send`
```nupp
send: function send(self, data: string, stopAt: number?, stallFor: integer?): integer
```
Writes every byte, suspending as often as the pipe makes it. Answers how many
went, which is all of them unless the far end went away first.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `data` | `string` | |
| `stopAt` | `number?` | |
| `stallFor` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `setTimeout`
```nupp
setTimeout: function setTimeout(self, timeoutMs: integer): nil
```
Bounds the next completion-oriented write.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
| `timeoutMs` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
###### Raises
- when timeoutMs is outside 0 through 2147483647
##### `write`
```nupp
write: function write(exclusive self, bytes: string): boolean, string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `any` | |
| `bytes` | `string` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
| `string?` | |
##### `writeSpan`
```nupp
writeSpan: function writeSpan(exclusive self, borrows source: span.ByteSpan): integer?, string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `any` | |
| `borrows source` | `span.ByteSpan` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer?` | |
| `string?` | |
##### `flush`
```nupp
flush: function flush(self): boolean, string?
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
| `string?` | |
##### `close`
```nupp
close: function close(takes self): nil
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `takes self` | `any` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
#### Fields
##### `timeoutMs`
```nupp
timeoutMs: integer
```
Maximum time one completion-oriented write may wait.
## Functions
### `process.asReader` _function_
```nupp
function process.asReader(borrows source: process.Reader): Reader2
```
Borrows a process reader through the shared completion-oriented contract.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows source` | `process.Reader` | |
#### Returns
| Type | Description |
| --- | --- |
| `Reader2` | |
### `process.asWriter` _function_
```nupp
function process.asWriter(borrows source: process.Writer): Writer2
```
Borrows a process writer through the shared completion-oriented contract.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows source` | `process.Writer` | |
#### Returns
| Type | Description |
| --- | --- |
| `Writer2` | |