diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b68ef9..7e47d27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +- Fixed a bug where using `actor.Stop()` with other reasons than `Normal` + would stop the process with `Normal`. + ## v0.16.1 - 2025-01-20 - Fixed a bug where the static supervisor would return the incorrect value if diff --git a/src/gleam/otp/actor.gleam b/src/gleam/otp/actor.gleam index 72f6e9e..858a097 100644 --- a/src/gleam/otp/actor.gleam +++ b/src/gleam/otp/actor.gleam @@ -139,7 +139,7 @@ import gleam/dynamic.{type Dynamic} import gleam/erlang/atom import gleam/erlang/charlist.{type Charlist} import gleam/erlang/process.{ - type ExitReason, type Pid, type Selector, type Subject, Abnormal, + type ExitReason, type Pid, type Selector, type Subject, Abnormal, Killed, } import gleam/option.{type Option, None, Some} import gleam/otp/system.{ @@ -189,7 +189,7 @@ pub fn with_selector( ) -> Next(message, state) { case value { Continue(state, _) -> Continue(state, Some(selector)) - _ -> value + Stop(_) -> value } } @@ -257,7 +257,12 @@ pub type Spec(state, msg) { // TODO: Check needed functionality here to be OTP compatible fn exit_process(reason: ExitReason) -> ExitReason { - // TODO + case reason { + Abnormal(reason) -> process.send_abnormal_exit(process.self(), reason) + Killed -> process.kill(process.self()) + _ -> Nil + } + reason } @@ -404,10 +409,10 @@ fn initialise_actor( loop(self) } - // The init failed. Exit with an error. + // The init failed. Send the reason back to the parent, but exit normally. Failed(reason) -> { process.send(ack, Error(Abnormal(reason))) - exit_process(Abnormal(reason)) + exit_process(process.Normal) } } } diff --git a/test/gleam/otp/actor_test.gleam b/test/gleam/otp/actor_test.gleam index b93ec89..b242d8e 100644 --- a/test/gleam/otp/actor_test.gleam +++ b/test/gleam/otp/actor_test.gleam @@ -231,6 +231,54 @@ pub fn replace_selector_test() { |> should.equal(dynamic.from("unknown message: String")) } +pub fn abnormal_exit_can_be_trapped_test() { + process.trap_exits(True) + let exits = + process.new_selector() + |> process.selecting_trapped_exits(function.identity) + + // Make an actor exit with an abnormal reason + let assert Ok(subject) = + actor.start(Nil, fn(_, _) { actor.Stop(process.Abnormal("reason")) }) + process.send(subject, Nil) + + let trapped_reason = process.select(exits, 10) + + // Stop trapping exits, as otherwise other tests fail + process.trap_exits(False) + + // The weird reason below is because of https://github.com/gleam-lang/erlang/issues/66 + trapped_reason + |> should.equal( + Ok(process.ExitMessage( + process.subject_owner(subject), + process.Abnormal("Abnormal(\"reason\")"), + )), + ) +} + +pub fn killed_exit_can_be_trapped_test() { + process.trap_exits(True) + let exits = + process.new_selector() + |> process.selecting_trapped_exits(function.identity) + + // Make an actor exit with a killed reason + let assert Ok(subject) = + actor.start(Nil, fn(_, _) { actor.Stop(process.Killed) }) + process.send(subject, Nil) + + let trapped_reason = process.select(exits, 10) + + // Stop trapping exits, as otherwise other tests fail + process.trap_exits(False) + + trapped_reason + |> should.equal( + Ok(process.ExitMessage(process.subject_owner(subject), process.Killed)), + ) +} + fn mapped_selector(mapper: fn(a) -> ActorMessage) { let subject = process.new_subject()