- processes, this post
- actors and subjects (not yet published)
- supervised actors (not yet published)
Processes
A process on the beam is an independently running piece of software. It:- is light weight
- has its own memory (and garbage collector)
- is scheduled by the BEAM
- communicates with other processes via messages
A gleam actor is a process, processing incoming messages. A supervisor is also a process, whose only job it is to keep an eye on other processes. Registries are processes that can look things up for you, possibly processes.
Be good
import gleam/erlang/process
pub fn main() {
process.spawn(fn() { echo "Hello, world!" })
}
$ gleam run -m spawn_002 Compiled in 0.02s Running spawn_002.main src/spawn_002.gleam:4 "Hello, world!"Too easy. A new process was spawned, running the function we passed it. The function gave us a friendly debug message and fininshed, after which the process terminated normally. Not all that exciting.
Be Bad
import gleam/erlang/process
pub fn main() {
process.spawn(fn() { panic as "Hello, world!" })
process.sleep(123)
}
$ gleam run -m spawn_panic_004
Compiled in 0.05s
Running spawn_panic_004.main
=CRASH REPORT==== 25-Feb-2026::17:57:27.407061 ===
crasher:
initial call: spawn_panic_004:'-main/0-anonymous-0-'/0
pid: <0.84.0>
registered_name: []
exception error: #{function => <<"main">>,line => 4,
message => <<"Hello, world!">>,
module => <<"spawn_panic_004">>,
file => <<"src/spawn_panic_004.gleam">>,
gleam_error => panic}
in function spawn_panic_004:'-main/0-anonymous-0-'/0 (src/spawn_panic_004.gleam:6)
ancestors: [<0.83.0>]
message_queue_len: 0
messages: []
links: [<0.83.0>]
dictionary: []
trap_exit: false
status: running
heap_size: 233
stack_size: 29
reductions: 19
neighbours:
neighbour:
pid: <0.83.0>
registered_name: []
initial_call: {erlang,apply,2}
current_function: {erlang,prepare_loading_1,2}
ancestors: []
message_queue_len: 0
links: [<0.10.0>,<0.84.0>]
trap_exit: false
status: running
heap_size: 233
stack_size: 13
reductions: 414
current_stacktrace: [{erlang,prepare_loading_1,2,[]},
{code,ensure_loaded,1,[{file,"code.erl"},{line,582}]},
{error_handler,undefined_function,3,
[{file,"error_handler.erl"},{line,86}]},
{processes@@main,run_module,1,
[{file,
"/home/kero/CodeChange/new-web-content/gleam-blog/20260225-2-processes/build/dev/erlang/processes/_gleam_artefacts/processes@@main.erl"},
{line,27}]}]
runtime error: panic
Hello, world!
stacktrace:
spawn_panic_004.-main/0-anonymous-0- src/spawn_panic_004.gleam:4
proc_lib.init_p proc_lib.erl:317
Whoops! Our process terminated abnormally, and worse, it took out our fancy application.
In Gleam, processes start linked, meaning that when one a process terminated abnormally, all linked processes are sent a special exit message. Without any precaution, that means linked processes also terminate. The list of neightbours contains one neighbour, our main function (pid 83 is linked to pid 84, so it is our main function, but I have trouble understanding what that error handler is doing).! Such a link goes in both
directions. If we were to panic in
our main function, the spawned process
would go down with it.
! Do note
the process.sleep. We need our main
process to be alive when our spawned function does. Without sleeping,
it might have terminated already, or it might not - that is what
concurrency is.
! We did not need to sleep in our previous example. I/O is done by a special process started by the BEAM. Even though our main function had terminated, current gleam (1.14) waits for one second after that. That is enough time for the I/O process to do its printing.
Spawn Unlinked
import gleam/erlang/process
pub fn main() {
process.spawn_unlinked(fn() { panic as "Hello, world!" })
process.sleep(123)
}
Compiling processes
Compiled in 0.60s
Running spawn_unlinked_007.main
=CRASH REPORT==== 25-Feb-2026::17:56:51.541772 ===
crasher:
initial call: spawn_unlinked_007:'-main/0-anonymous-0-'/0
pid: <0.84.0>
registered_name: []
exception error: #{function => <<"main">>,line => 4,
message => <<"Hello, world!">>,
module => <<"spawn_unlinked_007">>,
file => <<"src/spawn_unlinked_007.gleam">>,
gleam_error => panic}
in function spawn_unlinked_007:'-main/0-anonymous-0-'/0 (src/spawn_unlinked_007.gleam:6)
ancestors: [<0.83.0>]
message_queue_len: 0
messages: []
links: []
dictionary: []
trap_exit: false
status: running
heap_size: 233
stack_size: 29
reductions: 19
neighbours:
OK, that is better. Some logger still dumped the crash report on stdout, and as you can see the list of neightbours is now empty. Our main function was allowed to finish in peace.
Monitoring a process
Our process died. It was doing some very important things. We sometimes want to know that it terminated, so we can clean up after it or some such thing.
import gleam/erlang/process
pub fn main() {
let #(_pid, mon) = spawn_monitored(fn() { panic as "Hello, world!" })
process.new_selector()
|> process.select_specific_monitor(mon, Wrap)
|> process.selector_receive(123)
|> echo
}
pub type Msg {
Wrap(process.Down)
}
@external(erlang, "erlang", "spawn_monitor")
fn spawn_monitored(f: fn() -> Nil) -> #(process.Pid, process.Monitor)
$ gleam run -m spawn_monitored_010
Compiled in 0.02s
Running spawn_monitored_010.main
src/spawn_monitored_010.gleam:8
Ok(Wrap(ProcessDown(//erl(#Ref<0.3063925226.1700003845.102100>), //erl(<0.84.0>), Abnormal(#(dict.from_list([#(Function, "main"), #(Line, 4), #(Message, "Hello, world!"), #(Module, "spawn_monitored_010"), #(File, "src/spawn_monitored_010.gleam"), #(GleamError, Panic)]), [SpawnMonitored10(atom.create("-main/0-anonymous-0-"), 0, [File(charlist.from_string("src/spawn_monitored_010.gleam")), Line(7)])])))))
=ERROR REPORT==== 25-Feb-2026::18:34:52.022236 ===
Error in process <0.84.0> with exit value:
{#{function => <<"main">>,line => 4,message => <<"Hello, world!">>,
module => <<"spawn_monitored_010">>,
file => <<"src/spawn_monitored_010.gleam">>,gleam_error => panic},
[{spawn_monitored_010,'-main/0-anonymous-0-',0,
[{file,"src/spawn_monitored_010.gleam"},{line,7}]}]}
Quite the message we receive.
! We have an error report now, not a crash report.
! This erlang function is not in the gleam library (yet?)
Trapping exits
You can even trap exits of linked processes, as I looked at in my original blog post. It seems no better than monitoring in any way that I can think of, therefor I am not giving you a code example again.