diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml index f115542be..aeae5644b 100644 --- a/.github/workflows/codeql-analysis.yaml +++ b/.github/workflows/codeql-analysis.yaml @@ -39,7 +39,7 @@ concurrency: jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: actions: read contents: read @@ -61,7 +61,7 @@ jobs: uses: actions/checkout@v4 - name: "Initialize CodeQL" - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} queries: +./code-queries/term-to-non-term-func.ql,./code-queries/non-term-to-term-func.ql @@ -74,4 +74,4 @@ jobs: ninja - name: "Perform CodeQL Analysis" - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/esp32-build.yaml b/.github/workflows/esp32-build.yaml index e908ee804..aaae4d58b 100644 --- a/.github/workflows/esp32-build.yaml +++ b/.github/workflows/esp32-build.yaml @@ -29,7 +29,7 @@ concurrency: jobs: esp-idf: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: espressif/idf:${{ matrix.idf-version }} strategy: @@ -39,15 +39,16 @@ jobs: esp-idf-target: ["esp32", "esp32c3"] idf-version: - 'v5.0.7' - - 'v5.1.4' - - 'v5.2.2' - - 'v5.3.1' + - 'v5.1.5' + - 'v5.2.3' + - 'v5.3.2' + - 'v5.4' exclude: - esp-idf-target: "esp32c3" idf-version: 'v5.0.7' - esp-idf-target: "esp32c3" - idf-version: 'v5.1.4' + idf-version: 'v5.1.5' steps: - name: Checkout repo uses: actions/checkout@v4 diff --git a/.github/workflows/esp32-mkimage.yaml b/.github/workflows/esp32-mkimage.yaml index 8fd1c6d93..74e52698d 100644 --- a/.github/workflows/esp32-mkimage.yaml +++ b/.github/workflows/esp32-mkimage.yaml @@ -37,7 +37,7 @@ jobs: strategy: matrix: - idf-version: ["5.3.1"] + idf-version: ["5.3.2"] cc: ["clang-14"] cxx: ["clang++-14"] cflags: ["-O3"] diff --git a/.github/workflows/esp32-simtest.yaml b/.github/workflows/esp32-simtest.yaml index ea75c05c5..0fa7059fe 100644 --- a/.github/workflows/esp32-simtest.yaml +++ b/.github/workflows/esp32-simtest.yaml @@ -59,13 +59,26 @@ jobs: fail-fast: false # focus on device diversity. matrix: - esp-idf-target: ["esp32", "esp32s2", "esp32s3", "esp32c3", "esp32c6"] - idf-version: ${{ ((contains(github.event.head_commit.message, 'full_sim_test')||contains(github.event.pull_request.title, 'full_sim_test')) && fromJSON('["v5.1.5", "v5.2.3", "v5.3.2", "v5.4-beta1"]')) || fromJSON('["v5.3.2"]') }} - include: + esp-idf-target: + [ + "esp32", + "esp32s2", + "esp32s3", + "esp32c3", + "esp32c6", + "esp32h2", + "esp32p4", + ] + idf-version: ${{ ((contains(github.event.head_commit.message, 'full_sim_test')||contains(github.event.pull_request.title, 'full_sim_test')) && fromJSON('["v5.1.5", "v5.2.3", "v5.3.2", "v5.4"]')) || fromJSON('["v5.3.2"]') }} + exclude: - esp-idf-target: "esp32p4" - idf-version: "v5.3.2" + idf-version: "v5.1.5" + - esp-idf-target: "esp32p4" + idf-version: "v5.2.3" + - esp-idf-target: "esp32h2" + idf-version: "v5.1.5" - esp-idf-target: "esp32h2" - idf-version: "v5.3.2" + idf-version: "v5.2.3" steps: - name: Checkout repo diff --git a/.github/workflows/pico-build.yaml b/.github/workflows/pico-build.yaml index 879fe3568..01963ed55 100644 --- a/.github/workflows/pico-build.yaml +++ b/.github/workflows/pico-build.yaml @@ -33,7 +33,7 @@ concurrency: jobs: pico: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: board: ["pico", "pico_w", "pico2"] diff --git a/.github/workflows/reuse-lint.yaml b/.github/workflows/reuse-lint.yaml index ed63ea86a..cf5e72a31 100644 --- a/.github/workflows/reuse-lint.yaml +++ b/.github/workflows/reuse-lint.yaml @@ -12,7 +12,7 @@ concurrency: jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: REUSE Compliance Check diff --git a/.github/workflows/stm32-build.yaml b/.github/workflows/stm32-build.yaml index b33fcccb0..feacf4985 100644 --- a/.github/workflows/stm32-build.yaml +++ b/.github/workflows/stm32-build.yaml @@ -44,17 +44,6 @@ jobs: https://repo.hex.pm https://cdn.jsdelivr.net/hex - - name: Install arm-embedded toolchain - if: ${{ steps.builddeps-cache.outputs.cache-hit != 'true' }} - working-directory: /home/runner - run: | - set -euo pipefail - cd /home/runner - wget https://developer.arm.com/-/media/Files/downloads/gnu/11.3.rel1/binrel/arm-gnu-toolchain-11.3.rel1-x86_64-arm-none-eabi.tar.xz \ - --output-document=$RUNNER_TEMP/arm-gnu-toolchain-11.3.rel1-x86_64-arm-none-eabi.tar.xz - tar xJf $RUNNER_TEMP/arm-gnu-toolchain-11.3.rel1-x86_64-arm-none-eabi.tar.xz - pwd && ls - - name: Checkout and build libopencm3 if: ${{ steps.builddeps-cache.outputs.cache-hit != 'true' }} working-directory: /home/runner @@ -71,7 +60,7 @@ jobs: run: sudo apt update - name: "Install deps" - run: sudo apt install -y cmake gperf + run: sudo apt install -y cmake gperf gcc-arm-none-eabi - name: Checkout repo uses: actions/checkout@v4 diff --git a/.github/workflows/wasm-build.yaml b/.github/workflows/wasm-build.yaml index 58fbf84f4..a9164698e 100644 --- a/.github/workflows/wasm-build.yaml +++ b/.github/workflows/wasm-build.yaml @@ -78,7 +78,7 @@ jobs: wasm_build_and_test_node: needs: compile_tests - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: emscripten/emsdk steps: - name: Checkout repo @@ -146,7 +146,7 @@ jobs: src/platforms/emscripten/build/src/AtomVM-node-${{ github.ref_name }}.wasm.sha256 wasm_build_web: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: emscripten/emsdk steps: - name: Checkout repo @@ -176,7 +176,7 @@ jobs: wasm_test_web: needs: [compile_tests, wasm_build_web] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout repo uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index c635d9ec3..61f2b50d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for `registered_name` in `erlang:process_info/2` and `Process.info/2` - Added `net:gethostname/0` on platforms with gethostname(3). - Added `socket:getopt/2` +- Added `supervisor:terminate_child/2`, `supervisor:restart_child/2` and `supervisor:delete_child/2` - Added `atomvm:subprocess/4` to perform pipe/fork/execve on POSIX platforms - Added `externalterm_to_term_with_roots` to efficiently preserve roots when allocating memory for external terms. @@ -56,6 +57,9 @@ certain VM instructions are used. - Fixed an issue where a timeout would occur immediately in a race condition - Fixed SPI close command - Added missing lock on socket structure +- Fixed a race condition affecting multi-core MCUs where a timeout would not be properly cleared +- Fixed a double free when esp32 uart driver was closed, yielding an assert abort +- Fixed compilation with latest debian gcc-arm-none-eabi ## [0.6.5] - 2024-10-15 diff --git a/doc/release-notes.md.in b/doc/release-notes.md.in index f3f46540d..1de96f072 100644 --- a/doc/release-notes.md.in +++ b/doc/release-notes.md.in @@ -68,9 +68,10 @@ AtomVM currently supports the following versions of ESP-IDF: | IDF SDK supported versions | AtomVM support | |------------------------------|----------------| | ESP-IDF [v5.0](https://docs.espressif.com/projects/esp-idf/en/v5.0.7/esp32/get-started/index.html) | ✅ | -| ESP-IDF [v5.1](https://docs.espressif.com/projects/esp-idf/en/v5.1.4/esp32/get-started/index.html) | ✅ | -| ESP-IDF [v5.2](https://docs.espressif.com/projects/esp-idf/en/v5.2.2/esp32/get-started/index.html) | ✅ | -| ESP-IDF [v5.3](https://docs.espressif.com/projects/esp-idf/en/v5.3/esp32/get-started/index.html) | ✅ | +| ESP-IDF [v5.1](https://docs.espressif.com/projects/esp-idf/en/v5.1.5/esp32/get-started/index.html) | ✅ | +| ESP-IDF [v5.2](https://docs.espressif.com/projects/esp-idf/en/v5.2.3/esp32/get-started/index.html) | ✅ | +| ESP-IDF [v5.3](https://docs.espressif.com/projects/esp-idf/en/v5.3.2/esp32/get-started/index.html) | ✅ | +| ESP-IDF [v5.4](https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/get-started/index.html) | ✅ | Building the AtomVM virtual machine for ESP32 is optional. In most cases, you can simply download a release image from the AtomVM [release](https://github.com/atomvm/AtomVM/releases) repository. If you wish to work on development of the VM or use one on the additional drivers that are available in the [AtomVM repositories](https://github.com/atomvm) you will to build AtomVM from source. See the [Build Instructions](build-instructions.md) for information about how to build AtomVM from source code. We recommend you to use the latest subminor (patch) versions for source builds. You can check the current version used for testing in the [esp32-build.yaml](https://github.com/atomvm/AtomVM/actions/workflows/esp32-build.yaml) workflow. diff --git a/libs/estdlib/src/gen_server.erl b/libs/estdlib/src/gen_server.erl index 7248eba29..c1879e4e7 100644 --- a/libs/estdlib/src/gen_server.erl +++ b/libs/estdlib/src/gen_server.erl @@ -211,8 +211,8 @@ init_it(Starter, Module, Args, Options) -> end, case StateT of undefined -> ok; - {State, {continue, Continue}} -> loop(State, {continue, Continue}); - {State, Timeout} -> loop(State, Timeout) + {State, {continue, Continue}} -> loop(Starter, State, {continue, Continue}); + {State, Timeout} -> loop(Starter, State, Timeout) end. init_ack(Parent, Return) -> @@ -499,34 +499,34 @@ reply({Pid, Ref}, Reply) -> %% %% @private -loop(#state{mod = Mod, mod_state = ModState} = State, {continue, Continue}) -> +loop(Parent, #state{mod = Mod, mod_state = ModState} = State, {continue, Continue}) -> case Mod:handle_continue(Continue, ModState) of {noreply, NewModState} -> - loop(State#state{mod_state = NewModState}, infinity); + loop(Parent, State#state{mod_state = NewModState}, infinity); {noreply, NewModState, {continue, NewContinue}} -> - loop(State#state{mod_state = NewModState}, {continue, NewContinue}); + loop(Parent, State#state{mod_state = NewModState}, {continue, NewContinue}); {stop, Reason, NewModState} -> do_terminate(State, Reason, NewModState) end; -loop(#state{mod = Mod, mod_state = ModState} = State, Timeout) -> +loop(Parent, #state{mod = Mod, mod_state = ModState} = State, Timeout) -> receive {'$gen_call', {_Pid, _Ref} = From, Request} -> case Mod:handle_call(Request, From, ModState) of {reply, Reply, NewModState} -> ok = reply(From, Reply), - loop(State#state{mod_state = NewModState}, infinity); + loop(Parent, State#state{mod_state = NewModState}, infinity); {reply, Reply, NewModState, {continue, Continue}} -> ok = reply(From, Reply), - loop(State#state{mod_state = NewModState}, {continue, Continue}); + loop(Parent, State#state{mod_state = NewModState}, {continue, Continue}); {reply, Reply, NewModState, NewTimeout} -> ok = reply(From, Reply), - loop(State#state{mod_state = NewModState}, NewTimeout); + loop(Parent, State#state{mod_state = NewModState}, NewTimeout); {noreply, NewModState} -> - loop(State#state{mod_state = NewModState}, infinity); + loop(Parent, State#state{mod_state = NewModState}, infinity); {noreply, NewModState, {continue, Continue}} -> - loop(State#state{mod_state = NewModState}, {continue, Continue}); + loop(Parent, State#state{mod_state = NewModState}, {continue, Continue}); {noreply, NewModState, NewTimeout} -> - loop(State#state{mod_state = NewModState}, NewTimeout); + loop(Parent, State#state{mod_state = NewModState}, NewTimeout); {stop, Reason, Reply, NewModState} -> ok = reply(From, Reply), do_terminate(State, Reason, NewModState); @@ -538,11 +538,11 @@ loop(#state{mod = Mod, mod_state = ModState} = State, Timeout) -> {'$gen_cast', Request} -> case Mod:handle_cast(Request, ModState) of {noreply, NewModState} -> - loop(State#state{mod_state = NewModState}, infinity); + loop(Parent, State#state{mod_state = NewModState}, infinity); {noreply, NewModState, {continue, Continue}} -> - loop(State#state{mod_state = NewModState}, {continue, Continue}); + loop(Parent, State#state{mod_state = NewModState}, {continue, Continue}); {noreply, NewModState, NewTimeout} -> - loop(State#state{mod_state = NewModState}, NewTimeout); + loop(Parent, State#state{mod_state = NewModState}, NewTimeout); {stop, Reason, NewModState} -> do_terminate(State, Reason, NewModState); _ -> @@ -550,12 +550,14 @@ loop(#state{mod = Mod, mod_state = ModState} = State, Timeout) -> end; {'$stop', Reason} -> do_terminate(State, Reason, ModState); + {'EXIT', Parent, Reason} -> + do_terminate(State, Reason, ModState); Info -> case Mod:handle_info(Info, ModState) of {noreply, NewModState} -> - loop(State#state{mod_state = NewModState}, infinity); + loop(Parent, State#state{mod_state = NewModState}, infinity); {noreply, NewModState, NewTimeout} -> - loop(State#state{mod_state = NewModState}, NewTimeout); + loop(Parent, State#state{mod_state = NewModState}, NewTimeout); {stop, Reason, NewModState} -> do_terminate(State, Reason, NewModState); _ -> @@ -564,9 +566,9 @@ loop(#state{mod = Mod, mod_state = ModState} = State, Timeout) -> after Timeout -> case Mod:handle_info(timeout, ModState) of {noreply, NewModState} -> - loop(State#state{mod_state = NewModState}, infinity); + loop(Parent, State#state{mod_state = NewModState}, infinity); {noreply, NewModState, NewTimeout} -> - loop(State#state{mod_state = NewModState}, NewTimeout); + loop(Parent, State#state{mod_state = NewModState}, NewTimeout); {stop, Reason, NewModState} -> do_terminate(State, Reason, NewModState); _ -> diff --git a/libs/estdlib/src/supervisor.erl b/libs/estdlib/src/supervisor.erl index 8239d55e9..1db075599 100644 --- a/libs/estdlib/src/supervisor.erl +++ b/libs/estdlib/src/supervisor.erl @@ -25,14 +25,18 @@ -export([ start_link/2, start_link/3, - start_child/2 + start_child/2, + terminate_child/2, + restart_child/2, + delete_child/2 ]). -export([ init/1, handle_call/3, handle_cast/2, - handle_info/2 + handle_info/2, + terminate/2 ]). -export_type([ @@ -41,7 +45,11 @@ sup_flags/0 ]). --type restart() :: permanent | transient | temporary. +-type restart() :: + permanent + | transient + | temporary + | {terminating, permanent | transient | temporary, gen_server:from()}. -type shutdown() :: brutal_kill | timeout(). -type child_type() :: worker | supervisor. @@ -90,6 +98,15 @@ start_link(SupName, Module, Args) -> start_child(Supervisor, ChildSpec) -> gen_server:call(Supervisor, {start_child, ChildSpec}). +terminate_child(Supervisor, ChildId) -> + gen_server:call(Supervisor, {terminate_child, ChildId}). + +restart_child(Supervisor, ChildId) -> + gen_server:call(Supervisor, {restart_child, ChildId}). + +delete_child(Supervisor, ChildId) -> + gen_server:call(Supervisor, {delete_child, ChildId}). + init({Mod, Args}) -> erlang:process_flag(trap_exit, true), case Mod:init(Args) of @@ -152,6 +169,16 @@ restart_child(Pid, Reason, State) -> case lists:keyfind(Pid, #child.pid, State#state.children) of false -> {ok, State}; + #child{restart = {terminating, temporary, From}} -> + gen_server:reply(From, ok), + NewChildren = lists:keydelete(Pid, #child.pid, State#state.children), + {ok, State#state{children = NewChildren}}; + #child{restart = {terminating, Restart, From}} = Child -> + gen_server:reply(From, ok), + NewChildren = lists:keyreplace(Pid, #child.pid, State#state.children, Child#child{ + pid = undefined, restart = Restart + }), + {ok, State#state{children = NewChildren}}; #child{} = Child -> case should_restart(Reason, Child#child.restart) of true -> @@ -195,6 +222,46 @@ handle_call({start_child, ChildSpec}, _From, #state{children = Children} = State {error, _Reason} = ErrorT -> {reply, ErrorT, State} end + end; +handle_call({terminate_child, ID}, From, #state{children = Children} = State) -> + case lists:keyfind(ID, #child.id, Children) of + #child{pid = undefined} -> + {reply, ok, State}; + #child{restart = Restart} = Child -> + do_terminate(Child), + NewChild = Child#child{restart = {terminating, Restart, From}}, + NewChildren = lists:keyreplace(ID, #child.id, Children, NewChild), + {noreply, State#state{children = NewChildren}}; + false -> + {reply, {error, not_found}, State} + end; +handle_call({restart_child, ID}, _From, #state{children = Children} = State) -> + case lists:keyfind(ID, #child.id, Children) of + #child{pid = undefined} = Child -> + case try_start(Child) of + {ok, NewPid, Result} -> + NewChild = Child#child{pid = NewPid}, + NewChildren = lists:keyreplace( + ID, #child.id, Children, NewChild + ), + {reply, Result, State#state{children = NewChildren}}; + {error, _Reason} = ErrorT -> + {reply, ErrorT, State} + end; + #child{} -> + {reply, {error, running}, State}; + false -> + {reply, {error, not_found}, State} + end; +handle_call({delete_child, ID}, _From, #state{children = Children} = State) -> + case lists:keyfind(ID, #child.id, Children) of + #child{pid = undefined} -> + NewChildren = lists:keydelete(ID, #child.id, Children), + {reply, ok, State#state{children = NewChildren}}; + #child{} -> + {reply, {error, running}, State}; + false -> + {reply, {error, not_found}, State} end. handle_cast(_Msg, State) -> @@ -207,10 +274,50 @@ handle_info({'EXIT', Pid, Reason}, State) -> {shutdown, State1} -> {stop, shutdown, State1} end; +handle_info({ensure_killed, Pid}, State) -> + case lists:keyfind(Pid, #child.pid, State#state.children) of + false -> + {noreply, State}; + #child{} -> + exit(Pid, kill), + {noreply, State} + end; handle_info(_Msg, State) -> %TODO: log unexpected message {noreply, State}. +%% @hidden +terminate(_Reason, #state{children = Children} = State) -> + RemainingChildren = loop_terminate(Children, []), + loop_wait_termination(RemainingChildren), + {ok, State}. + +loop_terminate([#child{pid = undefined} | Tail], AccRemaining) -> + loop_terminate(Tail, AccRemaining); +loop_terminate([#child{pid = Pid} = Child | Tail], AccRemaining) when is_pid(Pid) -> + do_terminate(Child), + loop_terminate(Tail, [Pid | AccRemaining]); +loop_terminate([], AccRemaining) -> + AccRemaining. + +loop_wait_termination([]) -> + ok; +loop_wait_termination(RemainingChildren0) -> + receive + {'EXIT', Pid, _Reason} -> + RemainingChildren1 = lists:delete(Pid, RemainingChildren0), + loop_wait_termination(RemainingChildren1); + {ensure_killed, Pid} -> + case lists:member(Pid, RemainingChildren0) of + true -> + exit(Pid, kill), + RemainingChildren1 = lists:delete(Pid, RemainingChildren0), + loop_wait_termination(RemainingChildren1); + false -> + loop_wait_termination(RemainingChildren0) + end + end. + try_start(#child{start = {M, F, Args}} = Record) -> try case apply(M, F, Args) of @@ -229,3 +336,11 @@ try_start(#child{start = {M, F, Args}} = Record) -> error:Error -> {error, {{'EXIT', Error}, Record}} end. + +do_terminate(#child{pid = Pid, shutdown = brutal_kill}) -> + exit(Pid, kill); +do_terminate(#child{pid = Pid, shutdown = infinity}) -> + exit(Pid, shutdown); +do_terminate(#child{pid = Pid, shutdown = Timeout}) when is_integer(Timeout) -> + exit(Pid, shutdown), + erlang:send_after(Timeout, self(), {ensure_killed, Pid}). diff --git a/src/libAtomVM/context.c b/src/libAtomVM/context.c index 1e8741f58..1c8e8d81d 100644 --- a/src/libAtomVM/context.c +++ b/src/libAtomVM/context.c @@ -184,9 +184,7 @@ void context_destroy(Context *ctx) // globalcontext_get_process_lock before accessing platform_data. // Here, the context can no longer be acquired with // globalcontext_get_process_lock, so it's safe to free the pointer. - if (ctx->platform_data) { - free(ctx->platform_data); - } + free(ctx->platform_data); ets_delete_owned_tables(&ctx->global->ets, ctx->process_id, ctx->global); diff --git a/src/libAtomVM/defaultatoms.def b/src/libAtomVM/defaultatoms.def index fc978e1ad..0a5ab513b 100644 --- a/src/libAtomVM/defaultatoms.def +++ b/src/libAtomVM/defaultatoms.def @@ -166,5 +166,7 @@ X(LOCAL_ATOM, "\x5", "local") X(REGISTERED_NAME_ATOM, "\xF", "registered_name") +X(SHUTDOWN_ATOM, "\x8", "shutdown") + X(NONODE_AT_NOHOST_ATOM, "\xD", "nonode@nohost") X(NET_KERNEL_ATOM, "\xA", "net_kernel") diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index 4d0c61658..7875de3e9 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -7042,8 +7042,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) } } - // Do not print crash dump if reason is normal. - if (x_regs[0] != LOWERCASE_EXIT_ATOM || x_regs[1] != NORMAL_ATOM) { + // Do not print crash dump if reason is normal or shutdown. + if (x_regs[0] != LOWERCASE_EXIT_ATOM || (x_regs[1] != NORMAL_ATOM && x_regs[1] != SHUTDOWN_ATOM)) { dump(ctx); } diff --git a/src/libAtomVM/scheduler.c b/src/libAtomVM/scheduler.c index 21f1d7d75..ce97bbb16 100644 --- a/src/libAtomVM/scheduler.c +++ b/src/libAtomVM/scheduler.c @@ -414,11 +414,11 @@ void scheduler_cancel_timeout(Context *ctx) { GlobalContext *glb = ctx->global; - context_update_flags(ctx, ~(WaitingTimeout | WaitingTimeoutExpired), NoFlags); - struct TimerList *tw = &glb->timer_list; SMP_SPINLOCK_LOCK(&glb->timer_spinlock); timer_list_remove(tw, &ctx->timer_list_head); SMP_SPINLOCK_UNLOCK(&glb->timer_spinlock); + + context_update_flags(ctx, ~(WaitingTimeout | WaitingTimeoutExpired), NoFlags); } diff --git a/src/libAtomVM/term.h b/src/libAtomVM/term.h index e9e7fc60e..8a2f45136 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -28,6 +28,9 @@ #ifndef _TERM_H_ #define _TERM_H_ +// gcc-arm-none-eabi 13.2.1 with newlib requires this first +#include + #include #include #include diff --git a/src/platforms/esp32/components/avm_builtins/uart_driver.c b/src/platforms/esp32/components/avm_builtins/uart_driver.c index 22f9ed398..8f141d77b 100644 --- a/src/platforms/esp32/components/avm_builtins/uart_driver.c +++ b/src/platforms/esp32/components/avm_builtins/uart_driver.c @@ -309,7 +309,7 @@ static void uart_driver_do_read(Context *ctx, GenMessage gen_message) int local_pid = term_to_local_process_id(pid); if (uart_data->reader_process_pid != term_invalid_term()) { - if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2) * 2 + REF_SIZE) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2) * 2 , 1, &ref, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { ESP_LOGE(TAG, "[uart_driver_do_read] Failed to allocate space for error tuple"); globalcontext_send_message(glb, local_pid, MEMORY_ATOM); return; @@ -326,7 +326,7 @@ static void uart_driver_do_read(Context *ctx, GenMessage gen_message) if (count > 0) { int bin_size = term_binary_heap_size(count); - if (UNLIKELY(memory_ensure_free(ctx, bin_size + TUPLE_SIZE(2) * 2 + REF_SIZE) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free_with_roots(ctx, bin_size + TUPLE_SIZE(2) * 2, 1, &ref, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { ESP_LOGE(TAG, "[uart_driver_do_read] Failed to allocate space for return value"); globalcontext_send_message(glb, local_pid, MEMORY_ATOM); } @@ -387,7 +387,7 @@ static void uart_driver_do_write(Context *ctx, GenMessage gen_message) free(buffer); int local_pid = term_to_local_process_id(pid); - if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2) + REF_SIZE) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2), 1, &ref, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { ESP_LOGE(TAG, "[uart_driver_do_write] Failed to allocate space for return value"); globalcontext_send_message(glb, local_pid, MEMORY_ATOM); } @@ -406,7 +406,7 @@ static void uart_driver_do_close(Context *ctx, GenMessage gen_message) sys_unregister_listener(glb, &uart_data->listener); - if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2) + REF_SIZE) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2), 1, &ref, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { ESP_LOGE(TAG, "[uart_driver_do_close] Failed to allocate space for return value"); globalcontext_send_message(glb, local_pid, MEMORY_ATOM); } @@ -419,6 +419,7 @@ static void uart_driver_do_close(Context *ctx, GenMessage gen_message) } free(uart_data); + ctx->platform_data = NULL; } static NativeHandlerResult uart_driver_consume_mailbox(Context *ctx) diff --git a/src/platforms/esp32/components/avm_sys/sys.c b/src/platforms/esp32/components/avm_sys/sys.c index f8226c3ee..d300138c8 100644 --- a/src/platforms/esp32/components/avm_sys/sys.c +++ b/src/platforms/esp32/components/avm_sys/sys.c @@ -586,12 +586,12 @@ static void *select_thread_loop(void *arg) { GlobalContext *glb = arg; struct ESP32PlatformData *platform = glb->platform_data; - struct pollfd *fds = malloc(0); + struct pollfd *fds = NULL; while (!platform->select_thread_exit) { int select_events_poll_count = platform->select_events_poll_count; int poll_count = 1; int fd_index; - if (select_events_poll_count < 0) { + if (fds == NULL || select_events_poll_count < 0) { // Means it is dirty and should be rebuilt. struct ListHead *select_events = synclist_wrlock(&glb->select_events); size_t select_events_new_count; @@ -601,7 +601,11 @@ static void *select_thread_loop(void *arg) select_events_new_count = select_events_poll_count; } - fds = realloc(fds, sizeof(struct pollfd) * (poll_count + select_events_new_count)); + if (fds) { + fds = realloc(fds, sizeof(struct pollfd) * (poll_count + select_events_new_count)); + } else { + fds = malloc(sizeof(struct pollfd) * (poll_count + select_events_new_count)); + } fds[0].fd = platform->signal_fd; fds[0].events = POLLIN; diff --git a/tests/libs/estdlib/test_supervisor.erl b/tests/libs/estdlib/test_supervisor.erl index b18944113..cea4d9024 100644 --- a/tests/libs/estdlib/test_supervisor.erl +++ b/tests/libs/estdlib/test_supervisor.erl @@ -31,6 +31,8 @@ test() -> ok = test_start_child(), ok = test_start_child_ping_pong(), ok = test_supervisor_order(), + ok = test_terminate_delete_child(), + ok = test_terminate_timeout(), ok. test_basic_supervisor() -> @@ -85,6 +87,53 @@ test_start_child() -> exit(SupPid, shutdown), ok. +test_terminate_delete_child() -> + {ok, SupPid} = supervisor:start_link(?MODULE, {test_no_child, self()}), + {ok, Pid} = supervisor:start_child(SupPid, #{ + id => child_start, start => {?MODULE, child_start, [start]} + }), + {error, not_found} = supervisor:terminate_child(SupPid, Pid), + {error, running} = supervisor:delete_child(SupPid, child_start), + ok = supervisor:terminate_child(SupPid, child_start), + ok = supervisor:delete_child(SupPid, child_start), + {error, not_found} = supervisor:delete_child(SupPid, child_start), + unlink(SupPid), + exit(SupPid, shutdown), + ok. + +test_terminate_timeout() -> + {ok, SupPid} = supervisor:start_link(?MODULE, {test_no_child, self()}), + Self = self(), + {ok, Pid} = supervisor:start_child(SupPid, #{ + id => child_start, start => {?MODULE, child_start, [{trap_exit, Self}]}, shutdown => 500 + }), + ok = supervisor:terminate_child(SupPid, child_start), + ok = + receive + {Pid, {SupPid, shutdown}} -> ok + after 1000 -> timeout + end, + {ok, Pid2} = supervisor:restart_child(SupPid, child_start), + Pid2 ! ok, + ok = supervisor:terminate_child(SupPid, child_start), + ok = + receive + {Pid2, {SupPid, shutdown}} -> ok + after 1000 -> timeout + end, + ok = supervisor:delete_child(SupPid, child_start), + {ok, Pid3} = supervisor:start_child(SupPid, #{ + id => child_start, start => {?MODULE, child_start, [{trap_exit, Self}]}, shutdown => 500 + }), + unlink(SupPid), + exit(SupPid, shutdown), + ok = + receive + {Pid3, {SupPid, shutdown}} -> ok + after 1000 -> timeout + end, + ok. + child_start(ignore) -> ignore; child_start(start) -> @@ -104,7 +153,18 @@ child_start(info) -> child_start(error) -> {error, child_error}; child_start(fail) -> - fail. + fail; +child_start({trap_exit, Parent}) -> + Pid = spawn_link(fun() -> + process_flag(trap_exit, true), + receive + {'EXIT', From, Reason} -> Parent ! {self(), {From, Reason}} + end, + receive + ok -> ok + end + end), + {ok, Pid}. test_ping_pong(SupPid) -> Pid1 = get_and_test_server(),