Skip to content

Instantly share code, notes, and snippets.

@zacky1972
Last active August 30, 2022 20:19
A fact given by experiments: When a NIF aborted, it will terminate the entire Elixir abnormally, even if a supervisor monitors it.

AbortNif

A fact given by experiments: When a NIF aborted, it will terminate the entire Elixir abnormally, even if a supervisor monitors it.

Installation and Usage

  1. mix new abort_nif --sup
  2. cd abort_nif
  3. edit the following files.
  • mix.exs
  • lib/abort_nif/application.ex
  • lib/abort_nif.ex
  • Makefile
  • libnif.c
  1. mix deps.get
  2. iex -S mix

Scenario 1

AbortNif has push/1 and pop/0 functions:

iex(1)> AbortNif.pop()
:empty
iex(2)> AbortNif.push(1)
:ok
iex(3)> AbortNif.push(2)
:ok
iex(4)> AbortNif.pop()  
2
iex(5)> AbortNif.pop()  
1
iex(6)> AbortNif.pop()  
:empty

Scenario 2

AbortNif aborts by abort_soft/0 and restart soon by the supervisor. The state will be empty by restarting:

iex(1)> AbortNif.push(1)
:ok
iex(2)> AbortNif.push(2)
:ok
iex(3)> AbortNif.abort_soft()
:ok
iex(4)> AbortNif.pop()       
:empty

Scenario 3

AbortNif aborts by abort_hard/0 by NIF, but it will terminate the entire Elixir abnormally, even if the supervisor monitors it.

iex(1)> AbortNif.push(1)   
:ok
iex(2)> AbortNif.push(2)
:ok
iex(3)> AbortNif.abort_hard()
zsh: abort      iex -S mix
                          %
% 
defmodule AbortNif do
require Logger
use GenServer
@moduledoc """
Documentation for `AbortNif`.
"""
@on_load :load_nif
@doc false
def load_nif() do
nif_file = ~c'#{Application.app_dir(:abort_nif, "priv/libnif")}'
case :erlang.load_nif(nif_file, 0) do
:ok -> :ok
{:error, {:reload, _}} -> :ok
{:error, reason} -> Logger.error("Failed to load NIF: #{inspect(reason)}")
end
end
def abort_nif(), do: exit(:nif_not_loaded)
@impl true
def init(stack) do
{:ok, stack}
end
@impl true
def handle_call(:pop, _from, [head | tail]) do
{:reply, head, tail}
end
@impl true
def handle_call(:pop, _from, []) do
{:reply, :empty, []}
end
@impl true
def handle_cast({:push, element}, state) do
{:noreply, [element | state]}
end
@impl true
def handle_cast(:abort_soft, _state) do
exit({:shutdown, :abort_soft})
end
@impl true
def handle_cast(:abort_hard, _state) do
abort_nif()
end
@spec start_link(maybe_improper_list) :: :ignore | {:error, any} | {:ok, pid}
def start_link(default) when is_list(default) do
GenServer.start_link(__MODULE__, default, name: __MODULE__)
end
def pop() do
GenServer.call(__MODULE__, :pop)
end
def push(element) do
GenServer.cast(__MODULE__, {:push, element})
end
def abort_soft() do
GenServer.cast(__MODULE__, :abort_soft)
end
def abort_hard() do
GenServer.cast(__MODULE__, :abort_hard)
end
@doc """
Hello world.
## Examples
iex> AbortNif.hello()
:world
"""
def hello do
:world
end
end
defmodule AbortNif.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = [
# Starts a worker by calling: AbortNif.Worker.start_link(arg)
# {AbortNif.Worker, arg}
AbortNif
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: AbortNif.Supervisor]
Supervisor.start_link(children, opts)
end
end
#include <stdlib.h>
#include <erl_nif.h>
static ERL_NIF_TERM abort_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
abort();
}
static ErlNifFunc nif_funcs [] =
{
{"abort_nif", 0, abort_nif}
};
ERL_NIF_INIT(Elixir.AbortNif, nif_funcs, NULL, NULL, NULL, NULL)
.phony: all clean
PRIV = $(MIX_APP_PATH)/priv
BUILD = $(MIX_APP_PATH)/obj
NIF = $(PRIV)/libnif.so
ifeq ($(CROSSCOMPILE),)
ifeq ($(shell uname -s),Linux)
LDFLAGS += -fPIC -shared
CFLAFGS += -fPIC
else
LDFLAGS += -undefined dynamic_lookup -dynamiclib
endif
else
LDFLAGS += -fPIC -shared
CFLAGS += -fPIC
endif
ifeq ($(ERL_EI_INCLUDE_DIR),)
ERLANG_PATH = $(shell elixir --eval ':code.root_dir |> to_string() |> IO.puts()')
ifeq ($(ERLANG_PATH),)
$(error Could not find the Elixir installation. Check to see that 'elixir')
endif
ERL_EI_INCLUDE_DIR = $(ERLANG_PATH)/usr/include
ERL_EI_LIBDIR = $(ERLANG_PATH)/usr/lib
endif
ERL_CFLAGS ?= -I$(ERL_EI_INCLUDE_DIR)
ERL_LDFLAGS ?= -L$(ERL_EI_LIBDIR)
C_SRC = c_src/libnif.c
C_OBJ = $(C_SRC:c_src/%.c=$(BUILD)/%.o)
all: $(PRIV) $(BUILD) $(NIF)
$(PRIV) $(BUILD):
mkdir -p $@
$(BUILD)/%.o: c_src/%.c
@echo " CC $(notdir $@)"
$(CC) -c $(ERL_CFLAGS) $(CFLAGS) -o $@ $<
$(NIF): $(C_OBJ)
@echo " LD $(notdir $@)"
$(CC) -o $@ $(ERL_LDFLAGS) $(LDFLAGS) $^
clean:
$(RM) $(NIF) $(C_OBJ)
defmodule AbortNif.MixProject do
use Mix.Project
def project do
[
app: :abort_nif,
version: "0.1.0",
elixir: "~> 1.13",
start_permanent: Mix.env() == :prod,
deps: deps(),
compilers: [:elixir_make] ++ Mix.compilers,
deps: package()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {AbortNif.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
{:elixir_make, "~> 0.6", runtime: false}
]
end
defp package do
[
files: [
"lib",
"LICENSE",
"mix.exs",
"README.md",
"c_src/*.[ch]",
"Makefile"
]
]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment