From 80be06a7416caa709b25096ff96cc2b1d1fd09c5 Mon Sep 17 00:00:00 2001 From: Mikael Karlsson Date: Sun, 16 Nov 2025 23:51:28 +0100 Subject: [PATCH] Add example for gleam_otp static_supervisor and actor - gleam_otp uses proc_lib module for spawning actors and since this module is recently added to AtomVM it is possible now to use this. - The example is using asserts so it can be added to CI later. Signed-off-by: Mikael Karlsson --- .github/workflows/build-and-test.yaml | 2 +- CMakeModules/BuildGleam.cmake | 3 +- examples/gleam/CMakeLists.txt | 1 + examples/gleam/supervise_actor/CMakeLists.txt | 25 +++ examples/gleam/supervise_actor/README.md | 31 ++++ examples/gleam/supervise_actor/gleam.toml | 33 ++++ examples/gleam/supervise_actor/manifest.toml | 33 ++++ .../supervise_actor/src/supervise_actor.gleam | 158 ++++++++++++++++++ 8 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 examples/gleam/supervise_actor/CMakeLists.txt create mode 100644 examples/gleam/supervise_actor/README.md create mode 100644 examples/gleam/supervise_actor/gleam.toml create mode 100644 examples/gleam/supervise_actor/manifest.toml create mode 100644 examples/gleam/supervise_actor/src/supervise_actor.gleam diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 132050103e..3e9d06d9a4 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -52,7 +52,7 @@ jobs: cc: ["gcc-9", "gcc-11", "gcc-13", "clang-10", "clang-14", "clang-18"] cflags: ["-O3"] otp: ["25", "26", "27"] - gleam_version: ["1.8.0"] + gleam_version: ["1.11.1"] include: - cc: "gcc-7" diff --git a/CMakeModules/BuildGleam.cmake b/CMakeModules/BuildGleam.cmake index b688820dfe..4d3e4b0ada 100644 --- a/CMakeModules/BuildGleam.cmake +++ b/CMakeModules/BuildGleam.cmake @@ -52,6 +52,7 @@ macro(pack_gleam_runnable avm_name main) list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/${main}.gleam) list(APPEND BEAMS ${CMAKE_CURRENT_BINARY_DIR}/build/prod/erlang/${main}/ebin/${main}.beam) + list(APPEND BEAMS ${CMAKE_CURRENT_BINARY_DIR}/build/prod/erlang/*/ebin/*.beam) if(AVM_RELEASE) set(INCLUDE_LINES "") @@ -74,7 +75,7 @@ macro(pack_gleam_runnable avm_name main) COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/gleam.toml ${CMAKE_CURRENT_SOURCE_DIR}/manifest.toml ${CMAKE_CURRENT_BINARY_DIR}/ COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR}/src COMMAND gleam export erlang-shipment - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${INCLUDE_LINES} ${avm_name}.avm ${BEAMS} build/prod/erlang/gleam_stdlib/ebin/*.beam ${ARCHIVES} + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${INCLUDE_LINES} ${avm_name}.avm ${BEAMS} ${ARCHIVES} COMMENT "Packing gleam runnable ${avm_name}.avm" ) diff --git a/examples/gleam/CMakeLists.txt b/examples/gleam/CMakeLists.txt index 49bc5838a2..dd384f0271 100644 --- a/examples/gleam/CMakeLists.txt +++ b/examples/gleam/CMakeLists.txt @@ -21,3 +21,4 @@ project(examples_gleam) add_subdirectory(hello_atomvm) +add_subdirectory(supervise_actor) diff --git a/examples/gleam/supervise_actor/CMakeLists.txt b/examples/gleam/supervise_actor/CMakeLists.txt new file mode 100644 index 0000000000..71ccd7275e --- /dev/null +++ b/examples/gleam/supervise_actor/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# This file is part of AtomVM. +# +# Copyright 2025 Mikael Karlsson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +# + +project(supervise_actor) + +include(BuildGleam) + +pack_gleam_runnable(supervise_actor supervise_actor estdlib) diff --git a/examples/gleam/supervise_actor/README.md b/examples/gleam/supervise_actor/README.md new file mode 100644 index 0000000000..14f7c943fe --- /dev/null +++ b/examples/gleam/supervise_actor/README.md @@ -0,0 +1,31 @@ + + +# supervise_actor + +This is a basic supervised actors program created with +`gleam new supervise_actor`. + +AtomVM currently requires a `start/0` function which has been added to +`supervise_actor.gleam` and simply calls `main()` function. + +The project can be run using Gleam's default runtime: +```sh +gleam run # Run the project +``` + +It can also be run using AtomVM on generic unix with: +```sh +gleam export erlang-shipment +../../../build/src/AtomVM build/erlang-shipment/{supervise_actor,gleam_stdlib,gleam_erlang,gleam_otp}/ebin/*.beam ../../../build/libs/atomvmlib.avm +``` + +Alternatively, an AVM file can be generated and executed with: +```sh +gleam export erlang-shipment +../../../build/tools/packbeam/PackBEAM supervise_actor.avm build/erlang-shipment/supervise_actor/ebin/*.beam build/erlang-shipment/{gleam_stdlib,gleam_erlang,gleam_otp}/ebin/*.beam +../../../build/src/AtomVM supervise_actor.avm ../../../build/libs/atomvmlib.avm +``` diff --git a/examples/gleam/supervise_actor/gleam.toml b/examples/gleam/supervise_actor/gleam.toml new file mode 100644 index 0000000000..ba93c34c7d --- /dev/null +++ b/examples/gleam/supervise_actor/gleam.toml @@ -0,0 +1,33 @@ +# +# This file is part of AtomVM. +# +# Copyright 2025 Mikael Karlsson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +# + +name = "supervise_actor" +version = "1.0.0" + +description = "Supervisor and Actor example" +licences = ["Apache-2.0", "LGPL-2.1-or-later"] + +# For a full reference of all the available options, you can have a look at +# https://gleam.run/writing-gleam/gleam-toml/. + +[dependencies] +gleam_stdlib = ">= 0.65.0 and < 1.0.0" +gleam_erlang = ">= 1.3.0 and < 2.0.0" +gleam_otp = ">= 1.2.0 and < 2.0.0" diff --git a/examples/gleam/supervise_actor/manifest.toml b/examples/gleam/supervise_actor/manifest.toml new file mode 100644 index 0000000000..9261d1f68d --- /dev/null +++ b/examples/gleam/supervise_actor/manifest.toml @@ -0,0 +1,33 @@ +# +# This file is part of AtomVM. +# +# Copyright 2025 Mikael Karlsson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +# + +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, + { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, +] + +[requirements] +gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } +gleam_otp = { version = ">= 1.2.0 and < 2.0.0" } +gleam_stdlib = { version = ">= 0.65.0 and < 1.0.0" } diff --git a/examples/gleam/supervise_actor/src/supervise_actor.gleam b/examples/gleam/supervise_actor/src/supervise_actor.gleam new file mode 100644 index 0000000000..2e500409cb --- /dev/null +++ b/examples/gleam/supervise_actor/src/supervise_actor.gleam @@ -0,0 +1,158 @@ +// +// This file is part of AtomVM. +// +// Copyright 2025 Mikael Karlsson +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +// + +import gleam/erlang/process.{type Subject} +import gleam/io +import gleam/otp/actor +import gleam/otp/static_supervisor as supervisor +import gleam/otp/supervision + +pub type RequestMessage { + SendPid + Kill +} + +pub type ReplyMessage { + MyPidIs(process.Pid) + Aaarghhh +} + +// AtomVM currently requires a start/0 function instead of main/0 +pub fn start() -> Nil { + io.println("Hello from AtomVM!") + main() +} + +pub fn main() -> Nil { + io.println("Hello from Gleam !") + io.println("Hello from supervise_actor!") + let actor_name1 = process.new_name("actor1") + let actor_name2 = process.new_name("actor2") + let subject1: Subject(RequestMessage) = process.named_subject(actor_name1) + let subject2: Subject(RequestMessage) = process.named_subject(actor_name2) + let my_subject: Subject(ReplyMessage) = process.new_subject() + + let child_spec1 = + supervision.worker(fn() { init(my_subject, actor_name1) }) + |> supervision.restart(supervision.Permanent) + + let child_spec2 = + supervision.worker(fn() { init(my_subject, actor_name2) }) + |> supervision.restart(supervision.Permanent) + + io.println("Start a supervisor with two permanent actor children.") + let assert Ok(actor.Started(_pid, _supervisor)) = + supervisor.new(supervisor.OneForOne) + |> supervisor.add(child_spec1) + |> supervisor.add(child_spec2) + |> supervisor.start() + + io.println("Get child pids.") + let #(pid1, pid2) = echo get_pids(subject1, subject2, my_subject) + assert pid1 != pid2 + + io.println("Kill first actor, it shall be restarted with new pid.") + process.send(subject1, Kill) + let assert Ok(Aaarghhh) = process.receive(my_subject, 100) + // Give the supervisor some time to restart actor 1, + process.sleep(200) + io.println("Get child pids again, first shall be new.") + let #(pid3, pid4) = echo get_pids(subject1, subject2, my_subject) + assert pid1 != pid3 + assert pid2 == pid4 + + // Now add a new OneForAll supervisor + // Once PR 1958 is done + + // let actor_name1 = process.new_name("actor3") + // let actor_name2 = process.new_name("actor4") + // let subject1: Subject(RequestMessage) = process.named_subject(actor_name1) + // let subject2: Subject(RequestMessage) = process.named_subject(actor_name2) + // let my_subject: Subject(ReplyMessage) = process.new_subject() + + // let child_spec1 = + // supervision.worker(fn() { init(my_subject, actor_name1) }) + // |> supervision.restart(supervision.Permanent) + + // let child_spec2 = + // supervision.worker(fn() { init(my_subject, actor_name2) }) + // |> supervision.restart(supervision.Permanent) + + // let assert Ok(actor.Started(_pid, _supervisor)) = + // supervisor.new(supervisor.OneForAll) + // |> supervisor.add(child_spec1) + // |> supervisor.add(child_spec2) + // |> supervisor.start() + + // let #(pid1, pid2) = get_pids(subject1, subject2, my_subject) + // assert pid1 != pid2 + + // // Kill first actor, both actore shall be restarted with new pid + // process.send(subject1, Kill) + // let assert Ok(Aaarghhh) = process.receive(my_subject, 100) + // // Give the supervisor some time to restart actor 1, + // process.sleep(200) + // let #(pid3, pid4) = get_pids(subject1, subject2, my_subject) + // assert pid1 != pid3 + // assert pid2 != pid4 + Nil +} + +// --------- Actor stanza-------- +type State { + State(client: Subject(ReplyMessage)) +} + +fn init( + client: Subject(ReplyMessage), + name: process.Name(RequestMessage), +) -> actor.StartResult(Subject(RequestMessage)) { + let state = State(client: client) + actor.new(state) + |> actor.named(name) + |> actor.on_message(handle_message) + |> actor.start() +} + +fn handle_message(state: State, message: RequestMessage) { + case message { + SendPid -> { + actor.send(state.client, MyPidIs(process.self())) + actor.continue(state) + } + Kill -> { + actor.send(state.client, Aaarghhh) + actor.stop_abnormal("My manager killed me!") + } + } + // actor.continue(state) +} + +fn get_pids( + subject1: Subject(RequestMessage), + subject2: Subject(RequestMessage), + my_subject: Subject(ReplyMessage), +) { + process.send(subject1, SendPid) + let assert Ok(MyPidIs(pid1)) = process.receive(my_subject, 300) + process.send(subject2, SendPid) + let assert Ok(MyPidIs(pid2)) = process.receive(my_subject, 300) + #(pid1, pid2) +}