Skip to content

Commit e4971a1

Browse files
committed
Fix race to load NIF
This happens when multiple processes call GPIO.open at the same time. NIFs can only be loaded once only one process gets an `:ok` return. The others get an error telling them not to do that. That's harmless so just try calling the function again. The included unit test starts 32 processes in a race to open the GPIO. On my MBP, a whole bunch of the processes would get the reload error before the fix. Fixes #178
1 parent 2f07893 commit e4971a1

File tree

2 files changed

+26
-7
lines changed

2 files changed

+26
-7
lines changed

lib/gpio/gpio_nif.ex

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55
defmodule Circuits.GPIO.Nif do
66
@moduledoc false
77

8-
defp load_nif() do
8+
defp load_nif_and_apply(fun, args) do
99
nif_binary = Application.app_dir(:circuits_gpio, "priv/gpio_nif")
1010

11-
:erlang.load_nif(to_charlist(nif_binary), 0)
11+
# Optimistically load the NIF. Handle the possible race.
12+
case :erlang.load_nif(to_charlist(nif_binary), 0) do
13+
:ok -> apply(__MODULE__, fun, args)
14+
{:error, {:reload, _}} -> apply(__MODULE__, fun, args)
15+
error -> error
16+
end
1217
end
1318

1419
def open(pin_number, pin_direction, initial_value, pull_mode) do
15-
with :ok <- load_nif() do
16-
apply(__MODULE__, :open, [pin_number, pin_direction, initial_value, pull_mode])
17-
end
20+
load_nif_and_apply(:open, [pin_number, pin_direction, initial_value, pull_mode])
1821
end
1922

2023
def close(_gpio) do
@@ -46,7 +49,6 @@ defmodule Circuits.GPIO.Nif do
4649
end
4750

4851
def info() do
49-
:ok = load_nif()
50-
apply(__MODULE__, :info, [])
52+
load_nif_and_apply(:info, [])
5153
end
5254
end

test/circuits_gpio_test.exs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,21 @@ defmodule Circuits.GPIOTest do
272272
assert false == :code.purge(Circuits.GPIO)
273273
end
274274
end
275+
276+
test "racing to load the NIF" do
277+
# Make sure the NIF isn't loaded
278+
assert true == :code.delete(Circuits.GPIO.Nif)
279+
assert false == :code.purge(Circuits.GPIO.Nif)
280+
281+
# Try to hit the race by having 32 processes race to load the NIF
282+
tasks =
283+
for index <- 0..31 do
284+
Task.async(fn ->
285+
{:ok, gpio} = GPIO.open(index, :input)
286+
GPIO.close(gpio)
287+
end)
288+
end
289+
290+
Enum.each(tasks, &Task.await/1)
291+
end
275292
end

0 commit comments

Comments
 (0)