Process Management
The process global provides process spawning, messaging, monitoring, linking, naming, and lifecycle control.
It is available without require() and does not need to be listed in modules:.
This is an API reference. Its call-form blocks use placeholders such as id, host, destination, topic, and name for values supplied by application code; they are not standalone programs. Calls shown with an err result return their documented value on success or a failure sentinel plus error; the sentinel is normally nil, while process.set_options returns false. Application control flow should handle the error.
Process Information
Read the current frame ID or process ID:
local frame_id, err = process.id() -- Registry ID of the current function, process, or workflow definition
if err then return nil, err end
local pid, err = process.pid() -- Process ID
if err then return nil, err end
Sending Messages
Send one or more payload values to a process by PID or registered name:
local ok, err = process.send(destination, topic, ...)
| Parameter | Type | Description |
|---|---|---|
destination |
string | PID or registered name |
topic |
string | Topic name (cannot start with @) |
... |
any | Payload values |
Permission: process.send on target PID
Spawning Processes
-- Basic spawn
local pid, err = process.spawn(id, host, ...)
-- With monitoring (receive EXIT events)
local pid, err = process.spawn_monitored(id, host, ...)
-- With linking (receive LINK_DOWN on abnormal exit)
local pid, err = process.spawn_linked(id, host, ...)
-- Both linked and monitored
local pid, err = process.spawn_linked_monitored(id, host, ...)
| Parameter | Type | Description |
|---|---|---|
id |
string | Process source ID (e.g., "app.workers:handler") |
host |
string | Host ID (e.g., "app:processes") |
... |
any | Arguments passed to spawned process |
All variants require process.spawn on the process ID. The monitored variants also require process.spawn.monitored, and the linked variants require process.spawn.linked. At runtime v0.3.32a, only the module-level spawn() checks process.host on the host ID; the specialized module-level variants do not perform that host permission check.
Process Control
-- Forcefully terminate a process
local ok, err = process.terminate(destination)
-- Request graceful cancellation with an optional reason
local ok, err = process.cancel(destination, "shutting down")
| Parameter | Type | Description |
|---|---|---|
destination |
string | PID or registered name |
reason |
string | Optional reason delivered to the target |
Permissions: process.terminate, process.cancel on target PID
Monitoring and Linking
Add or remove monitoring and links for an existing process:
-- Monitoring: receive EXIT events when target exits
local ok, err = process.monitor(destination)
local ok, err = process.unmonitor(destination)
-- Linking: bidirectional, receive LINK_DOWN on abnormal exit
local ok, err = process.link(destination)
local ok, err = process.unlink(destination)
Permissions: process.monitor, process.unmonitor, process.link, process.unlink on target PID
Process Options
local options = process.get_options()
local ok, err = process.set_options({trap_links = true})
| Field | Type | Description |
|---|---|---|
trap_links |
boolean | Whether LINK_DOWN events are delivered to events channel |
upgradable |
boolean | Opt in to OUTDATED events when the process's code is invalidated |
Inbox and Events
Use the inbox and event channels to receive messages and lifecycle events:
local inbox = process.inbox() -- Message objects from @inbox topic
local events = process.events() -- Lifecycle events from @events topic
Event Types
| Constant | Description |
|---|---|
process.event.CANCEL |
Cancellation requested |
process.event.EXIT |
Monitored process exited |
process.event.LINK_DOWN |
Linked process terminated abnormally |
process.event.OUTDATED |
The process's code or an imported dependency changed in the registry |
Event Fields
| Field | Type | Description |
|---|---|---|
kind |
string | Event type constant |
from |
string | Source PID (absent for OUTDATED) |
result |
table | For EXIT/LINK_DOWN: a {value, error} record; the process return value is at result.value and any error at result.error |
reason |
string | For CANCEL: why the process is being cancelled |
sources |
string[] | For OUTDATED: registry IDs that changed or were transitively affected |
OUTDATED is delivered only to processes that opt in with process.set_options({upgradable = true}). Multiple invalidations are combined into one pending event containing the union of their sources. Handle the event by calling process.upgrade.
Topic Subscription
Subscribe to a custom message topic:
local ch, err = process.listen(topic, options)
if err then return nil, err end
local ok, err = process.unlisten(ch)
if err then return nil, err end
| Parameter | Type | Description |
|---|---|---|
topic |
string | Topic name (cannot start with @) |
options.message |
boolean | If true, receive Message objects; if false, raw payloads |
Message Objects
The inbox and listeners configured with {message = true} return message objects:
local msg = inbox:receive()
msg:topic() -- string: topic name
msg:from() -- string: sender PID (empty string when unknown)
msg:payload() -- Payload: wrapper (call :data() to extract); nil when empty, table of wrappers for several values
msg:payload():data() -- any: actual payload value
Synchronous Call
process.exec spawns a process and waits for its result:
local result, err = process.exec(id, host, ...)
Permissions: process.exec on process id, process.host on host id
Process Upgrade
Upgrade the current process while preserving its PID:
The two snippets below are alternative call forms, not sequential operations.
-- Upgrade to new version, passing state
process.upgrade(id, ...)
-- Keep same definition, re-run with new state
process.upgrade(nil, preserved_state)
process.upgrade is a terminal control transfer: it clears the current execution and starts the requested definition with the same PID. Code after the call does not run in the old execution.
Context Spawner
Create a spawner that supplies custom context to child processes:
local spawner = process.with_context({request_id = "123"})
Permission: process.context on "context"
Spawner with Options
process.with_options(options) creates a spawner with spawn-time options, such as a network selector, rather than context values:
local spawner = process.with_options({network = "app:tor_proxy"})
| Option | Type | Description |
|---|---|---|
network |
string | Registry ID of a network.* entry to use for the child's outbound connections |
terminal |
string | Viewport grant that attaches a virtual terminal to the child |
Permission: process.context on "context"; selecting a network additionally requires network.select on that network ID.
Terminal Attachment
A terminal grant comes from viewport:grant() and gives the child a terminal port of its own, so it can use the TTY module exactly as it would on a terminal host:
local view = assert(tty.viewport({width = 80, height = 24}))
local child = assert(process.with_options({terminal = assert(view:grant())})
:spawn_monitored("app:child", "app:workers"))
The grant is one-shot and is consumed at admission: a rejected start leaves it unresolved and reusable, a child that resolves the port consumes it permanently, and a host that does not support terminal attachments rejects the spawn rather than dropping the option. The spawning process keeps reading the child's frames through the viewport it created. See Terminal.
SpawnBuilder Methods
SpawnBuilder is immutable; each configuration method returns a new instance:
spawner:with_context(values) -- Add context values
spawner:with_actor(actor) -- Set security actor
spawner:with_scope(scope) -- Set security scope
spawner:with_name(name) -- Register name at start; if taken, spawn returns the existing PID and queued messages go to it
spawner:with_message(topic, ...) -- Queue message to send after spawn
spawner:with_options(options) -- Merge spawn-time options (e.g. network)
Permission: process.security on "security" for :with_actor() and :with_scope()
Spawner Spawn Methods
spawner:spawn(id, host, ...)
spawner:spawn_monitored(id, host, ...)
spawner:spawn_linked(id, host, ...)
spawner:spawn_linked_monitored(id, host, ...)
All SpawnBuilder spawn methods require process.host on the host ID in addition to the applicable process.spawn, process.spawn.monitored, and process.spawn.linked permissions.
Spawner Exec
local result, err = spawner:exec(id, host, ...)
This method runs the target process synchronously with the builder's context, actor, and scope, then returns its result. A deferred worker can use with_actor and with_scope to execute with an owner's identity.
Permissions: process.exec on process id, process.host on host id
Name Registry
Register a process under a name so callers can use the name instead of its PID. Functions that accept a destination, including send, terminate, cancel, monitor, and link, also accept registered names.
local ok, err = process.registry.register(name) -- self, local scope
local pid, err = process.registry.lookup(name)
local ok, err = process.registry.unregister(name)
Scope
The optional scope argument selects the name's consistency guarantee and defaults to LOCAL. See the Cluster Guide for the complete model.
| Constant | Visibility | Guarantee |
|---|---|---|
process.registry.LOCAL |
this node only | Instant, node-local |
process.registry.EVENTUAL |
cluster-wide | Eventually consistent (gossip) |
process.registry.CONSISTENT |
cluster-wide | Linearizable singleton (Raft) |
process.registry.STRONG |
cluster-wide | Consistent + every live node acknowledges |
On a standalone node, only LOCAL is available; cluster scopes require clustering.
register
local ok, err = process.registry.register(name, pid, scope)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | yes | Name to register | |
pid |
string | no | self | PID to register; defaults to the calling process |
scope |
number | no | LOCAL |
One of the scope constants above |
Returns true on success, or nil, error on failure. Conflicts (name already registered to a different PID) return errors.ALREADY_EXISTS. Registering the same name to the same PID is idempotent. A STRONG registration blocks until every live node acknowledges or the reservation deadline expires; on timeout it returns an error.
Registering on behalf of a different PID additionally requires the process.registry.foreign permission on the target PID.
lookup
local pid, err = process.registry.lookup(name)
Returns the registered PID string, or nil, error with kind errors.NOT_FOUND when the name is not registered.
unregister
local ok, err = process.registry.unregister(name, scope)
scope defaults to LOCAL and must match the scope the name was registered under. For CONSISTENT and STRONG, the owning process is the one allowed to unregister; unregistering a name owned by another PID returns false. Names also release automatically when the owning process exits (and, for cluster scopes, when its node leaves), so explicit unregister is for early release.
Permissions
Permission checks evaluate the caller's security actor against the target resource.
Policy Evaluation
Policies can allow or deny an operation based on:
- Actor: The security principal making the request
- Action: The operation being performed (e.g.,
process.send) - Resource: The target (PID, process id, host id, or name)
- Attributes: Additional context including
pid(caller's process ID)
Permission Reference
| Permission | Functions | Resource |
|---|---|---|
process.spawn |
spawn*() |
process id |
process.spawn.monitored |
spawn_monitored(), spawn_linked_monitored() |
process id |
process.spawn.linked |
spawn_linked(), spawn_linked_monitored() |
process id |
process.host |
module-level spawn(), all SpawnBuilder spawn methods, exec() |
host id |
process.send |
send() |
target PID |
process.exec |
exec() |
process id |
process.terminate |
terminate() |
target PID |
process.cancel |
cancel() |
target PID |
process.monitor |
monitor() |
target PID |
process.unmonitor |
unmonitor() |
target PID |
process.link |
link() |
target PID |
process.unlink |
unlink() |
target PID |
process.context |
with_context(), with_options() |
"context" |
process.security |
:with_actor(), :with_scope() |
"security" |
process.registry.register |
registry.register() |
name |
process.registry.unregister |
registry.unregister() |
name |
process.registry.foreign |
registry.register() |
target PID |
Cluster name scopes are authorized by scope-suffixed variants of these actions (process.registry.register.eventual, .consistent, .strong, and the matching unregister actions), so a policy can grant local naming separately from cluster-wide naming.
Multiple Permissions
Some operations require multiple permissions:
| Operation | Required Permissions |
|---|---|
spawn() |
process.spawn + process.host |
module-level spawn_monitored() |
process.spawn + process.spawn.monitored |
module-level spawn_linked() |
process.spawn + process.spawn.linked |
module-level spawn_linked_monitored() |
process.spawn + process.spawn.monitored + process.spawn.linked |
SpawnBuilder:spawn() |
process.spawn + process.host |
SpawnBuilder:spawn_monitored() |
process.spawn + process.spawn.monitored + process.host |
SpawnBuilder:spawn_linked() |
process.spawn + process.spawn.linked + process.host |
SpawnBuilder:spawn_linked_monitored() |
process.spawn + process.spawn.monitored + process.spawn.linked + process.host |
exec() |
process.exec + process.host |
| spawn with custom actor/scope | spawn permissions + process.security |
Errors
| Condition | Kind |
|---|---|
| No context found | errors.INTERNAL |
| Frame context not found | errors.INTERNAL |
| Missing required arguments | errors.INVALID |
Reserved topic prefix (@) |
errors.INVALID |
| Destination is neither a PID nor a registered name | errors.NOT_FOUND |
| Name not registered | errors.NOT_FOUND |
| Permission denied | errors.PERMISSION_DENIED |
| Name already registered | errors.ALREADY_EXISTS |
See Error Handling for working with errors.
See Also
- Channels - In-process coroutine coordination
- Message Queue - Queue-based messaging
- Functions - Function invocation
- Supervision - Process lifecycle management
- Cluster - Name scopes and cluster-wide naming