Skip to content

Commit 858cee8

Browse files
committed
Forward port changes from v0.6 release branch
Merge a number of fixes and improvements from release-0.6 into main. Including UART improvements on ESP32 and "run GC when there is a memory fragment to copy message data", in order to avoid a out of memory
2 parents 0296bb3 + 7607279 commit 858cee8

File tree

10 files changed

+455
-100
lines changed

10 files changed

+455
-100
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
with nodejs and emscripten)
3636
- Added preliminary support for ESP32P4 (no networking support yet).
3737
- Added memory info in `out_of_memory` crash logs to help developers fix memory issues.
38+
- Added documentation and function specs for uart driver
39+
- Added `uart:read/2` with a timeout parameter.
3840

3941
### Fixed
4042

@@ -73,6 +75,14 @@ bug when handling errors from BIFs used as NIFs (when called with `CALL_EXT` and
7375
- Fixed call to funs such as fun erlang:'not'/1, that make use of BIFs
7476
- Fixed potential crashes or memory leaks caused by a mistake in calculation of reference counts
7577
and a race condition in otp_socket code
78+
- Fixed an out of memory issue by forcing GC to copy data from message fragments
79+
- Fixed a bug where calling repeatedly `process_info` on a stopped process could cause an out of
80+
memory error
81+
- Fixed possible concurrency problems in ESP32 UART driver
82+
83+
### Changed
84+
85+
- ESP32 UART driver no longer aborts because of badargs in configuration, instead raising an error
7686

7787
## [0.6.5] - 2024-10-15
7888

libs/eavmlib/src/uart.erl

Lines changed: 124 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,143 @@
1919
%
2020

2121
-module(uart).
22-
-export([open/1, open/2, close/1, read/1, write/2]).
22+
-export([open/1, open/2, close/1, read/1, read/2, write/2]).
2323

24+
-type peripheral() :: string() | binary().
25+
% The peripheral `Name' may be one of: `"UART0"' | `"UART1"' | `"UART2"' | `<<"UART0">>' | `<<"UART1">>' | `<<"UART2">>'.
26+
27+
-type uart_opts() :: [
28+
{tx, Tx_pin :: integer()}
29+
| {rx, Rx_pin :: integer()}
30+
| {rts, Rts_pin :: integer()}
31+
| {cts, Cts_pin :: integer()}
32+
| {speed, Speed :: pos_integer()}
33+
| {data_bits, 5..8}
34+
| {stop_bits, 1 | 2}
35+
| {event_queue_len, Qlen :: pos_integer()}
36+
| {flow_control, none | hardware | software}
37+
| {parity, none | even | odd}
38+
| {peripheral, peripheral()}
39+
| []
40+
].
41+
42+
%%-----------------------------------------------------------------------------
43+
%% @param Name the uart peripheral to be opened
44+
%% @param Opts uart configuration options
45+
%% @returns Pid of the driver.
46+
%% @doc Open a connection to the UART driver
47+
%%
48+
%% This function will open a connection to the UART driver.
49+
%% @end
50+
%%-----------------------------------------------------------------------------
51+
-spec open(Name :: peripheral(), Opts :: uart_opts()) -> Pid :: pid() | {error, _Reason :: term()}.
2452
open(Name, Opts) ->
2553
open([{peripheral, Name} | Opts]).
2654

55+
%%-----------------------------------------------------------------------------
56+
%% @param Opts uart configuration options
57+
%% @returns Pid of the driver.
58+
%% @doc Open a connection to the UART driver default port
59+
%%
60+
%% This function will open a connection to the UART driver.
61+
%% @end
62+
%%-----------------------------------------------------------------------------
63+
-spec open(Opts :: uart_opts()) -> Pid :: pid() | {error, _Reason :: term()}.
2764
open(Opts) ->
2865
open_port({spawn, "uart"}, migrate_config(Opts)).
2966

30-
close(Pid) ->
67+
%%-----------------------------------------------------------------------------
68+
%% @param Pid of the uart port to be closed
69+
%% @returns ok.
70+
%% @doc Close a port connection to the UART driver
71+
%%
72+
%% This function will close the given port connection to the UART driver.
73+
%% @end
74+
%%-----------------------------------------------------------------------------
75+
-spec close(Pid :: pid()) -> ok | {error, _Reason :: term()}.
76+
close(Pid) when is_pid(Pid) ->
3177
port:call(Pid, close).
3278

33-
read(Pid) ->
79+
%%-----------------------------------------------------------------------------
80+
%% @param Pid of the uart port to be read
81+
%% @returns {ok, Data} or {error, Reason}
82+
%% @doc Read data from a UART port
83+
%%
84+
%% This function will return any data that is available, or return
85+
%% a `{error, timeout}' tuple. The driver will sent the next available
86+
%% data from the UART driver to the process that made the last read.
87+
%% Example:
88+
%% ```
89+
%% Data = case uart:read(Uart) of
90+
%% {ok, Binary} -> Binary;
91+
%% {error, timeout} ->
92+
%% receive
93+
%% {ok, RecvBinary} -> RecvBinary;
94+
%% Error -> error(Error)
95+
%% end;
96+
%% Error -> error(Error)
97+
%% end,
98+
%% '''
99+
%% Any attempt by another (or the same process) to read from uart before the
100+
%% next uart payload is sent by the driver will result in `{error, ealready}'.
101+
%% @end
102+
%%-----------------------------------------------------------------------------
103+
-spec read(Pid :: pid()) -> {ok, Data :: iodata()} | {error, _Reason :: term()}.
104+
read(Pid) when is_pid(Pid) ->
34105
port:call(Pid, read).
35106

36-
write(Pid, B) ->
37-
case is_iolist(B) of
107+
%%-----------------------------------------------------------------------------
108+
%% @param Pid of the uart port to be read
109+
%% @param Timeout millisecond to wait for data to become available
110+
%% @returns `{ok, Data}', or `{error, Reason}'
111+
%% @doc Read data from a UART port
112+
%%
113+
%% This function will return any data that is available within the
114+
%% timeout period to the process. After the timeout has expired a new
115+
%% read command may be used regardless of whether the last read was
116+
%% sent a payload.
117+
%% Example:
118+
%% ```
119+
%% Data = case uart:read(Uart, 3000) of
120+
%% {ok, Bin} -> Bin;
121+
%% {error, timeout} -> <<"">>;
122+
%% Error -> error_handler_fun(Uart, Error)
123+
%% end,
124+
%% '''
125+
%% Any data sent to the esp32 over uart between reads with a timeout will
126+
%% be lost, so be sure this is what you want. Most applications will want
127+
%% a single process to read from UART and continue to listen until a payload
128+
%% is received, and likely pass the payload off for processing and
129+
%% immediately begin another read.
130+
%% @end
131+
%%-----------------------------------------------------------------------------
132+
-spec read(Pid :: pid(), Timeout :: pos_integer()) ->
133+
{ok, Data :: iodata()} | {error, _Reason :: term()}.
134+
read(Pid, Timeout) when is_pid(Pid) ->
135+
case port:call(Pid, read, Timeout) of
136+
{error, timeout} ->
137+
port:call(Pid, cancel_read),
138+
{error, timeout};
139+
Result ->
140+
Result
141+
end.
142+
143+
%%-----------------------------------------------------------------------------
144+
%% @param Pid of the uart port to be written to
145+
%% @param Data to be written to the given uart port
146+
%% @returns ok or {error, Reason}
147+
%% @doc Write data to a UART port
148+
%%
149+
%% This function will write the given data to the UART port.
150+
%% @end
151+
%%-----------------------------------------------------------------------------
152+
-spec write(Pid :: pid(), Data :: iodata()) -> ok | {error, _Reason :: term()}.
153+
write(Pid, Data) ->
154+
case is_iolist(Data) andalso is_pid(Pid) of
38155
true ->
39-
port:call(Pid, {write, B});
156+
port:call(Pid, {write, Data});
40157
false ->
41-
throw(badarg)
158+
error(badarg)
42159
end.
43160

44161
%% @private

src/libAtomVM/context.c

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <fenv.h>
2424
#include <math.h>
2525

26+
#include "defaultatoms.h"
2627
#include "dictionary.h"
2728
#include "erl_nif.h"
2829
#include "erl_nif_priv.h"
@@ -196,11 +197,22 @@ void context_process_process_info_request_signal(Context *ctx, struct BuiltInAto
196197
{
197198
Context *target = globalcontext_get_process_lock(ctx->global, signal->sender_pid);
198199
if (target) {
199-
term ret;
200-
if (context_get_process_info(ctx, &ret, signal->atom)) {
201-
mailbox_send_term_signal(target, TrapAnswerSignal, ret);
200+
size_t term_size;
201+
if (context_get_process_info(ctx, NULL, &term_size, signal->atom, NULL)) {
202+
Heap heap;
203+
if (UNLIKELY(memory_init_heap(&heap, term_size) != MEMORY_GC_OK)) {
204+
mailbox_send_built_in_atom_signal(target, TrapExceptionSignal, OUT_OF_MEMORY_ATOM);
205+
} else {
206+
term ret;
207+
if (context_get_process_info(ctx, &ret, NULL, signal->atom, &heap)) {
208+
mailbox_send_term_signal(target, TrapAnswerSignal, ret);
209+
} else {
210+
mailbox_send_built_in_atom_signal(target, TrapExceptionSignal, ret);
211+
}
212+
memory_destroy_heap(&heap, ctx->global);
213+
}
202214
} else {
203-
mailbox_send_built_in_atom_signal(target, TrapExceptionSignal, ret);
215+
mailbox_send_built_in_atom_signal(target, TrapExceptionSignal, BADARG_ATOM);
204216
}
205217
globalcontext_get_process_unlock(ctx->global, target);
206218
} // else: sender died
@@ -264,7 +276,7 @@ size_t context_size(Context *ctx)
264276
+ memory_heap_memory_size(&ctx->heap) * BYTES_PER_TERM;
265277
}
266278

267-
bool context_get_process_info(Context *ctx, term *out, term atom_key)
279+
bool context_get_process_info(Context *ctx, term *out, size_t *term_size, term atom_key, Heap *heap)
268280
{
269281
size_t ret_size;
270282
switch (atom_key) {
@@ -289,16 +301,19 @@ bool context_get_process_info(Context *ctx, term *out, term atom_key)
289301
break;
290302
}
291303
default:
292-
*out = BADARG_ATOM;
304+
if (out != NULL) {
305+
*out = BADARG_ATOM;
306+
}
293307
return false;
294308
}
295-
296-
if (UNLIKELY(memory_ensure_free(ctx, ret_size) != MEMORY_GC_OK)) {
297-
*out = OUT_OF_MEMORY_ATOM;
298-
return false;
309+
if (term_size != NULL) {
310+
*term_size = ret_size;
311+
}
312+
if (out == NULL) {
313+
return true;
299314
}
300315

301-
term ret = term_alloc_tuple(2, &ctx->heap);
316+
term ret = term_alloc_tuple(2, heap);
302317
switch (atom_key) {
303318
// heap_size size in words of the heap of the process
304319
case HEAP_SIZE_ATOM: {
@@ -361,7 +376,7 @@ bool context_get_process_info(Context *ctx, term *out, term atom_key)
361376
struct Monitor *monitor = GET_LIST_ENTRY(item, struct Monitor, monitor_list_head);
362377
// Links are struct Monitor entries with ref_ticks equal to 0
363378
if (monitor->ref_ticks == 0) {
364-
list = term_list_prepend(monitor->monitor_obj, list, &ctx->heap);
379+
list = term_list_prepend(monitor->monitor_obj, list, heap);
365380
}
366381
}
367382
term_put_tuple_element(ret, 1, list);

src/libAtomVM/context.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,12 +396,14 @@ void context_process_flush_monitor_signal(Context *ctx, uint64_t ref_ticks, bool
396396
* @brief Get process information.
397397
*
398398
* @param ctx the context being executed
399-
* @param out the answer term
399+
* @param out the answer term. Can be NULL if only the size matters.
400+
* @param term_size the size of the answer term, in words.
400401
* @param atom_key the key representing the info to get
402+
* @param heap the heap to allocate the answer to
401403
* @return \c true if successful, \c false in case of an error in which case
402-
* *out is filled with an exception atom
404+
* *out is filled with an exception atom if it was not NULL
403405
*/
404-
bool context_get_process_info(Context *ctx, term *out, term atom_key);
406+
bool context_get_process_info(Context *ctx, term *out, size_t *term_size, term atom_key, Heap *heap);
405407

406408
/**
407409
* @brief Half-link process to another process

src/libAtomVM/memory.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ enum MemoryGCResult memory_ensure_free_with_roots(Context *c, size_t size, size_
159159
// Target heap size depends on:
160160
// - alloc_mode (MEMORY_FORCE_SHRINK takes precedence)
161161
// - heap growth strategy
162-
bool should_gc = free_space < size || (alloc_mode == MEMORY_FORCE_SHRINK);
162+
bool should_gc = free_space < size || (alloc_mode == MEMORY_FORCE_SHRINK) || c->heap.root->next != NULL;
163163
size_t memory_size = 0;
164164
if (!should_gc) {
165165
switch (c->heap_growth_strategy) {

src/libAtomVM/nifs.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2738,7 +2738,16 @@ static term nif_erlang_process_info(Context *ctx, int argc, term argv[])
27382738

27392739
term ret = term_invalid_term();
27402740
if (ctx == target) {
2741-
if (!context_get_process_info(ctx, &ret, item)) {
2741+
size_t term_size;
2742+
if (UNLIKELY(!context_get_process_info(ctx, NULL, &term_size, item, NULL))) {
2743+
globalcontext_get_process_unlock(ctx->global, target);
2744+
RAISE_ERROR(BADARG_ATOM);
2745+
}
2746+
if (UNLIKELY(memory_ensure_free_opt(ctx, term_size, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
2747+
globalcontext_get_process_unlock(ctx->global, target);
2748+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
2749+
}
2750+
if (UNLIKELY(!context_get_process_info(ctx, &ret, NULL, item, &ctx->heap))) {
27422751
globalcontext_get_process_unlock(ctx->global, target);
27432752
RAISE_ERROR(ret);
27442753
}

0 commit comments

Comments
 (0)