From 5dfcf3715392f533d93438f56ec6affdfcccd8db Mon Sep 17 00:00:00 2001 From: Jesse Stimpson Date: Sat, 20 Dec 2025 19:34:38 -0500 Subject: [PATCH 1/3] Fix future cancelation deadlock --- c_src/main.c | 3 ++- test/erlfdb_02_anon_fdbserver_test.erl | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/c_src/main.c b/c_src/main.c index 9527ee5..afb49b0 100644 --- a/c_src/main.c +++ b/c_src/main.c @@ -680,10 +680,11 @@ static ERL_NIF_TERM erlfdb_future_cancel(ErlNifEnv *env, int argc, enif_mutex_lock(future->lock); future->cancelled = true; - fdb_future_cancel(future->future); enif_mutex_unlock(future->lock); + fdb_future_cancel(future->future); + return ATOM_ok; } diff --git a/test/erlfdb_02_anon_fdbserver_test.erl b/test/erlfdb_02_anon_fdbserver_test.erl index 5471f20..70bb02d 100644 --- a/test/erlfdb_02_anon_fdbserver_test.erl +++ b/test/erlfdb_02_anon_fdbserver_test.erl @@ -361,6 +361,23 @@ watch_test() -> error(timeout) end. +watch_cancel_test() -> + Db = erlfdb_sandbox:open(), + Tenant = erlfdb_util:create_and_open_test_tenant(Db, [empty]), + Future = + {erlfdb_future, MsgRef, _FRef} = erlfdb:transactional(Tenant, fun(Tx) -> + erlfdb:set(Tx, <<"hello_watch">>, <<"foo">>), + erlfdb:watch(Tx, <<"hello_watch">>) + end), + ok = erlfdb:cancel(Future, [{flush, true}]), + erlfdb:transactional(Tenant, fun(Tx) -> erlfdb:set(Tx, <<"hello_watch">>, <<"bar">>) end), + receive + {MsgRef, ready} -> + error(unexpected_ready) + after 100 -> + ok + end. + watch_to_test() -> Db = erlfdb_sandbox:open(), Tenant = erlfdb_util:create_and_open_test_tenant(Db, [empty]), From 916c854736e7db59bbaf72464b76b599824ae574 Mon Sep 17 00:00:00 2001 From: Jesse Stimpson Date: Sat, 20 Dec 2025 22:58:33 -0500 Subject: [PATCH 2/3] Add comments regarding management of pid_env --- c_src/main.c | 9 +++++++-- c_src/resources.h | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/c_src/main.c b/c_src/main.c index afb49b0..e3907d5 100644 --- a/c_src/main.c +++ b/c_src/main.c @@ -678,11 +678,16 @@ static ERL_NIF_TERM erlfdb_future_cancel(ErlNifEnv *env, int argc, future = (ErlFDBFuture *)res; enif_mutex_lock(future->lock); - future->cancelled = true; - enif_mutex_unlock(future->lock); + // fdb_future_cancel is likely to call the registered callback. + // If not for the cancelled flag, this would lead to a segfault + // since pid_env is no longer valid. The cancelled flag prevents + // the callback from using the pid_env for enif_send. Other operations + // on the future may cause a synchronous callback without the + // flag to protect us. Use caution. + fdb_future_cancel(future->future); return ATOM_ok; diff --git a/c_src/resources.h b/c_src/resources.h index 7caa1fc..47b313a 100644 --- a/c_src/resources.h +++ b/c_src/resources.h @@ -39,8 +39,24 @@ typedef struct _ErlFDBFuture { FDBFuture *future; ErlFDBFutureType ftype; ErlNifPid pid; + + /* pid_env: A 'process bound environment' for sending messages + * in the event of the callback executing synchronously + * (e.g. fdb_future_cancel) + * + * Being process bound, it must not be used across different + * NIF calls. + * + * An alternative is to use thread-specific data (tsd) to + * store the environment for each NIF call. + */ ErlNifEnv *pid_env; + + /* msg_env: A 'process independent environment' used to send + * terms with enif_send. + */ ErlNifEnv *msg_env; + ERL_NIF_TERM msg_ref; ErlNifMutex *lock; bool cancelled; From c3d484c9582b645b0dbb86d0356314522c39ab7c Mon Sep 17 00:00:00 2001 From: Jesse Stimpson Date: Sun, 21 Dec 2025 11:53:35 -0500 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dffa76..e4b6315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ * (#57) Improve performance of the Tuple layer's encoding and decoding of strings/binaries. * (#50) Add support for caller to provide local target pid for watch and get_versionstamp ready messages. (e.g, `erlfdb:watch(Tx, <<"key>>, [{to, Pid}])`) * (#61) Add `erlfdb:get_main_thread_busyness/1` and `erlfdb:get_client_status/1`. + +### Bug fixes + + * (#59) Fix deadlock when canceling a future ## v0.3.2 (2025-06-13)