My Gleam Program Crashes!
It is possible that a Gleam actor crashes, e.g. as in the example below. All linked processes will either receive a message, or crash themselves. Gleam actors are linked, and the creating process will also crash. If all you use is actors, your entire Gleam program will crash. This is not the promised BEAM behaviour!import gleam/erlang/process
import gleam/otp/actor
pub fn main() {
let assert Ok(a) = actor.start(Nil, loop)
actor.send(a, False)
process.sleep_forever()
}
fn loop(msg: Bool, _state: Nil) {
let assert True = msg
actor.continue(Nil)
}
This will crash, and generate an erlang crash dump.
We will look at three approaches to work with this, namely:- trapping exits (this post),
- monitoring processes, and
- using supervisors.
Trapping Exits
My use-case is running games. When a game finishes, I need to know that, so I can remove it from the list of games. When a game crashes, I have bigger problems, but at least want the other games to continue.To switch behaviour from crashing, to receiving messages, we need to 'trap exits'. When we call
process.trap_exits(True)
before starting our actor, the program will not crash, and hence sleep forever.
It will also log a stack trace for our actor, which is erlang behaviour.
In addition, we need to call process.selecting_trapped_exits()
and wait for those messages e.g. with process.select_forever()
. In my use-case, I already have a daemon that is waiting for messages, and thus has a selector. For our example, we need to create a selector, and a message type for it.
pub type MainMsg {
ActorExited(ExitMessage)
}
let s: Selector(MainMsg) = process.new_selector()
let s2 = process.selecting_trapped_exits(s, ActorExited)
Inside our main
function we create the selector. We specified the type for clarity. We added a variant specifically for the exit messages, and pass it to selecting_trapped_exits()
. If you already have a selector and a type, you still need to add a specific variant to wrap the ExitMessage
– a pattern you will see whenever you need to receive messages from multiple sources and of different types.
Waiting for the exit messages can be done as follows:
case process.select_forever(s2) {
ActorExited(ExitMessage(pid, reason)) -> Nil
}
Where we get the pid and the reason. First, the reason: normal, killed, or abnormal. The latter comes with a String that represents a complex Erlang value, that looks suspiciously like a stack trace; as far as I am concerned, not usable to extract information within the program, and no need to log it since the BEAM already logged an error report.
Second, the pid. If you have mulltipe actors and need to know which one terminated, there is actor.to_erlang_start_result()
.
import gleam/erlang/process.{type ExitMessage, ExitMessage, type Selector}
import gleam/otp/actor
pub type MainMsg {
ActorExited(ExitMessage)
}
pub fn main() {
process.trap_exits(True)
let s: Selector(MainMsg) = process.new_selector()
let s2 = process.selecting_trapped_exits(s, ActorExited)
let assert Ok(a) = actor.start(Nil, loop)
let assert Ok(_pid) = actor.to_erlang_start_result(Ok(a))
actor.send(a, False)
case process.select_forever(s2) {
ActorExited(ExitMessage(_pid, _reason)) -> Nil
}
}
fn loop(msg: Bool, _state: Nil) {
let assert True = msg
actor.continue(Nil)
}