From 3dfabf3f543009e57684a10f39cd00268ada2fb5 Mon Sep 17 00:00:00 2001 From: Frank Hunleth Date: Wed, 17 Jan 2024 18:15:01 -0500 Subject: [PATCH] Remove deferred loading of NIF It was possible to crash the BEAM by loading a completely trimmed down shared library with multiple processes. This converts the NIF loader back to the traditional `@on_load` way that's serialized by the code loader. The benefits of delaying the load time are less now that the shared library has been tested quite a bit. --- lib/gpio/gpio_nif.ex | 38 +++++++++---------------------------- test/circuits_gpio_test.exs | 17 ----------------- 2 files changed, 9 insertions(+), 46 deletions(-) diff --git a/lib/gpio/gpio_nif.ex b/lib/gpio/gpio_nif.ex index 127199b..2d99372 100644 --- a/lib/gpio/gpio_nif.ex +++ b/lib/gpio/gpio_nif.ex @@ -5,26 +5,15 @@ defmodule Circuits.GPIO.Nif do @moduledoc false - defp load_nif_and_apply(fun, args) do - nif_binary = Application.app_dir(:circuits_gpio, "priv/gpio_nif") + @on_load {:load_nif, 0} + @compile {:autoload, false} - # Optimistically load the NIF. Handle the possible race. - case :erlang.load_nif(to_charlist(nif_binary), 0) do - :ok -> apply(__MODULE__, fun, args) - {:error, {:reload, _}} -> apply(__MODULE__, fun, args) - error -> error - end + def load_nif() do + :erlang.load_nif(:code.priv_dir(:circuits_gpio) ++ ~c"/gpio_nif", 0) end - def open(gpio_spec, resolved_gpio_spec, direction, initial_value, pull_mode) do - load_nif_and_apply(:open, [ - gpio_spec, - resolved_gpio_spec, - direction, - initial_value, - pull_mode - ]) - end + def open(_gpio_spec, _resolved_gpio_spec, _direction, _initial_value, _pull_mode), + do: :erlang.nif_error(:nif_not_loaded) def close(_gpio), do: :erlang.nif_error(:nif_not_loaded) def read(_gpio), do: :erlang.nif_error(:nif_not_loaded) @@ -36,16 +25,7 @@ defmodule Circuits.GPIO.Nif do def set_direction(_gpio, _direction), do: :erlang.nif_error(:nif_not_loaded) def set_pull_mode(_gpio, _pull_mode), do: :erlang.nif_error(:nif_not_loaded) def info(_gpio), do: :erlang.nif_error(:nif_not_loaded) - - def status(resolved_gpio_spec) do - load_nif_and_apply(:status, [resolved_gpio_spec]) - end - - def backend_info() do - load_nif_and_apply(:backend_info, []) - end - - def enumerate() do - load_nif_and_apply(:enumerate, []) - end + def status(_resolved_gpio_spec), do: :erlang.nif_error(:nif_not_loaded) + def backend_info(), do: :erlang.nif_error(:nif_not_loaded) + def enumerate(), do: :erlang.nif_error(:nif_not_loaded) end diff --git a/test/circuits_gpio_test.exs b/test/circuits_gpio_test.exs index aeef64f..118eb77 100644 --- a/test/circuits_gpio_test.exs +++ b/test/circuits_gpio_test.exs @@ -482,21 +482,4 @@ defmodule Circuits.GPIO2Test do assert GPIO.write_one({@gpiochip, 1}, 1) == :ok assert GPIO.read_one({@gpiochip, 0}) == 1 end - - test "racing to load the NIF" do - # Make sure the NIF isn't loaded - assert true == :code.delete(Circuits.GPIO.Nif) - assert false == :code.purge(Circuits.GPIO.Nif) - - # Try to hit the race by having 32 processes race to load the NIF - tasks = - for index <- 0..31 do - Task.async(fn -> - {:ok, gpio} = GPIO.open({@gpiochip, index}, :input) - GPIO.close(gpio) - end) - end - - Enum.each(tasks, &Task.await/1) - end end