Skip to content

Commit edadd7d

Browse files
Allow definition of a load and unload NIF callback (#16)
Co-authored-by: Jonatan Kłosko <[email protected]>
1 parent dd85a34 commit edadd7d

File tree

6 files changed

+123
-15
lines changed

6 files changed

+123
-15
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,40 @@ Attempting to decode STL containers making use of `std::pmr::polymorphic_allocat
604604
will result in the `std::pmr::get_default_resource()` memory resource being
605605
used.
606606
607+
## Global Callbacks
608+
609+
The Erlang NIF API allows the creation of global callbacks. This section
610+
describes how to leverage these callbacks while using Fine.
611+
612+
### Load
613+
614+
The NIF load callback is called by the ERTS when the NIFs are being loaded by
615+
`:erlang.load_nif/2`. Fine allows customizing the behavior of the load callback
616+
using the `fine::Registration::register_load` function:
617+
618+
```c++
619+
static std::unique_ptr<ThreadPool> s_pool;
620+
621+
static auto load_registration = fine::Registration::register_load([](ErlNifEnv *env, void **priv_data, fine::Term load_info) {
622+
const auto thread_count = fine::decode<std::uint64_t>(caller_env, load_info);
623+
s_pool = std::make_unique<FixedThreadPool>(thread_count);
624+
});
625+
```
626+
627+
### Unload
628+
629+
The NIF unload callback is called by the ERTS when the NIFs are being unloaded
630+
from the runtime. Fine allows customizing the behavior of the unload callback
631+
using the `fine::Registration::register_unload` function:
632+
633+
```c++
634+
static std::unique_ptr<ThreadPool> s_pool;
635+
636+
static auto unload_registration = fine::Registration::register_unload([](ErlNifEnv *env, void *priv_data) noexcept {
637+
s_pool.stop();
638+
});
639+
```
640+
607641
<!-- Docs -->
608642
609643
## Prior work

c_include/fine.hpp

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <cstdint>
66
#include <cstdlib>
77
#include <cstring>
8+
#include <functional>
89
#include <map>
910
#include <memory>
1011
#include <optional>
@@ -47,7 +48,10 @@ template <typename T, typename SFINAE = void> struct Encoder;
4748

4849
namespace __private__ {
4950
std::vector<ErlNifFunc> &get_erl_nif_funcs();
50-
int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info);
51+
void init_atoms(ErlNifEnv *env);
52+
bool init_resources(ErlNifEnv *env);
53+
int load(ErlNifEnv *, void **, ERL_NIF_TERM) noexcept;
54+
void unload(ErlNifEnv *, void *) noexcept;
5155
} // namespace __private__
5256

5357
// Definitions
@@ -97,8 +101,7 @@ class Atom {
97101
friend struct Decoder<Atom>;
98102
friend struct ::std::hash<Atom>;
99103

100-
friend int __private__::load(ErlNifEnv *env, void **priv_data,
101-
ERL_NIF_TERM load_info);
104+
friend void __private__::init_atoms(ErlNifEnv *env);
102105

103106
// We accumulate all globally defined atom objects and create the
104107
// terms upfront as part of init (called from the NIF load callback).
@@ -1187,6 +1190,12 @@ template <typename T> void raise(ErlNifEnv *env, const T &value) {
11871190
// Mechanism for accumulating information via static object definitions.
11881191
class Registration {
11891192
public:
1193+
// A function compatible with the load callback of Erlang's NIFs.
1194+
using LoadCallback = std::function<void(ErlNifEnv *, void **, fine::Term)>;
1195+
1196+
// A function compatible with the unload callback of Erlang's NIFs.
1197+
using UnloadCallback = std::function<void(ErlNifEnv *, void *)>;
1198+
11901199
template <typename T>
11911200
static Registration register_resource(const char *name) {
11921201
Registration::resources.push_back({&fine::ResourcePtr<T>::resource_type,
@@ -1200,6 +1209,26 @@ class Registration {
12001209
return {};
12011210
}
12021211

1212+
// Registers a load callback.
1213+
static Registration register_load(LoadCallback callback) {
1214+
if (erl_nif_load_callback) {
1215+
throw std::logic_error("load callback already registered");
1216+
}
1217+
1218+
Registration::erl_nif_load_callback = callback;
1219+
return {};
1220+
}
1221+
1222+
// Registers an unload callback.
1223+
static Registration register_unload(UnloadCallback callback) {
1224+
if (erl_nif_unload_callback) {
1225+
throw std::logic_error("unload callback already registered");
1226+
}
1227+
1228+
Registration::erl_nif_unload_callback = callback;
1229+
return {};
1230+
}
1231+
12031232
private:
12041233
static bool init_resources(ErlNifEnv *env) {
12051234
for (const auto &[resource_type_ptr, name, dtor] :
@@ -1220,15 +1249,18 @@ class Registration {
12201249
}
12211250

12221251
friend std::vector<ErlNifFunc> &__private__::get_erl_nif_funcs();
1252+
friend int __private__::load(ErlNifEnv *, void **, ERL_NIF_TERM) noexcept;
1253+
friend void __private__::unload(ErlNifEnv *, void *) noexcept;
12231254

1224-
friend int __private__::load(ErlNifEnv *env, void **priv_data,
1225-
ERL_NIF_TERM load_info);
1255+
friend bool __private__::init_resources(ErlNifEnv *env);
12261256

12271257
inline static std::vector<std::tuple<ErlNifResourceType **, const char *,
12281258
void (*)(ErlNifEnv *, void *)>>
12291259
resources = {};
12301260

12311261
inline static std::vector<ErlNifFunc> erl_nif_funcs = {};
1262+
inline static LoadCallback erl_nif_load_callback = {};
1263+
inline static UnloadCallback erl_nif_unload_callback = {};
12321264
};
12331265

12341266
// NIF definitions
@@ -1295,23 +1327,49 @@ constexpr unsigned int nif_arity(Ret (*)(Args...)) {
12951327
}
12961328

12971329
namespace __private__ {
1330+
void init_atoms(ErlNifEnv *env) { fine::Atom::init_atoms(env); }
1331+
1332+
bool init_resources(ErlNifEnv *env) {
1333+
return fine::Registration::init_resources(env);
1334+
}
1335+
12981336
inline std::vector<ErlNifFunc> &get_erl_nif_funcs() {
12991337
return Registration::erl_nif_funcs;
13001338
}
13011339

1302-
inline int load(ErlNifEnv *env, void **, ERL_NIF_TERM) {
1303-
Atom::init_atoms(env);
1340+
inline int load(ErlNifEnv *caller_env, void **priv_data,
1341+
ERL_NIF_TERM load_info) noexcept {
1342+
init_atoms(caller_env);
13041343

1305-
if (!Registration::init_resources(env)) {
1344+
if (!init_resources(caller_env)) {
1345+
return -1;
1346+
}
1347+
1348+
try {
1349+
if (fine::Registration::erl_nif_load_callback) {
1350+
std::invoke(fine::Registration::erl_nif_load_callback, caller_env,
1351+
priv_data, load_info);
1352+
}
1353+
} catch (const std::exception &e) {
1354+
enif_fprintf(stderr, "unhandled exception: %s\n", e.what());
1355+
return -1;
1356+
} catch (...) {
1357+
enif_fprintf(stderr, "unhandled throw\n");
13061358
return -1;
13071359
}
13081360

13091361
return 0;
13101362
}
1363+
1364+
inline void unload(ErlNifEnv *caller_env, void *priv_data) noexcept {
1365+
if (fine::Registration::erl_nif_unload_callback) {
1366+
std::invoke(fine::Registration::erl_nif_unload_callback, caller_env,
1367+
priv_data);
1368+
}
1369+
}
13111370
} // namespace __private__
13121371

13131372
// Macros
1314-
13151373
#define FINE_NIF(name, flags) \
13161374
static ERL_NIF_TERM name##_nif(ErlNifEnv *env, int argc, \
13171375
const ERL_NIF_TERM argv[]) { \
@@ -1345,16 +1403,16 @@ inline int load(ErlNifEnv *env, void **, ERL_NIF_TERM) {
13451403
auto &nif_funcs = fine::__private__::get_erl_nif_funcs(); \
13461404
auto num_funcs = static_cast<int>(nif_funcs.size()); \
13471405
auto funcs = nif_funcs.data(); \
1348-
auto load = fine::__private__::load; \
1406+
\
13491407
static ErlNifEntry entry = {ERL_NIF_MAJOR_VERSION, \
13501408
ERL_NIF_MINOR_VERSION, \
13511409
name, \
13521410
num_funcs, \
13531411
funcs, \
1354-
load, \
1355-
NULL, \
1412+
fine::__private__::load, \
13561413
NULL, \
13571414
NULL, \
1415+
fine::__private__::unload, \
13581416
ERL_NIF_VM_VARIANT, \
13591417
1, \
13601418
sizeof(ErlNifResourceTypeInit), \

example/c_src/example.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
#include <fine.hpp>
22

3-
int64_t add(ErlNifEnv *env, int64_t x, int64_t y) {
4-
return x + y;
5-
}
3+
int64_t add(ErlNifEnv *env, int64_t x, int64_t y) { return x + y; }
64

75
FINE_NIF(add, 0);
86
FINE_INIT("Elixir.Example");

test/c_src/finest.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,16 @@ std::uint64_t hash_atom(ErlNifEnv *, fine::Atom atom) noexcept {
462462
}
463463
FINE_NIF(hash_atom, 0);
464464

465+
static bool s_loaded = false;
466+
467+
static auto load_registration = fine::Registration::register_load(
468+
[](ErlNifEnv *, void **, ERL_NIF_TERM) { s_loaded = true; });
469+
470+
static auto unload_registration = fine::Registration::register_unload(
471+
[](ErlNifEnv *, void *) noexcept { s_loaded = false; });
472+
473+
bool is_loaded(ErlNifEnv *) { return s_loaded; }
474+
FINE_NIF(is_loaded, 0);
465475
} // namespace finest
466476

467477
FINE_INIT("Elixir.Finest.NIF");

test/lib/finest/nif.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,7 @@ defmodule Finest.NIF do
8080
def hash_term(_term), do: err!()
8181
def hash_atom(_term), do: err!()
8282

83+
def is_loaded(), do: err!()
84+
8385
defp err!(), do: :erlang.nif_error(:not_loaded)
8486
end

test/test/finest_test.exs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,4 +502,10 @@ defmodule FinestTest do
502502
end
503503
end
504504
end
505+
506+
describe "callbacks" do
507+
test "load" do
508+
assert NIF.is_loaded()
509+
end
510+
end
505511
end

0 commit comments

Comments
 (0)