Skip to content

Commit 448b318

Browse files
Handle allocation failures and improve C docs (#66)
1 parent 947d3a4 commit 448b318

3 files changed

Lines changed: 86 additions & 21 deletions

File tree

c_src/main.c

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,22 @@ static void erlfdb_future_cb(FDBFuture *fdb_future, void *data) {
6363
ErlNifEnv *caller;
6464
ERL_NIF_TERM msg;
6565

66-
// FoundationDB callbacks can fire from the thread
67-
// that created them. Check if we were actually
68-
// submitted to the network thread or not so that
69-
// we pass the correct environment to enif_send
66+
// FoundationDB callbacks can fire either:
67+
// 1. Synchronously from the calling thread (when the future is already
68+
// ready at the time fdb_future_set_callback is called), or
69+
// 2. Asynchronously from the FDB network thread
70+
//
71+
// We use enif_thread_type() to distinguish these cases:
72+
// - ERL_NIF_THR_UNDEFINED means we're on the FDB network thread (case 2)
73+
// - Any other value means we're on an Erlang scheduler thread (case 1)
74+
//
75+
// For enif_send, when called from a non-scheduler thread we must pass
76+
// NULL as the caller environment. When called synchronously from a NIF,
77+
// we can pass the process-bound environment (pid_env) for efficiency.
78+
//
79+
// IMPORTANT: pid_env is only valid during the NIF call that created
80+
// this future. This is safe here because synchronous callbacks only
81+
// occur during erlfdb_create_future, before that NIF returns.
7082
if (enif_thread_type() == ERL_NIF_THR_UNDEFINED) {
7183
caller = NULL;
7284
} else {
@@ -100,6 +112,11 @@ static ERL_NIF_TERM erlfdb_create_future(ErlNifEnv *env, ERL_NIF_TERM *tx_ref,
100112
fdb_error_t err;
101113

102114
f = enif_alloc_resource(ErlFDBFutureRes, sizeof(ErlFDBFuture));
115+
if (f == NULL) {
116+
fdb_future_destroy(future);
117+
return enif_make_badarg(env);
118+
}
119+
103120
f->future = future;
104121
f->ftype = ftype;
105122
if (to == NULL) {
@@ -109,13 +126,23 @@ static ERL_NIF_TERM erlfdb_create_future(ErlNifEnv *env, ERL_NIF_TERM *tx_ref,
109126
}
110127
f->pid_env = env;
111128
f->msg_env = enif_alloc_env();
129+
if (f->msg_env == NULL) {
130+
enif_release_resource(f);
131+
return enif_make_badarg(env);
132+
}
133+
112134
if (tx_ref != NULL) {
113135
f->msg_ref = T2(f->msg_env, enif_make_copy(f->msg_env, *tx_ref),
114136
enif_make_copy(f->msg_env, ref));
115137
} else {
116138
f->msg_ref = enif_make_copy(f->msg_env, ref);
117139
}
118140
f->lock = enif_mutex_create("fdb:future_lock");
141+
if (f->lock == NULL) {
142+
enif_release_resource(f);
143+
return enif_make_badarg(env);
144+
}
145+
119146
f->cancelled = false;
120147

121148
// This resource reference counting dance is a bit
@@ -591,9 +618,8 @@ static ERL_NIF_TERM erlfdb_network_set_option(ErlNifEnv *env, int argc,
591618
option = FDB_NET_OPTION_DISABLE_CLIENT_STATISTICS_LOGGING;
592619
} else if (IS_ATOM(argv[0], enable_slow_task_profiling)) {
593620
option = FDB_NET_OPTION_ENABLE_SLOW_TASK_PROFILING;
594-
}
595621
#if FDB_API_VERSION >= 630
596-
else if (IS_ATOM(argv[0], enable_run_loop_profiling)) {
622+
} else if (IS_ATOM(argv[0], enable_run_loop_profiling)) {
597623
option = FDB_NET_OPTION_ENABLE_RUN_LOOP_PROFILING;
598624
} else if (IS_ATOM(argv[0], client_threads_per_version)) {
599625
option = FDB_NET_OPTION_CLIENT_THREADS_PER_VERSION;
@@ -602,9 +628,9 @@ static ERL_NIF_TERM erlfdb_network_set_option(ErlNifEnv *env, int argc,
602628
#if FDB_API_VERSION >= 730
603629
option = FDB_NET_OPTION_IGNORE_EXTERNAL_CLIENT_FAILURES;
604630
#else
605-
// 7.3 added some new checks for loading external client libraries
606-
// and those checks are not present in the older versions
607-
return ATOM_ok;
631+
// 7.3 added some new checks for loading external client libraries
632+
// and those checks are not present in the older versions
633+
return ATOM_ok;
608634
#endif
609635
} else {
610636
return enif_make_badarg(env);
@@ -843,6 +869,11 @@ static ERL_NIF_TERM erlfdb_create_database(ErlNifEnv *env, int argc,
843869
}
844870

845871
d = enif_alloc_resource(ErlFDBDatabaseRes, sizeof(ErlFDBDatabase));
872+
if (d == NULL) {
873+
fdb_database_destroy(database);
874+
return enif_make_badarg(env);
875+
}
876+
846877
d->database = database;
847878

848879
ret = enif_make_resource(env, d);
@@ -890,6 +921,11 @@ static ERL_NIF_TERM erlfdb_database_open_tenant(ErlNifEnv *env, int argc,
890921
}
891922

892923
ten = enif_alloc_resource(ErlFDBTenantRes, sizeof(ErlFDBTenant));
924+
if (ten == NULL) {
925+
fdb_tenant_destroy(tenant);
926+
return enif_make_badarg(env);
927+
}
928+
893929
ten->tenant = tenant;
894930

895931
ret = enif_make_resource(env, ten);
@@ -995,6 +1031,11 @@ erlfdb_database_create_transaction(ErlNifEnv *env, int argc,
9951031
}
9961032

9971033
t = enif_alloc_resource(ErlFDBTransactionRes, sizeof(ErlFDBTransaction));
1034+
if (t == NULL) {
1035+
fdb_transaction_destroy(transaction);
1036+
return enif_make_badarg(env);
1037+
}
1038+
9981039
t->transaction = transaction;
9991040

10001041
enif_self(env, &pid);
@@ -1095,6 +1136,11 @@ erlfdb_tenant_create_transaction(ErlNifEnv *env, int argc,
10951136
}
10961137

10971138
t = enif_alloc_resource(ErlFDBTransactionRes, sizeof(ErlFDBTransaction));
1139+
if (t == NULL) {
1140+
fdb_transaction_destroy(transaction);
1141+
return enif_make_badarg(env);
1142+
}
1143+
10981144
t->transaction = transaction;
10991145

11001146
enif_self(env, &pid);

c_src/resources.h

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,37 @@ typedef struct _ErlFDBFuture {
4040
ErlFDBFutureType ftype;
4141
ErlNifPid pid;
4242

43-
/* pid_env: A 'process bound environment' for sending messages
44-
* in the event of the callback executing synchronously
45-
* (e.g. fdb_future_cancel)
43+
/* pid_env: The process-bound environment from the NIF call that
44+
* created this future. Used as the 'caller_env' argument to
45+
* enif_send() when the FDB callback fires synchronously.
4646
*
47-
* Being process bound, it must not be used across different
48-
* NIF calls.
47+
* LIFETIME: Only valid during the erlfdb_create_future() call.
48+
* After that NIF returns, this pointer is stale and must not be
49+
* dereferenced.
4950
*
50-
* An alternative is to use thread-specific data (tsd) to
51-
* store the environment for each NIF call.
51+
* SAFETY: This is safe because we only use pid_env when
52+
* enif_thread_type() != ERL_NIF_THR_UNDEFINED, which indicates
53+
* we're on an Erlang scheduler thread. Synchronous callbacks
54+
* only occur during fdb_future_set_callback() within
55+
* erlfdb_create_future(), so pid_env is still valid at that point.
56+
* Asynchronous callbacks from the FDB network thread pass NULL
57+
* to enif_send() instead.
58+
*
59+
* FUTURE CONSIDERATION: A more robust alternative would be to use
60+
* enif_tsd_key_create() to store the current NIF environment in
61+
* thread-specific data at the start of each NIF call. The callback
62+
* could then retrieve it via enif_tsd_get(), eliminating the need
63+
* to store a potentially-stale pointer in the future struct.
64+
*
65+
* See erlfdb_future_cb() in main.c for the callback implementation.
5266
*/
5367
ErlNifEnv *pid_env;
5468

55-
/* msg_env: A 'process independent environment' used to send
56-
* terms with enif_send.
69+
/* msg_env: A process-independent environment allocated with
70+
* enif_alloc_env(). Owns the terms (msg_ref) sent via enif_send().
71+
*
72+
* LIFETIME: Allocated in erlfdb_create_future(), freed in
73+
* erlfdb_future_dtor(). Survives across NIF calls and threads.
5774
*/
5875
ErlNifEnv *msg_env;
5976

src/erlfdb.erl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1930,10 +1930,12 @@ get_approximate_size(?IS_SS = SS) ->
19301930
Gets a local auto-incrementing integer for this transaction.
19311931
19321932
Each time this function is executed, a local counter on the transaction is incremented,
1933-
and the value returned.
1933+
and the value returned. Returns values in the range 0-65535 (16-bit). Raises `badarg`
1934+
if called more than 65536 times on the same transaction.
19341935
19351936
This integer is useful in combination with versionstamps to ensure uniqueness
1936-
of versionstamped keys and values for your transaction.
1937+
of versionstamped keys and values for your transaction. The 16-bit limit matches
1938+
the user-batch portion of FoundationDB versionstamps.
19371939
19381940
## Examples
19391941
@@ -1955,7 +1957,7 @@ Without `get_next_tx_id`, the versionstamped keys generated at commit time would
19551957
exactly one versionstamp is created at commit time.
19561958
""".
19571959
-endif.
1958-
-spec get_next_tx_id(transaction() | snapshot()) -> non_neg_integer().
1960+
-spec get_next_tx_id(transaction() | snapshot()) -> 0..65535.
19591961
get_next_tx_id(?IS_TX = Tx) ->
19601962
erlfdb_nif:transaction_get_next_tx_id(Tx);
19611963
get_next_tx_id(?IS_SS = SS) ->

0 commit comments

Comments
 (0)