Processes and Messaging
Processes and Messaging
Section titled “Processes and Messaging”Spawn isolated processes and communicate via message passing.
Overview
Section titled “Overview”Processes provide isolated execution units that communicate through message passing. Each process has its own inbox and can subscribe to specific message topics.
This page is a primer: each snippet shows one API in isolation. For a complete runnable application that wires spawning, monitoring, and messaging together, see the Echo Service tutorial.
Key concepts:
- Spawn processes with
process.spawn()and variants - Send messages to PIDs or registered names via topics
- Receive messages using
process.listen()orprocess.inbox() - Monitor process lifecycle with events
- Link processes for coordinated failure handling
Spawning Processes
Section titled “Spawning Processes”Spawn a new process from an entry reference.
local pid, err = process.spawn("app.test.process:echo_worker", "app:processes", "hello")if err then return false, "spawn failed: " .. errend
-- pid is a string identifier for the spawned processprint("Started worker:", pid)Parameters:
- Entry reference (e.g.,
"app.test.process:echo_worker") - Host reference (e.g.,
"app:processes") - Optional arguments passed to worker’s main function
Getting Your Own PID
Section titled “Getting Your Own PID”local my_pid = process.pid()-- Returns string PID of current processMessage Passing
Section titled “Message Passing”Messages use a topic-based routing system. Send messages to PIDs with a topic, then receive via topic subscription or inbox.
Sending Messages
Section titled “Sending Messages”-- Send to process by PIDlocal sent, err = process.send(worker_pid, "messages", "hello from parent")if err then return false, "send failed: " .. errend
-- send returns (bool, error)Receiving via Topic Subscription
Section titled “Receiving via Topic Subscription”Subscribe to specific topics using process.listen():
-- Worker that listens for messages on "messages" topiclocal function main() local ch = process.listen("messages")
local msg = ch:receive() if msg then -- msg is the payload directly print("Received:", msg) return true end
return falseend
return { main = main }Receiving via Inbox
Section titled “Receiving via Inbox”Inbox receives messages that don’t match any topic listener:
local function main() local inbox_ch = process.inbox() local specific_ch = process.listen("specific_topic")
while true do local result = channel.select({ specific_ch:case_receive(), inbox_ch:case_receive() })
if result.channel == specific_ch then -- Messages to "specific_topic" arrive here local payload = result.value elseif result.channel == inbox_ch then -- Messages to any OTHER topic arrive here local msg = result.value print("Inbox got:", msg:topic(), msg:payload():data()) end endendMessage Mode for Sender Info
Section titled “Message Mode for Sender Info”Use { message = true } to access sender PID and topic:
-- Worker that echoes messages back to senderlocal function main() local ch = process.listen("echo", { message = true })
local msg = ch:receive() if msg then local sender = msg:from() local data = msg:payload():data()
if sender then process.send(sender, "reply", data) end return true end
return falseend
return { main = main }Monitoring Processes
Section titled “Monitoring Processes”Monitor processes to receive EXIT events when they terminate.
Spawn with Monitoring
Section titled “Spawn with Monitoring”local events_ch = process.events()
local worker_pid, err = process.spawn_monitored( "app.test.process:events_exit_worker", "app:processes")if err then return false, "spawn failed: " .. errend
-- Wait for EXIT eventlocal timeout = time.after("3s")local result = channel.select { events_ch:case_receive(), timeout:case_receive(),}
if result.channel == timeout then return false, "timeout waiting for EXIT event"end
local event = result.valueif event.kind == process.event.EXIT then print("Worker exited:", event.from) if event.result and event.result.error then print("Exit error:", event.result.error) elseif event.result then print("Return value:", event.result.value) endendExplicit Monitoring
Section titled “Explicit Monitoring”Monitor an already running process:
local events_ch = process.events()
-- Spawn without monitoringlocal worker_pid, err = process.spawn("app.test.process:long_worker", "app:processes")if err then return false, "spawn failed: " .. errend
-- Add monitoring explicitlylocal ok, monitor_err = process.monitor(worker_pid)if monitor_err then return false, "monitor failed: " .. monitor_errend
-- Now will receive EXIT events for this workerStop monitoring:
local ok, err = process.unmonitor(worker_pid)Process Linking
Section titled “Process Linking”Link processes for coordinated lifecycle management. Linked processes receive LINK_DOWN events when linked processes fail.
Spawn Linked Process
Section titled “Spawn Linked Process”-- Child terminates if parent crashes (unless trap_links is set)local pid, err = process.spawn_linked("app.test.process:child_worker", "app:processes")if err then return false, "spawn_linked failed: " .. errendExplicit Linking
Section titled “Explicit Linking”-- Link to existing processlocal ok, err = process.link(target_pid)if err then return false, "link failed: " .. errend
-- Unlinklocal ok, err = process.unlink(target_pid)Handling LINK_DOWN Events
Section titled “Handling LINK_DOWN Events”By default, LINK_DOWN causes the process to fail. Enable trap_links to receive it as an event:
local function main() -- Enable trap_links to receive LINK_DOWN events instead of crashing local ok, err = process.set_options({ trap_links = true }) if not ok then return false, "set_options failed: " .. err end
-- Verify trap_links is enabled local opts = process.get_options() if not opts.trap_links then return false, "trap_links should be true" end
local events_ch = process.events()
-- Spawn a linked process that will fail local error_pid, err2 = process.spawn_linked( "app.test.process:error_exit_worker", "app:processes" ) if err2 then return false, "spawn error worker failed: " .. err2 end
-- Wait for LINK_DOWN event local timeout = time.after("2s") local result = channel.select { events_ch:case_receive(), timeout:case_receive(), }
if result.channel == timeout then return false, "timeout waiting for LINK_DOWN" end
local event = result.value if event.kind == process.event.LINK_DOWN then print("Linked process died:", event.from) -- Handle gracefully instead of crashing return true end
return false, "expected LINK_DOWN, got: " .. tostring(event.kind)end
return { main = main }Process Registry
Section titled “Process Registry”Register names for processes to enable name-based lookups and messaging.
Registering Names
Section titled “Registering Names”local function main() local test_name = "my_service_" .. tostring(os.time())
-- Register current process with a name local ok, err = process.registry.register(test_name) if err then return false, "register failed: " .. err end
-- Lookup the registered name local pid, lookup_err = process.registry.lookup(test_name) if lookup_err then return false, "lookup failed: " .. lookup_err end
-- Verify it resolves to our PID if pid ~= process.pid() then return false, "lookup returned wrong pid" end
return trueend
return { main = main }Unregistering Names
Section titled “Unregistering Names”-- Unregister explicitlylocal unregistered = process.registry.unregister(test_name)if not unregistered then print("Name was not registered")end
-- Lookup after unregister returns nil + errorlocal pid, err = process.registry.lookup(test_name)-- pid will be nil, err will be non-nilNames are automatically released when the process exits.
Complete Example: Monitored Worker Pool
Section titled “Complete Example: Monitored Worker Pool”This example shows a parent process spawning multiple monitored workers and tracking their completion.
-- Parent processlocal time = require("time")
local function main() local events_ch = process.events()
-- Track spawned workers local workers = {} local worker_count = 5
-- Spawn multiple monitored workers for i = 1, worker_count do local worker_pid, err = process.spawn_monitored( "app.test.process:task_worker", "app:processes", { task_id = i, value = i * 10 } )
if err then return false, "spawn worker " .. i .. " failed: " .. err end
workers[worker_pid] = { task_id = i, started = os.time() } end
-- Wait for all workers to complete local completed = 0 local timeout = time.after("10s")
while completed < worker_count do local result = channel.select { events_ch:case_receive(), timeout:case_receive(), }
if result.channel == timeout then return false, "timeout waiting for workers" end
local event = result.value if event.kind == process.event.EXIT then local worker = workers[event.from] if worker then if event.result and event.result.error then print("Worker " .. worker.task_id .. " failed:", event.result.error) else print("Worker " .. worker.task_id .. " completed:", event.result and event.result.value) end completed = completed + 1 end end end
return trueend
return { main = main }Worker process:
-- task_worker.lualocal time = require("time")
local function main(task) -- Simulate work time.sleep("100ms")
-- Process task local result = task.value * 2
return resultend
return { main = main }Next Steps
Section titled “Next Steps”- Process Module Reference - Full API documentation
- Channels - Channel operations for message handling