Clause
← All documentation

Processes

Plan 11 step 43 (2026-10-08): spawned processes on a cooperative executor; step 44: exit reasons and error reports; step 45: sending messages; step 46: selective receive; step 47: receive timeouts; step 48: links and exit signals; step 49: monitors; step 50: registered names; step 53: ports (none); step 56: scheduler workers; step 57: cross-worker wakeups and shutdown.

Executor

One runtime runs its processes on its scheduler workers (workers, execution model). Startup makes the entry function's call the first process, the main process, and runs the executor until it ends:

Workers

Plan step 56 (2026-10-08). The executor runs processes on RuntimeOptions::schedulers worker threads: the thread that started the program and one more thread per further worker. Programs take the count from --schedulers N (1 to 1,024, runtime options); the default is one worker per logical processor, as OTP's +S.

Wakeups and shutdown (plan step 57) need no further mechanism, because every change of a process's scheduling state happens under the executor mutex:

Exits

A process other than the main one ends with an exit reason, as in OTP (detail::exit_reason, process/exits). Exit signals carry it to linked processes (links) and 'DOWN' messages to monitoring ones (monitors); error reports show it.

How the process endsExit reasonError report
Its first call returnsnormalNone
exit(Reason) (also normal, kill)ReasonNone
An error (error/1,2,3, badarith, {badmatch, V}, undef, ...){Reason, Stack}Yes
An uncaught throw(Value){{nocatch, Value}, Stack}Yes
An exit signal ends it (exit signals)The signal's reason, killed for exit(Pid, kill)None

An error report is written on stderr when the process ends, after flushing standard output, in the format of OTP's default logger handler:

=ERROR REPORT==== 8-Oct-2026::03:42:15.983000 ===
Error in process <0.8.0> with exit value:
{boom,[{crash_reports,'-main/1-fun-6-',0,[]}]}

The header has the local time; the reason is laid out as ~p does. The main process does not write one: its uncaught exception is the program's (executables).

Messages

Dest ! Msg and erlang:send(Dest, Msg) (plan step 45) evaluate Dest first and return Msg.

DestEffect
A pid of a live processMsg is copied into the receiver's heap (copying between heaps), keeping its sharing, and appended to its signal inbox
A pid of a process that has endedNothing; the send succeeds
A registered nameAs for its pid; badarg when no live process has the name
{Name, nonode@nohost} of two atomsAs for the pid registered as Name; nothing when there is none
{Name, Node} for any other nodeNothing (there are no other nodes)
Anything elsebadarg

Receive

receive (plan steps 46–47) selects among its clauses like case, over the messages of the mailbox:

Plan step 48. A link connects two processes both ways (Signals in each context: the linked pids in link order and the trap_exit flag).

Monitors

Plan step 49. A monitor is one-way: the monitoring process holds it (by reference, in Signals) and the monitored process keeps the reference and the monitoring pid, so it can send the message when it ends.

Registered names

Plan step 50. The executor keeps one table of names (atoms) to pids, and each process its own name (Signals::name).

Exit signals

Every exit signal comes from a running process (exit/2, exit_signal/2, link/1) or from the end of a process. The executor acts on it at once when its target does not run on another worker, else once the target's slice ended (workers, scheduler/signals): an ended target leaves the run queue or its wait and is finished (its links signalled, its error report written, its context released) before the sending builtin returns. A long linked chain ends process by process without recursion.

Signal at a processNot trapping exitsTrapping exits
exit(Pid, kill), exit_signal(Pid, kill)Ends with reason killedEnds with reason killed
Reason normal (from a link, or sent to another process)Nothing{'EXIT', From, normal} message
exit(self(), normal)Ends with reason normal (OTP's quirk){'EXIT', Self, normal} message
exit_signal(self(), normal)Nothing{'EXIT', Self, normal} message
Any other reason, including kill from a linkEnds with that reason{'EXIT', From, Reason} message

Ports

Ports (plan steps 57A–57F) are specified in ports: a port is linked to its opener, takes part in links, monitors, exit signals and registered names like a process, and talks to its connected process with messages. Step 57B provides identities, the port table, the port builtins and output-only {fd, In, Out} ports.

Builtins

BuiltinBehavior
spawn(Fun)badarg unless Fun is a fun; otherwise a new process calls Fun(), raising {badarity, {Fun, []}} in that process for another arity
spawn(M, F, Args)badarg unless M and F are atoms and Args a proper list; otherwise a new process calls M:F(Args...), raising undef in that process when no module exports it and no builtin has that name
spawn_link(Fun), spawn_link(M, F, Args)As spawn, and the new process is linked to the caller (links)
is_process_alive(Pid)badarg unless Pid is a pid; true while its process has not ended
spawn_monitor(Fun), spawn_monitor(M, F, Args)As spawn, returning {Pid, Ref} of a new monitor
link(Pid), unlink(Pid)badarg unless Pid is a pid (links)
monitor(process, Item)badarg for another type or item; a reference (monitors); Item is a pid or a registered name
register(Name, Pid), unregister(Name), whereis(Name), registered()See registered names; Name must be an atom
demonitor(Ref), demonitor(Ref, Options)badarg unless Ref is a reference and Options a proper list of flush and info
exit(Dest, Reason), exit_signal(Dest, Reason)badarg unless Dest is a pid or reference; true after the exit signal
process_flag(trap_exit, Bool)The previous setting; badarg for another flag or a non-boolean
erlang:send(Dest, Msg), Dest ! MsgMsg, after sending it (messages); send/2 is not auto-imported

The new process is queued behind every runnable process; spawn returns its pid at once.