Skip to content

Commit

Permalink
Manage registry in NIF private data
Browse files Browse the repository at this point in the history
  • Loading branch information
saleyn committed Feb 27, 2024
1 parent 10b90d3 commit f6908be
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 80 deletions.
42 changes: 26 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
```
23 changes: 13 additions & 10 deletions c_src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
115 changes: 71 additions & 44 deletions c_src/sema_nif.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -88,21 +89,24 @@ struct enif_allocator : std::allocator<T> {
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:
std::map<ERL_NIF_TERM, sema*> m_reg;
std::mutex m_mtx;
};

static std::unique_ptr<registry> s_registry;

static registry* get_registry(ErlNifEnv* env) {
return static_cast<registry*>(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<ErlNifMonitor, unsigned> mon_cnt_t;
std::map<
Expand All @@ -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};
Expand Down Expand Up @@ -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;
Expand All @@ -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();
}
}
Expand All @@ -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<sema*>(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;
}
Expand Down Expand Up @@ -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<void*>(new registry());

return 0;
}

static void unload(ErlNifEnv *env, void *priv_data) {
if (priv_data)
delete static_cast<registry*>(priv_data);
}

static ErlNifFunc nif_funcs[] = {
{"create", 1, create},
{"create", 2, create},
Expand All @@ -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);
14 changes: 5 additions & 9 deletions src/sema_nif.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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:
% <ul>
% <li>`{name, Name::atom()}' semaphore name
% </ul>
-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()) ->
Expand Down
2 changes: 1 addition & 1 deletion test/sema_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down

0 comments on commit f6908be

Please sign in to comment.