Skip to content

Commit 7244185

Browse files
committed
Merge pull request #1546 from pguyot/w07/gc-memory-fragments
Run GC when there is a memory fragment to copy message data Fix a bug where an out of memory error could occur if a process received messages repeatedly but didn't allocate enough memory to cause a GC. Received messages would accumulate as memory fragments and never be disposed. The fix consists in forcing a GC if there is a memory fragment, either from a message or from an allocation when running a GC was prohibited. This ensures fragments are processed and referred data from fragments are copied to the heap. This fix has a small performance penalty as the GC is always triggered where there is a fragment, even if there was no need to run a GC based on available memory. 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 aa96dd0 + 6309b72 commit 7244185

File tree

3 files changed

+47
-1
lines changed

3 files changed

+47
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ bug when handling errors from BIFs used as NIFs (when called with `CALL_EXT` and
5050
- Fixed call to funs such as fun erlang:'not'/1, that make use of BIFs
5151
- Fixed potential crashes or memory leaks caused by a mistake in calculation of reference counts
5252
and a race condition in otp_socket code
53+
- Fixed an out of memory issue by forcing GC to copy data from message fragments
5354

5455
## [0.6.5] - 2024-10-15
5556

src/libAtomVM/memory.c

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

tests/erlang_tests/test_heap_growth.erl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ start() ->
2828
ok = test_bounded_free_strategy(true),
2929
ok = test_minimum_strategy(),
3030
ok = test_fibonacci_strategy(),
31+
ok = test_messages_get_gcd(),
3132
0.
3233

3334
test_grow_beyond_min_heap_size() ->
@@ -214,3 +215,47 @@ allocate_until_heap_size_changes(Heap) ->
214215
alloc_some_heap_words(100),
215216
allocate_until_heap_size_changes(Heap)
216217
end.
218+
219+
% This test ensures that when messages are received, they eventually get gc'd
220+
test_messages_get_gcd() ->
221+
{Pid1, Ref1} = spawn_opt(
222+
fun() ->
223+
loop([])
224+
end,
225+
[monitor, {atomvm_heap_growth, minimum}]
226+
),
227+
FinalHeapSize = loop_send(Pid1, 20),
228+
Pid1 ! quit,
229+
ok =
230+
receive
231+
{'DOWN', Ref1, process, Pid1, normal} -> ok
232+
after 500 -> timeout
233+
end,
234+
ok =
235+
if
236+
FinalHeapSize < 200 -> ok;
237+
true -> {heap_size_too_large, FinalHeapSize}
238+
end,
239+
ok.
240+
241+
loop(State) when is_list(State) ->
242+
NewData =
243+
receive
244+
{get_data, Pid} ->
245+
Pid ! {data, State},
246+
State;
247+
{data, Data} ->
248+
Data;
249+
quit ->
250+
ok
251+
end,
252+
loop(NewData);
253+
loop(_Other) ->
254+
ok.
255+
256+
loop_send(Pid, 0) ->
257+
{total_heap_size, THS} = process_info(Pid, total_heap_size),
258+
THS;
259+
loop_send(Pid, N) ->
260+
Pid ! {data, alloc_some_heap_words(40)},
261+
loop_send(Pid, N - 1).

0 commit comments

Comments
 (0)