From f6908bedd77efc0e280d67460063873e419b6b34 Mon Sep 17 00:00:00 2001 From: Serge Aleynikov Date: Tue, 27 Feb 2024 01:02:22 -0500 Subject: [PATCH] Manage registry in NIF private data --- README.md | 42 ++++++++++------ c_src/Makefile | 23 +++++---- c_src/sema_nif.cpp | 115 +++++++++++++++++++++++++++----------------- src/sema_nif.erl | 14 ++---- test/sema_tests.erl | 2 +- 5 files changed, 116 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 3310b34..9c8023f 100644 --- a/README.md +++ b/README.md @@ -3,27 +3,37 @@ erlsem An OTP library implementing counting non-blocking semaphore with the ability to monitor a process that obtains a lock and to release the corresponding -resource(s) automatically in case the process exits. +resource(s) automatically when the process exits. Build ----- - $ rebar3 compile - $ rebar3 eunit +```shell +$ rebar3 compile +$ rebar3 eunit +``` Demo ---- - Eshell V13.1.3 (abort with ^G) - 1> Pid = self(). - <0.145.0> - 2> S = sema_nif:create(3). - #Ref<0.1541145699.2864316417.117910> - 3> L = [spawn(fun() -> Pid ! {self(), sema_nif:acquire(S)}, timer:sleep(10) end) || _ <- lists:seq(1, 5)]. - [<0.154.0>,<0.155.0>,<0.156.0>,<0.157.0>,<0.158.0>] - 4> [receive {P, X} -> X end || P <- L]. - [{ok,1}, - {ok,2}, - {ok,3}, - {error,full}, - {error,full}] +```erlang +Eshell V13.1.3 (abort with ^G) +1> Pid = self(). +<0.145.0> +2> S = sema_nif:create(3). +#Ref<0.1541145699.2864316417.117910> +3> L = [spawn(fun() -> Pid ! {self(), sema_nif:acquire(S)}, timer:sleep(10) end) || _ <- lists:seq(1, 5)]. +[<0.154.0>,<0.155.0>,<0.156.0>,<0.157.0>,<0.158.0>] +4> [receive {P, X} -> X end || P <- L]. +[{ok,1}, + {ok,2}, + {ok,3}, + {error,full}, + {error,full}] +5> sema_nif:create(sema, 5). +#Ref<0.1541145699.2864316445.123450> +6> sema_nif:acquire(sema). +{ok, 1} +6> sema_nif:release(sema). +{ok, 0} +``` diff --git a/c_src/Makefile b/c_src/Makefile index 0bc5a52..74fc619 100644 --- a/c_src/Makefile +++ b/c_src/Makefile @@ -12,25 +12,28 @@ ERL_INTERFACE_LIB_DIR ?= $(shell erl -noshell -eval "io:format(\"~ts\", [code:li C_SRC_DIR = $(CURDIR) C_SRC_OUTPUT ?= $(CURDIR)/../priv/$(PROJECT).so +ifeq ($(NIF_DEBUG),) + OPTIMIZE = -O3 -DNDEBUG +else + OPTIMIZE = -g -O0 +endif + # System type and C compiler/flags. UNAME_SYS := $(shell uname -s) -ifeq ($(UNAME_SYS), Darwin) +ifeq ($(UNAME_SYS),Darwin) CC ?= cc - CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes - CXXFLAGS ?= -O3 -finline-functions -Wall + CXXFLAGS ?= -finline-functions -Wall LDFLAGS ?= -flat_namespace -undefined suppress -else ifeq ($(UNAME_SYS), FreeBSD) +else ifeq ($(UNAME_SYS),FreeBSD) CC ?= cc - CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes - CXXFLAGS ?= -O3 -finline-functions -Wall -else ifeq ($(UNAME_SYS), Linux) + CXXFLAGS ?= -finline-functions -Wall +else ifeq ($(UNAME_SYS),Linux) CC ?= gcc - CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes - CXXFLAGS ?= -O3 -finline-functions -Wall + CXXFLAGS ?= -finline-functions -Wall endif -CXXFLAGS += -std=c++20 +CXXFLAGS += -std=c++20 $(OPTIMIZE) CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) CXXFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) diff --git a/c_src/sema_nif.cpp b/c_src/sema_nif.cpp index a338379..43a471c 100644 --- a/c_src/sema_nif.cpp +++ b/c_src/sema_nif.cpp @@ -23,6 +23,7 @@ static ERL_NIF_TERM atom_cnt; static ERL_NIF_TERM atom_max; static ERL_NIF_TERM atom_name; static ERL_NIF_TERM atom_undefined; +static ERL_NIF_TERM atom_duplicate_name; inline ERL_NIF_TERM mk_atom(ErlNifEnv *env, const char *name) { ERL_NIF_TERM ret; @@ -88,7 +89,7 @@ struct enif_allocator : std::allocator { struct sema; struct registry { - void add(ERL_NIF_TERM name, sema* s); + bool add(ERL_NIF_TERM name, sema* s); void remove(sema* s); sema* get(ERL_NIF_TERM name); private: @@ -96,13 +97,16 @@ struct registry { std::mutex m_mtx; }; -static std::unique_ptr s_registry; + +static registry* get_registry(ErlNifEnv* env) { + return static_cast(enif_priv_data(env)); +} struct sema { std::atomic_uint cnt; std::atomic_uint dead_counter; - unsigned max; - ERL_NIF_TERM name; + unsigned max; + ERL_NIF_TERM name; typedef std::pair mon_cnt_t; std::map< @@ -115,18 +119,12 @@ struct sema { static ErlNifMonitor null_mon; - sema(unsigned n, ERL_NIF_TERM name = 0) + sema(ErlNifEnv* env, unsigned n, ERL_NIF_TERM name = 0) : cnt(0) , dead_counter(0) , max(n) , name(name ? name : atom_undefined) - { - s_registry->add(name, this); - } - - ~sema() { - s_registry->remove(this); - } + {} ERL_NIF_TERM info(ErlNifEnv *env) { ERL_NIF_TERM keys[] = {atom_dead, atom_cnt, atom_max}; @@ -254,13 +252,19 @@ struct sema { ErlNifMonitor sema::null_mon; +class sema_already_registered : public std::exception {}; + //----------------------------------------------------------------------------- // registry implementation //----------------------------------------------------------------------------- -void registry::add(ERL_NIF_TERM name, sema* s) { - if (name == 0 || name == atom_undefined) return; +bool registry::add(ERL_NIF_TERM name, sema* s) { + if (name == 0 || name == atom_undefined) return false; std::unique_lock lock(m_mtx); + auto it = m_reg.find(name); + if (it != m_reg.end()) + return false; m_reg.emplace(std::make_pair(name, s)); + return true; } void registry::remove(sema* s) { if (!s) return; @@ -283,6 +287,10 @@ static void free_sema(ErlNifEnv *env, void *obj) { auto n = x.cnt.load(std::memory_order_acquire); std::cout << "free> cnt: " << n << ", max: " << x.max << "\r\n"; #endif + auto reg = get_registry(env); + assert(reg); + reg->remove(&x); + x.~sema(); } } @@ -307,59 +315,52 @@ static bool open_resource(ErlNifEnv *env) { //----------------------------------------------------------------------------- // NIF implementation //----------------------------------------------------------------------------- -static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) { - if (!open_resource(env)) return -1; - atom_ok = mk_atom(env, "ok"); - atom_error = mk_atom(env, "error"); - atom_full = mk_atom(env, "full"); - atom_not_found = mk_atom(env, "not_found"); - atom_dead = mk_atom(env, "dead"); - atom_cnt = mk_atom(env, "cnt"); - atom_max = mk_atom(env, "max"); - atom_name = mk_atom(env, "name"); - atom_undefined = mk_atom(env, "undefined"); - - s_registry.reset(new registry()); - - return 0; -} static ERL_NIF_TERM create(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { assert(argc >= 1 && argc <= 2); + int pos = argc == 1 ? 0 : 1; unsigned max; - if (!enif_get_uint(env, argv[0], &max)) + if (!enif_get_uint(env, argv[pos], &max)) return enif_make_badarg(env); ERL_NIF_TERM name = 0; if (argc > 1) { - if (!enif_is_map(env, argv[1])) + if (!enif_is_atom(env, argv[0])) return enif_make_badarg(env); - - if (!enif_get_map_value(env, argv[1], atom_name, &name)) - return enif_raise_exception(env, - enif_make_tuple2(env, atom_error, make_binary(env, "Missing option: name"))); + name = argv[0]; } - void *res = enif_alloc_resource(SEMA, sizeof(sema)); + sema *res = static_cast(enif_alloc_resource(SEMA, sizeof(sema))); if (res == nullptr) return enif_make_badarg(env); - ERL_NIF_TERM ret = enif_make_resource(env, res); - enif_release_resource(res); + ERL_NIF_TERM ret; + + auto reg = get_registry(env); + assert(reg); + if (name && name != atom_undefined && !reg->add(name, res)) + ret = enif_raise_exception(env, atom_duplicate_name); + else { + new (res) sema(env, max, name); + ret = enif_make_resource(env, res); + } - new (res) sema(max, name); + enif_release_resource(res); return ret; } static sema* get_sema(ErlNifEnv *env, ERL_NIF_TERM id) { - if (enif_is_atom(env, id)) - return s_registry->get(id); - + if (enif_is_atom(env, id)) { + auto reg = get_registry(env); + assert(reg); + return reg->get(id); + } + sema *res = nullptr; return enif_get_resource(env, id, SEMA, (void **)&res) ? res : nullptr; } @@ -455,6 +456,32 @@ release(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { return res->release(env, pid, n); } +//----------------------------------------------------------------------------- +// NIF loading/unloading +//----------------------------------------------------------------------------- +static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) { + if (!open_resource(env)) return -1; + atom_ok = mk_atom(env, "ok"); + atom_error = mk_atom(env, "error"); + atom_full = mk_atom(env, "full"); + atom_not_found = mk_atom(env, "not_found"); + atom_dead = mk_atom(env, "dead"); + atom_cnt = mk_atom(env, "cnt"); + atom_max = mk_atom(env, "max"); + atom_name = mk_atom(env, "name"); + atom_undefined = mk_atom(env, "undefined"); + atom_duplicate_name = mk_atom(env, "duplicate_name"); + + *priv_data = static_cast(new registry()); + + return 0; +} + +static void unload(ErlNifEnv *env, void *priv_data) { + if (priv_data) + delete static_cast(priv_data); +} + static ErlNifFunc nif_funcs[] = { {"create", 1, create}, {"create", 2, create}, @@ -468,4 +495,4 @@ static ErlNifFunc nif_funcs[] = { {"release", 3, release} }; -ERL_NIF_INIT(sema_nif, nif_funcs, &load, nullptr, nullptr, nullptr); +ERL_NIF_INIT(sema_nif, nif_funcs, &load, nullptr, nullptr, unload); diff --git a/src/sema_nif.erl b/src/sema_nif.erl index 27a70f6..e58c9f2 100644 --- a/src/sema_nif.erl +++ b/src/sema_nif.erl @@ -46,21 +46,17 @@ -type sema_ref() :: reference(). -type sema_id() :: sema_ref() | atom(). --type sema_opts() :: #{name => atom()}. +-type sema_name() :: atom(). --export_type([sema_ref/0, sema_id/0, sema_opts/0, acquire_ret/0, release_ret/0]). +-export_type([sema_ref/0, sema_id/0, acquire_ret/0, release_ret/0]). % @doc Create a new semaphore with the given capacity -spec create(Max :: pos_integer()) -> sema_ref(). create(_) -> not_loaded(?LINE). -% @doc Create a new semaphore with the given capacity -% `Opts' is a map of options: -%
    -%
  • `{name, Name::atom()}' semaphore name -%
--spec create(pos_integer(), sema_opts()) -> sema_ref(). -create(Max, Opts) when is_integer(Max), is_map(Opts) -> not_loaded(?LINE). +% @doc Create a named semaphore with the given capacity +-spec create(sema_name(), pos_integer()) -> sema_ref(). +create(Name, Max) when is_atom(Name), is_integer(Max) -> not_loaded(?LINE). % @doc Get internal properties of the semaphore resource -spec info(Semaphore :: sema_id()) -> diff --git a/test/sema_tests.erl b/test/sema_tests.erl index a92119c..49d7f92 100644 --- a/test/sema_tests.erl +++ b/test/sema_tests.erl @@ -72,7 +72,7 @@ basic_api_test() -> ok. sema_name_test() -> - S = sema_nif:create(3, #{name => sema_test}), + S = sema_nif:create(sema_test, 3), ?assert(is_reference(S)), ?assertEqual(3, sema_nif:capacity(sema_test)), ?assertEqual(3, sema_nif:capacity(S)),