Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
12 changes: 9 additions & 3 deletions c_src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -678,12 +678,18 @@ static ERL_NIF_TERM erlfdb_future_cancel(ErlNifEnv *env, int argc,
future = (ErlFDBFuture *)res;

enif_mutex_lock(future->lock);

future->cancelled = true;
fdb_future_cancel(future->future);

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;
}

Expand Down
16 changes: 16 additions & 0 deletions c_src/resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions test/erlfdb_02_anon_fdbserver_test.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
Expand Down