Skip to content

Commit

Permalink
Remove deferred loading of NIF
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
fhunleth committed Jan 18, 2024
1 parent cf0285d commit 3dfabf3
Show file tree
Hide file tree
Showing 2 changed files with 9 additions and 46 deletions.
38 changes: 9 additions & 29 deletions lib/gpio/gpio_nif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
17 changes: 0 additions & 17 deletions test/circuits_gpio_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 3dfabf3

Please sign in to comment.