Skip to content

Instantly share code, notes, and snippets.

@zacky1972
Last active November 9, 2023 03:04
Show Gist options
  • Save zacky1972/08be6d1da99519da50df2e1642b3d364 to your computer and use it in GitHub Desktop.
Save zacky1972/08be6d1da99519da50df2e1642b3d364 to your computer and use it in GitHub Desktop.
HtPipe prevents the entire Elixir from terminating abnormaly by NIFs.

AbortNif with HtPipe

HtPipe prevents the entire Elixir from terminating abnormaly by NIFs.

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 --name main_node --cookie cookie -S mix

Scenario 1

AbortNif has push/1 and pop/0 functions:

iex(main_node@yourhost.local)1> AbortNif.pop()
:empty
iex(main_node@yourhost.local)2> AbortNif.push(1)
:ok
iex(main_node@yourhost.local)3> AbortNif.push(2)
:ok
iex(main_node@yourhost.local)4> AbortNif.pop()  
2
iex(main_node@yourhost.local)5> AbortNif.pop()  
1
iex(main_node@yourhost.local)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(main_node@yourhost.local)1> AbortNif.push(1)
:ok
iex(main_node@yourhost.local)2> AbortNif.push(2)
:ok
iex(main_node@yourhost.local)3> AbortNif.abort_soft()
:ok
iex(main_node@yourhost.local)4> AbortNif.pop()       
:empty

Scenario 3

AbortNif aborts by abort_hard/0 by NIF and restart soon by the supervisor of HtPipe, with some error message from Logger. The state will be empty by restarting

iex(main_node@yourhost.local)1> AbortNif.push(1)
:ok
iex(main_node@yourhost.local)2> AbortNif.push(2)
:ok
iex(main_node@yourhost.local)3> AbortNif.abort_hard()
:ok
iex(main_node@yourhost.local)4> 
05:28:26.650 [error] Process #PID<20680.102.0> on node :"[email protected]" raised an exception
** (UndefinedFunctionError) function HtPipe.worker/3 is undefined or private
    (ht_pipe 0.1.0-dev) HtPipe.worker(#PID<0.220.0>, 5000, #Function<0.51034852/0 in AbortNif.handle_cast/2>)
05:28:31.759 [error] GenServer AbortNif terminating
** (stop) bad return value: nil
Last message: {:"$gen_cast", :abort_hard}
State: [2, 1]
AbortNif.pop
:empty
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
HtPipe.htp(fn -> abort_nif() end, spawn: :os)
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},
{:ht_pipe, "~> 0.1.0-dev", github: "zeam-vm/ht_pipe", branch: "main"}
]
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