Skip to content

Commit 3b3ae06

Browse files
authored
feat: wrap cgo Handles with Erlang Resources (#17)
* Add Option_type resource type * Use resource in functions * Add resources for each type * Use resources on stream handler * Fix destructor not being called NIFs keep a reference to the allocated resource by default on allocation. We need to release that reference before returning, otherwise it will never be GC'd. * Use reference instead of integer for typespec * Unify function naming in Go * Explicitly ignore autogenerated header only * Add "dirty NIF" flags to IO calls
1 parent 5d26272 commit 3b3ae06

File tree

6 files changed

+147
-89
lines changed

6 files changed

+147
-89
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ lambda_ethereum_consensus-*.tar
3636
# Compiled artifacts.
3737
*.o
3838
*.a
39-
*.h
4039
*.so
40+
native/libp2p_nif/main.h
4141

4242
# VSCode configuration dir.
4343
.vscode/

lib/lambda_ethereum_consensus/libp2p.ex

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,37 @@ defmodule Libp2p do
1313
@typedoc """
1414
A handle to a Go resource.
1515
"""
16-
@type handle :: integer
16+
@opaque handle :: reference
1717

1818
@typedoc """
1919
A handle to a host.Host.
2020
"""
21-
@type host :: handle
21+
@opaque host :: handle
2222

2323
@typedoc """
2424
A handle to a peerstore.Peerstore.
2525
"""
26-
@type peerstore :: handle
26+
@opaque peerstore :: handle
2727

2828
@typedoc """
2929
A handle to a peer.ID.
3030
"""
31-
@type peer_id :: handle
31+
@opaque peer_id :: handle
3232

3333
@typedoc """
3434
A handle to a []multiaddr.MultiAddr.
3535
"""
36-
@type addrs :: handle
36+
@opaque addrs :: handle
3737

3838
@typedoc """
3939
A handle to a stream.
4040
"""
41-
@type stream :: handle
41+
@opaque stream :: handle
4242

4343
@typedoc """
4444
A handle to an Option.
4545
"""
46-
@type option :: handle
46+
@opaque option :: handle
4747

4848
@typedoc """
4949
An error returned by this module.

native/libp2p_nif/libp2p.c

Lines changed: 113 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
#include "main.h"
22
#include "utils.h"
3+
#include <stdbool.h>
34
#include <erl_nif.h>
45

56
#define ERL_FUNCTION(FUNCTION_NAME) static ERL_NIF_TERM FUNCTION_NAME(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
67

7-
#define ERL_FUNCTION_GETTER(NAME, GETTER) \
8-
ERL_FUNCTION(NAME) \
9-
{ \
10-
uintptr_t _handle = get_handle_from_term(env, argv[0]); \
11-
IF_ERROR(_handle == 0, "invalid first argument"); \
12-
uintptr_t _res = GETTER(_handle); \
13-
return get_handle_result(env, _res); \
8+
#define ERL_FUNCTION_GETTER(NAME, RECV_TYPE, ATTR_TYPE, GETTER) \
9+
ERL_FUNCTION(NAME) \
10+
{ \
11+
uintptr_t _handle = get_handle_from_term(env, RECV_TYPE, argv[0]); \
12+
IF_ERROR(_handle == 0, "invalid first argument"); \
13+
uintptr_t _res = GETTER(_handle); \
14+
return get_handle_result(env, ATTR_TYPE, _res); \
1415
}
1516

1617
#define IF_ERROR(COND, MSG) \
@@ -19,29 +20,72 @@
1920
return make_error_msg(env, (MSG)); \
2021
}
2122

22-
#define GET_HANDLE(TERM, NAME) \
23-
({ \
24-
uintptr_t _handle = get_handle_from_term(env, (TERM)); \
25-
IF_ERROR(_handle == 0, "invalid " NAME); \
26-
_handle; \
23+
#define GET_HANDLE(TERM, TYPE) \
24+
({ \
25+
uintptr_t _handle = get_handle_from_term(env, (TYPE), (TERM)); \
26+
IF_ERROR(_handle == 0, "invalid " #TYPE); \
27+
_handle; \
2728
})
2829

29-
#define NIF_ENTRY(FUNCTION_NAME, ARITY) \
30-
{ \
31-
#FUNCTION_NAME, ARITY, FUNCTION_NAME \
30+
#define NIF_ENTRY(FUNCTION_NAME, ARITY, ...) \
31+
{ \
32+
#FUNCTION_NAME, ARITY, FUNCTION_NAME, __VA_ARGS__ \
3233
}
3334

34-
const uint64_t PID_LENGTH = 1024;
3535
const uint64_t BUFFER_SIZE = 4096;
3636

37+
/*************/
38+
/* NIF Setup */
39+
/*************/
40+
41+
ErlNifResourceType *Option;
42+
ErlNifResourceType *Host;
43+
ErlNifResourceType *Peerstore;
44+
ErlNifResourceType *peer_ID;
45+
ErlNifResourceType *Multiaddr_arr;
46+
ErlNifResourceType *Stream;
47+
48+
// Resource type helpers
49+
void handle_cleanup(ErlNifEnv *env, void *obj)
50+
{
51+
uintptr_t *handle = obj;
52+
DeleteHandle(*handle);
53+
}
54+
55+
#define OPEN_RESOURCE_TYPE(NAME) ((NAME) = enif_open_resource_type(env, NULL, (#NAME), handle_cleanup, flags, NULL))
56+
57+
static int open_resource_types(ErlNifEnv *env, ErlNifResourceFlags flags)
58+
{
59+
int failed = false;
60+
failed |= NULL == OPEN_RESOURCE_TYPE(Option);
61+
failed |= NULL == OPEN_RESOURCE_TYPE(Host);
62+
failed |= NULL == OPEN_RESOURCE_TYPE(Peerstore);
63+
failed |= NULL == OPEN_RESOURCE_TYPE(peer_ID);
64+
failed |= NULL == OPEN_RESOURCE_TYPE(Multiaddr_arr);
65+
failed |= NULL == OPEN_RESOURCE_TYPE(Stream);
66+
return failed;
67+
}
68+
69+
static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)
70+
{
71+
return open_resource_types(env, ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);
72+
}
73+
74+
static int upgrade(ErlNifEnv *env, void **priv_data, void **old_priv_data,
75+
ERL_NIF_TERM load_info)
76+
{
77+
return open_resource_types(env, ERL_NIF_RT_TAKEOVER);
78+
}
79+
3780
/***********/
3881
/* Helpers */
3982
/***********/
4083

41-
static uintptr_t get_handle_from_term(ErlNifEnv *env, ERL_NIF_TERM term)
84+
static uintptr_t get_handle_from_term(ErlNifEnv *env, ErlNifResourceType *type, ERL_NIF_TERM term)
4285
{
43-
uintptr_t handle;
44-
return enif_get_uint64(env, term, &handle) ? handle : 0;
86+
uintptr_t *obj;
87+
int result = enif_get_resource(env, term, type, (void **)&obj);
88+
return (!result || obj == NULL) ? 0 : *obj;
4589
}
4690

4791
static ERL_NIF_TERM _make_error_msg(ErlNifEnv *env, uint len, const char *msg)
@@ -62,10 +106,32 @@ static ERL_NIF_TERM make_ok_tuple2(ErlNifEnv *env, ERL_NIF_TERM term)
62106
return enif_make_tuple2(env, enif_make_atom(env, "ok"), term);
63107
}
64108

65-
static ERL_NIF_TERM get_handle_result(ErlNifEnv *env, uintptr_t handle)
109+
static ERL_NIF_TERM get_handle_result(ErlNifEnv *env, ErlNifResourceType *type, uintptr_t handle)
66110
{
67111
IF_ERROR(handle == 0, "invalid handle returned");
68-
return make_ok_tuple2(env, enif_make_uint64(env, handle));
112+
uintptr_t *obj = enif_alloc_resource(type, sizeof(uintptr_t));
113+
IF_ERROR(obj == NULL, "couldn't create resource");
114+
*obj = handle;
115+
ERL_NIF_TERM term = enif_make_resource(env, obj);
116+
// NOTE: we need to release our reference, so it can be GC'd
117+
enif_release_resource(obj);
118+
return make_ok_tuple2(env, term);
119+
}
120+
121+
void send_message(erl_pid_t _pid, uintptr_t stream_handle)
122+
{
123+
// Passed as void* to avoid including erl_nif.h in the header.
124+
ErlNifPid *pid = (ErlNifPid *)_pid;
125+
ErlNifEnv *env = enif_alloc_env();
126+
127+
ERL_NIF_TERM message = get_handle_result(env, Stream, stream_handle);
128+
129+
int result = enif_send(NULL, pid, env, message);
130+
// On error, the env isn't freed by the function.
131+
if (!result)
132+
{
133+
enif_free_env(env);
134+
}
69135
}
70136

71137
/*********/
@@ -80,7 +146,7 @@ ERL_FUNCTION(listen_addr_strings)
80146

81147
uintptr_t handle = ListenAddrStrings(listen_addr);
82148

83-
return get_handle_result(env, handle);
149+
return get_handle_result(env, Option, handle);
84150
}
85151

86152
/****************/
@@ -97,23 +163,24 @@ ERL_FUNCTION(host_new)
97163
while (!enif_is_empty_list(env, tail) && i < MAX_OPTIONS)
98164
{
99165
enif_get_list_cell(env, tail, &head, &tail);
100-
options[i++] = GET_HANDLE(head, "option");
166+
uintptr_t handle = GET_HANDLE(head, Option);
167+
options[i++] = handle;
101168
}
102169
GoSlice go_options = {options, i, MAX_OPTIONS};
103170
uintptr_t result = HostNew(go_options);
104-
return get_handle_result(env, result);
171+
return get_handle_result(env, Host, result);
105172
}
106173

107174
ERL_FUNCTION(host_close)
108175
{
109-
uintptr_t host = GET_HANDLE(argv[0], "host");
176+
uintptr_t host = GET_HANDLE(argv[0], Host);
110177
HostClose(host);
111178
return enif_make_atom(env, "ok");
112179
}
113180

114181
ERL_FUNCTION(host_set_stream_handler)
115182
{
116-
uintptr_t host = GET_HANDLE(argv[0], "host");
183+
uintptr_t host = GET_HANDLE(argv[0], Host);
117184

118185
ErlNifBinary bin;
119186
IF_ERROR(!enif_inspect_binary(env, argv[1], &bin), "invalid protocol ID");
@@ -124,41 +191,41 @@ ERL_FUNCTION(host_set_stream_handler)
124191

125192
IF_ERROR(!enif_self(env, pid), "failed to get pid");
126193

127-
SetStreamHandler(host, proto_id, (void *)pid);
194+
HostSetStreamHandler(host, proto_id, (void *)pid, send_message);
128195

129196
return enif_make_atom(env, "ok");
130197
}
131198

132199
ERL_FUNCTION(host_new_stream)
133200
{
134-
uintptr_t host = GET_HANDLE(argv[0], "host");
135-
uintptr_t id = GET_HANDLE(argv[1], "peer id");
201+
uintptr_t host = GET_HANDLE(argv[0], Host);
202+
uintptr_t id = GET_HANDLE(argv[1], peer_ID);
136203

137204
ErlNifBinary bin;
138205
IF_ERROR(!enif_inspect_binary(env, argv[2], &bin), "invalid protocol ID");
139206
GoString proto_id = {(const char *)bin.data, bin.size};
140207

141-
int result = NewStream(host, id, proto_id);
142-
return get_handle_result(env, result);
208+
uintptr_t result = HostNewStream(host, id, proto_id);
209+
return get_handle_result(env, Stream, result);
143210
}
144211

145-
ERL_FUNCTION_GETTER(host_peerstore, Peerstore)
146-
ERL_FUNCTION_GETTER(host_id, ID)
147-
ERL_FUNCTION_GETTER(host_addrs, Addrs)
212+
ERL_FUNCTION_GETTER(host_peerstore, Host, Peerstore, HostPeerstore)
213+
ERL_FUNCTION_GETTER(host_id, Host, peer_ID, HostID)
214+
ERL_FUNCTION_GETTER(host_addrs, Host, Multiaddr_arr, HostAddrs)
148215

149216
/*********************/
150217
/* Peerstore methods */
151218
/*********************/
152219

153220
ERL_FUNCTION(peerstore_add_addrs)
154221
{
155-
uintptr_t ps = GET_HANDLE(argv[0], "peerstore");
156-
uintptr_t id = GET_HANDLE(argv[1], "peer id");
157-
uintptr_t addrs = GET_HANDLE(argv[2], "addrs");
222+
uintptr_t ps = GET_HANDLE(argv[0], Peerstore);
223+
uintptr_t id = GET_HANDLE(argv[1], peer_ID);
224+
uintptr_t addrs = GET_HANDLE(argv[2], Multiaddr_arr);
158225
u_long ttl;
159226
IF_ERROR(!enif_get_uint64(env, argv[3], &ttl), "invalid TTL");
160227

161-
AddAddrs(ps, id, addrs, ttl);
228+
PeerstoreAddAddrs(ps, id, addrs, ttl);
162229
return enif_make_atom(env, "ok");
163230
}
164231

@@ -168,7 +235,7 @@ ERL_FUNCTION(peerstore_add_addrs)
168235

169236
ERL_FUNCTION(stream_read)
170237
{
171-
uintptr_t stream = GET_HANDLE(argv[0], "stream");
238+
uintptr_t stream = GET_HANDLE(argv[0], Stream);
172239

173240
char buffer[BUFFER_SIZE];
174241
GoSlice go_buffer = {buffer, BUFFER_SIZE, BUFFER_SIZE};
@@ -185,7 +252,7 @@ ERL_FUNCTION(stream_read)
185252

186253
ERL_FUNCTION(stream_write)
187254
{
188-
uintptr_t stream = GET_HANDLE(argv[0], "stream");
255+
uintptr_t stream = GET_HANDLE(argv[0], Stream);
189256

190257
ErlNifBinary bin;
191258
IF_ERROR(!enif_inspect_binary(env, argv[1], &bin), "invalid data");
@@ -199,7 +266,7 @@ ERL_FUNCTION(stream_write)
199266

200267
ERL_FUNCTION(stream_close)
201268
{
202-
uintptr_t stream = GET_HANDLE(argv[0], "stream");
269+
uintptr_t stream = GET_HANDLE(argv[0], Stream);
203270
StreamClose(stream);
204271
return enif_make_atom(env, "ok");
205272
}
@@ -209,14 +276,15 @@ static ErlNifFunc nif_funcs[] = {
209276
NIF_ENTRY(host_new, 1),
210277
NIF_ENTRY(host_close, 1),
211278
NIF_ENTRY(host_set_stream_handler, 2),
212-
NIF_ENTRY(host_new_stream, 3),
279+
// TODO: check if host_new_stream is truly dirty
280+
NIF_ENTRY(host_new_stream, 3, ERL_NIF_DIRTY_JOB_IO_BOUND), // blocks negotiating protocol
213281
NIF_ENTRY(host_peerstore, 1),
214282
NIF_ENTRY(host_id, 1),
215283
NIF_ENTRY(host_addrs, 1),
216284
NIF_ENTRY(peerstore_add_addrs, 4),
217-
NIF_ENTRY(stream_read, 1),
218-
NIF_ENTRY(stream_write, 2),
285+
NIF_ENTRY(stream_read, 1, ERL_NIF_DIRTY_JOB_IO_BOUND), // blocks until reading
286+
NIF_ENTRY(stream_write, 2, ERL_NIF_DIRTY_JOB_IO_BOUND), // blocks when buffer is full
219287
NIF_ENTRY(stream_close, 1),
220288
};
221289

222-
ERL_NIF_INIT(Elixir.Libp2p, nif_funcs, NULL, NULL, NULL, NULL)
290+
ERL_NIF_INIT(Elixir.Libp2p, nif_funcs, load, NULL, upgrade, NULL)

0 commit comments

Comments
 (0)