Skip to content

Commit f660ded

Browse files
committed
Merge pull request #1641 from feature/distributed-erlang
Distributed Erlang Despite the title, this PR introduces countless improvements. Some manual updates to 3rd party NIFs and components are expected, but most of existing code should continue to work. These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 0b7a9e6 + 906389e commit f660ded

File tree

128 files changed

+10092
-797
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

128 files changed

+10092
-797
lines changed

.github/workflows/run-tests-with-beam.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ jobs:
142142
working-directory: build
143143
run: |
144144
export PATH="${{ matrix.path_prefix }}$PATH"
145-
erl -pa tests/libs/estdlib/ -pa tests/libs/estdlib/beams/ -pa libs/etest/src/beams -s tests -s init stop -noshell
145+
erl -pa tests/libs/estdlib/ -pa tests/libs/estdlib/beams/ -pa libs/etest/src/beams -pa libs/eavmlib/src/beams -s tests -s init stop -noshell
146146
147147
# Test
148148
- name: "Run tests/libs/etest/test_eunit with OTP eunit"

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010
- Added a limited implementation of the OTP `ets` interface
1111
- Added `code:all_loaded/0` and `code:all_available/0`
12+
- Added `erlang:split_binary/2`
13+
- Added `inet:getaddr/2`
14+
- Added support for external pids and encoded pids in external terms
15+
- Added support for external refs and encoded refs in external terms
16+
- Introduce ports to represent native processes and added support for external ports and encoded ports in external terms
17+
- Added `atomvm:get_creation/0`, equivalent to `erts_internal:get_creation/0`
1218
- Added menuconfig option for enabling USE_USB_SERIAL, eg. serial over USB for certain ESP32-S2 boards etc.
1319
- Partial support for `erlang:fun_info/2`
1420
- Added support for `registered_name` in `erlang:process_info/2` and `Process.info/2`
@@ -19,6 +25,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1925
- Added `esp:partition_read/3`, and documentation for `esp:partition_erase_range/2/3` and `esp:partition_write/3`
2026
- Added support for list insertion in 'ets:insert/2'.
2127
- Support to OTP-28
28+
- Added `atomvm:subprocess/4` to perform pipe/fork/execve on POSIX platforms
29+
- Added `externalterm_to_term_with_roots` to efficiently preserve roots when allocating memory for external terms.
30+
- Added `erl_epmd` client implementation to epmd using `socket` module
31+
- Added support for socket asynchronous API for `recv`, `recvfrom` and `accept`.
32+
- Added support for UDP multicast with socket API.
2233
- Added support for `ets:update_counter/3` and `ets:update_counter/4`.
2334
- Added `erlang:+/1`
2435
- Added `lists:append/1` and `lists:append/2`
@@ -33,10 +44,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3344
- Added `code:is_loaded/1` and `code:which/1`
3445
- Added several `io_lib` functions including `io_lib:fwrite/2` and `io_lib:write_atom/1`
3546
- Added `init:get_argument/1`, `init:get_plain_arguments/0` and `init:notify_when_started/1`
47+
- Added `application:get_env/2`
3648
- Added CodeQL analysis to esp32, stm32, pico, and wasm workflows
3749

50+
### Changed
51+
52+
- Removed `externalterm_to_term_copy` added in [0.6.5] and introduced flags to `externalterm_to_term` to perform copy.
53+
3854
### Fixed
55+
3956
- ESP32: improved sntp sync speed from a cold boot.
57+
- Fixed `gen_server` internal messages to match OTP so it works across erlang distribution
4058
- Utilize reserved `phy_init` partition on ESP32 to store wifi calibration for faster connections.
4159
- Support for zero count in `lists:duplicate/2`.
4260
- packbeam: fix memory leak preventing building with address sanitizer

UPDATING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66

77
# AtomVM Update Instructions
88

9+
## v0.6.6 -> Unreleased
10+
- Ports now represent the native processes. Port drivers should return a port (instead of a pid),
11+
by using `term_port_from_local_process_id` instead of `term_from_local_process_id`. Sockets, from
12+
port socket driver, are also represented by a port and some matching code may need to be updated from
13+
`is_pid/1` to `is_port/1`.
14+
- Ports and pids can be registered. Function `globalcontext_get_registered_process` result now is
15+
a term that can be a `port()` or a `pid()`.
16+
917
## v0.6.4 -> v0.6.5
1018

1119
- ESP32: `esp32boot.avm` doesn't contain anymore Elixir standard library, use instead

doc/src/distributed-erlang.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<!--
2+
Copyright 2025 Paul Guyot <pguyot@kallisys.net>
3+
SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
4+
-->
5+
6+
# Distributed Erlang
7+
8+
For a generic introduction to Distributed Erlang Systems, please refer to the [dedicated section](https://www.erlang.org/doc/system/distributed.html) of Erlang/OTP documentation.
9+
10+
AtomVM provides an implementation of Erlang distribution protocol and AtomVM nodes can take part in clusters with both AtomVM and BEAM nodes.
11+
12+
Distribution is currently available on all platforms with TCP/IP communication, namely:
13+
- Generic Unix
14+
- ESP32
15+
- RP2 (Pico)
16+
17+
Two examples are provided:
18+
19+
- disterl in `examples/erlang/disterl.erl`: distribution on Unix systems
20+
- epmd\_disterl in `examples/esp32/epmd_disterl.erl`: distribution on ESP32 devices
21+
22+
## Starting and stopping distribution
23+
24+
Distribution has to be started programmatically. Following Erlang/OTP, distribution relies on `kernel` which needs to be started.
25+
26+
The following lines will start distribution on Unix systems with long name `atomvm@127.0.0.1`.
27+
28+
```erlang
29+
{ok, _KernelPid} = kernel:start(normal, []),
30+
{ok, _NetKernelPid} = net_kernel:start('atomvm@127.0.0.1', #{name_domain => longnames}),
31+
ok = net_kernel:set_cookie(<<"AtomVM">>).
32+
```
33+
34+
`net_kernel:stop/0` can be used to stop distribution.
35+
36+
## `epmd`
37+
38+
AtomVM nodes can use Erlang/OTP's epmd on Unix systems. AtomVM is also bundled with a pure Erlang implementation of `epmd` which can be used on all platforms. Module is called `epmd`, to be distinguished from `erl_epmd` which is the client.
39+
40+
AtomVM's epmd daemon can be started with:
41+
42+
```erlang
43+
{ok, _EPMDPid} = epmd:start_link([]).
44+
```
45+
46+
This has to be called before invoking `net_kernel:start/2`.
47+
48+
## Erlang/OTP compatibility
49+
50+
AtomVM can connect to Erlang/OTP 24 and higher.
51+
52+
## Security
53+
54+
AtomVM supports cookie authentication. However, distribution over TLS is not supported yet.
55+
56+
## Alternative carrier
57+
58+
Following Erlang/OTP, AtomVM supports alternative carriers with distribution modules. Please refer to [Erlang/OTP's dedicated documentation](https://www.erlang.org/doc/apps/erts/alt_dist#distribution-module).
59+
60+
The main difference is that packets exchanged by `f_recv` and `f_send` handlers must be binaries instead of list of integers, for memory usage reasons.
61+
62+
AtomVM's `f_send` has the following signature:
63+
64+
```erlang
65+
fun (DistCtrlr, Data :: binary()) -> ok | {error, Error}
66+
```
67+
68+
AtomVM's `f_recv` has the following signature:
69+
70+
```erlang
71+
fun (DistCtrlr, Length :: pos_integer(), Timeout :: timeout()) -> {ok, Packet} | {error, Reason}
72+
```
73+
74+
AtomVM's distribution is based on `socket_dist` and `socket_dist_controller` modules which can also be used with BEAM by definining `BEAM_INTERFACE` to adjust for the difference.
75+
76+
## Distribution features
77+
78+
Distribution implementation is (very) partial. The most basic features are available:
79+
- serialization of all types
80+
- epmd protocol (client and server)
81+
- message passing
82+
- monitoring processes
83+
- I/O distribution ("group leader").
84+
85+
RPC (remote procedure call) from Erlang/OTP to AtomVM is also supported.
86+
Shell is not supported yet.
87+
88+
Please do not hesitate to file issues or pull requests for additional features.

doc/src/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ AtomVM includes many advanced features, including process spawning, monitoring,
2828
atomvm-tooling.md
2929
programmers-guide
3030
network-programming-guide
31+
distributed-erlang
3132
build-instructions
3233
atomvm-internals
3334
memory-management

examples/emscripten/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ include(BuildErlang)
2525
pack_runnable(run_script run_script estdlib eavmlib)
2626
pack_runnable(call_cast call_cast eavmlib)
2727
pack_runnable(html5_events html5_events estdlib eavmlib)
28+
pack_runnable(echo_websocket echo_websocket estdlib eavmlib)
2829
pack_runnable(wasm_webserver wasm_webserver estdlib eavmlib)
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2025 Paul Guyot <pguyot@kallisys.net>
5+
%
6+
% Licensed under the Apache License, Version 2.0 (the "License");
7+
% you may not use this file except in compliance with the License.
8+
% You may obtain a copy of the License at
9+
%
10+
% http://www.apache.org/licenses/LICENSE-2.0
11+
%
12+
% Unless required by applicable law or agreed to in writing, software
13+
% distributed under the License is distributed on an "AS IS" BASIS,
14+
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
% See the License for the specific language governing permissions and
16+
% limitations under the License.
17+
%
18+
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
%
20+
21+
-module(echo_websocket).
22+
-export([start/0]).
23+
24+
-define(ECHO_WEBSOCKET_URL, <<"wss://echo.websocket.org">>).
25+
26+
start() ->
27+
register(main, self()),
28+
Supported = websocket:is_supported(),
29+
if
30+
Supported ->
31+
emscripten:run_script(
32+
[
33+
<<"window.document.getElementById('supported').innerHTML = 'yes';">>,
34+
<<"window.document.getElementById('send-button').onclick = () => { Module.cast('main', window.document.getElementById('send-text').value); };">>
35+
],
36+
[main_thread, async]
37+
),
38+
websocket_loop();
39+
true ->
40+
emscripten:run_script(
41+
[
42+
<<"window.document.getElementById('supported').innerHTML = 'no';">>
43+
],
44+
[main_thread, async]
45+
)
46+
end.
47+
48+
websocket_loop() ->
49+
Websocket = websocket:new(?ECHO_WEBSOCKET_URL),
50+
websocket_loop(Websocket).
51+
52+
websocket_loop(Websocket) ->
53+
ReadyState = websocket:ready_state(Websocket),
54+
URL = websocket:url(Websocket),
55+
Protocol = websocket:protocol(Websocket),
56+
Extensions = websocket:extensions(Websocket),
57+
emscripten:run_script(
58+
[
59+
<<"window.document.getElementById('ready-state').innerHTML = '">>,
60+
atom_to_list(ReadyState),
61+
<<"';">>,
62+
<<"window.document.getElementById('url').innerHTML = '">>,
63+
URL,
64+
<<"';">>,
65+
<<"window.document.getElementById('protocol').innerHTML = '">>,
66+
Protocol,
67+
<<"';">>,
68+
<<"window.document.getElementById('extensions').innerHTML = '">>,
69+
Extensions,
70+
<<"';">>
71+
],
72+
[main_thread, async]
73+
),
74+
receive
75+
{emscripten, {cast, Message}} ->
76+
emscripten:run_script(
77+
[
78+
<<"const e = window.document.createElement('div');">>,
79+
<<"e.classList.add('client-msg');">>,
80+
<<"e.append(\"">>,
81+
escape_js_str(binary_to_list(Message)),
82+
<<"\");">>,
83+
<<"window.document.getElementById('transcript').appendChild(e);">>
84+
],
85+
[main_thread, async]
86+
),
87+
ok = websocket:send_binary(Websocket, Message),
88+
websocket_loop(Websocket);
89+
{websocket_open, Websocket} ->
90+
emscripten:run_script(
91+
[
92+
<<"window.document.getElementById('send-text').disabled = false;">>,
93+
<<"window.document.getElementById('send-button').disabled = false;">>
94+
],
95+
[main_thread, async]
96+
),
97+
websocket_loop(Websocket);
98+
{websocket, Websocket, Data} ->
99+
emscripten:run_script(
100+
[
101+
<<"const e = window.document.createElement('div');">>,
102+
<<"e.classList.add('server-msg');">>,
103+
<<"e.append(\"">>,
104+
escape_js_str(binary_to_list(Data)),
105+
<<"\");">>,
106+
<<"window.document.getElementById('transcript').appendChild(e);">>
107+
],
108+
[main_thread, async]
109+
),
110+
websocket_loop(Websocket);
111+
{websocket_error, Websocket} ->
112+
emscripten:run_script(
113+
[
114+
<<"window.document.getElementById('send-text').disabled = false;">>,
115+
<<"window.document.getElementById('send-button').disabled = false;">>,
116+
<<"const e = window.document.createElement('div');">>,
117+
<<"e.classList.add('error-msg');">>,
118+
<<"e.append('Error');">>,
119+
<<"window.document.getElementById('transcript').appendChild(e);">>
120+
],
121+
[main_thread, async]
122+
);
123+
{websocket_closed, Websocket, {WasClean, Code, Reason}} ->
124+
emscripten:run_script(
125+
[
126+
<<"window.document.getElementById('send-text').disabled = true;">>,
127+
<<"window.document.getElementById('send-button').disabled = true;">>,
128+
<<"const e = window.document.createElement('div');">>,
129+
<<"e.classList.add('close-msg');">>,
130+
<<"const wasClean = window.document.createElement('p');">>,
131+
<<"wasClean.append(\"">>,
132+
atom_to_list(WasClean),
133+
<<"\");">>,
134+
<<"e.appendChild(wasClean);">>,
135+
<<"const code = window.document.createElement('p');">>,
136+
<<"code.append(\"">>,
137+
integer_to_list(Code),
138+
<<"\");">>,
139+
<<"e.appendChild(code);">>,
140+
<<"const reason = window.document.createElement('p');">>,
141+
<<"reason.append(\"">>,
142+
escape_js_str(binary_to_list(Reason)),
143+
<<"\");">>,
144+
<<"e.appendChild(reason);">>,
145+
<<"window.document.getElementById('transcript').appendChild(e);">>
146+
],
147+
[main_thread, async]
148+
);
149+
Other ->
150+
io:format("Unexpected message ~p\n", [Other]),
151+
websocket_loop(Websocket)
152+
end.
153+
154+
escape_js_str(Str) ->
155+
escape_js_str(Str, []).
156+
157+
escape_js_str([$" | Tail], Acc) ->
158+
escape_js_str(Tail, ["\\\"" | Acc]);
159+
escape_js_str([$\n | Tail], Acc) ->
160+
escape_js_str(Tail, ["<br />" | Acc]);
161+
escape_js_str([C | Tail], Acc) ->
162+
escape_js_str(Tail, [C | Acc]);
163+
escape_js_str([], Acc) ->
164+
lists:reverse(Acc).

0 commit comments

Comments
 (0)