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