From 6c1a343c824528325825d0cd0ea9b3499ee5df24 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Sun, 12 Apr 2026 14:47:00 +0200 Subject: [PATCH] Improve mnesia-compat abort returns, use latest rocksdb --- rebar.config | 2 +- rebar.lock | 2 +- src/mrdb.erl | 55 +++++++++++++++++++++++++---------- test/mnesia_rocksdb_SUITE.erl | 24 ++++++++++----- 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/rebar.config b/rebar.config index e480c14..b170acd 100644 --- a/rebar.config +++ b/rebar.config @@ -4,7 +4,7 @@ {deps, [ {sext, "1.8.0"}, - {rocksdb, {git, "https://github.com/emqx/erlang-rocksdb.git", {ref,"d695c6e"}}}, + {rocksdb, {git, "https://github.com/emqx/erlang-rocksdb.git", {tag, "9.10.0-emqx-2"}}}, {hut, "1.4.0"} ]}. diff --git a/rebar.lock b/rebar.lock index 5149a02..05f0d25 100644 --- a/rebar.lock +++ b/rebar.lock @@ -2,7 +2,7 @@ [{<<"hut">>,{pkg,<<"hut">>,<<"1.4.0">>},0}, {<<"rocksdb">>, {git,"https://github.com/emqx/erlang-rocksdb.git", - {ref,"d695c6ee9dd27bfe492ed4e24c72ad20ab0d770b"}}, + {ref,"d3481881fa70ff116846a73cc58b540a6c444f4a"}}, 0}, {<<"sext">>,{pkg,<<"sext">>,<<"1.8.0">>},0}]}. [ diff --git a/src/mrdb.erl b/src/mrdb.erl index 8148eda..215573e 100644 --- a/src/mrdb.erl +++ b/src/mrdb.erl @@ -99,6 +99,11 @@ , encode_val/2 , decode_val/3 ]). +%% TODO: OTP 28.3 dialyzer is quite pesky about opaque types. Find a way +%% to structure this, and firm up the type specs. +-dialyzer([no_opaque, no_return]). +-dialyzer([{nowarn_function, [activity/3]}]). + -export_type( [ mrdb_iterator/0 , itr_handle/0 , iterator_action/0 @@ -179,7 +184,7 @@ -type attr_pos() :: #{atom() := pos()}. -type db_ref() :: #{ name => table() - , alias => atom() + , alias => alias() , vsn => non_neg_integer() , db_ref := db_handle() , cf_handle := cf_handle() @@ -194,6 +199,11 @@ , activity => activity() , _ => _}. +-type context() :: #{ 'activity' => activity() + , 'alias' => alias() + , 'retries' => retries() + , 'db_ref' => db_ref() }. + -type error() :: {error, any()}. -type ref_or_tab() :: table() | db_ref(). @@ -285,7 +295,7 @@ release_snapshot(SHandle) -> %% To simplify code adaptation, `tx | transaction | sync_transaction' are synonyms, and %% `batch | async_dirty | sync_dirty' are synonyms. %% @end --spec activity(activity_type(), alias(), fun( () -> Res )) -> Res. +-spec activity(activity_type(), alias(), fun( () -> any() )) -> any(). activity(Type, Alias, F) -> #{db_ref := DbRef} = ensure_ref({admin, Alias}), Ctxt = case tx_type(Type) of @@ -303,6 +313,7 @@ activity(Type, Alias, F) -> end, do_activity(F, Alias, Ctxt). +-spec do_activity(fun(() -> any()), alias(), context()) -> any(). do_activity(F, Alias, Ctxt) -> try try_f(F, Ctxt) catch @@ -319,11 +330,11 @@ try_f(false, F, Ctxt) -> commit_and_pop(Res) catch throw:Something -> - abort_and_pop(throw, Something); + abort_and_pop(false, throw, Something); Cat:Err:T -> %% Without capturing the stacktract here, %% debugging gets pretty difficult. Incompatible with mnesia, though. - abort_and_pop(Cat, {Err, T}) + abort_and_pop(false, Cat, {Err, T}) end; try_f(true, F, Ctxt) -> try run_f(F, Ctxt) of @@ -331,11 +342,11 @@ try_f(true, F, Ctxt) -> commit_and_pop(Res) catch throw:Something -> - abort_and_pop(throw, Something); + abort_and_pop(true, throw, Something); Cat:Err -> %% Without capturing the stacktract here, %% debugging gets pretty difficult - abort_and_pop(Cat, Err) + abort_and_pop(true, Cat, Err) end. @@ -368,7 +379,7 @@ retry_activity(F, Alias, #{activity := #{ type := Type retry_activity(F, Alias, Ctxt1) end; error -> - return_abort(Type, error, retry_limit) + return_abort(mnesia_compatible_aborts(Ctxt), Type, error, retry_limit) end. retry_activity_(inner, F, Alias, Ctxt) -> @@ -425,6 +436,7 @@ current_context() -> undefined end. +-spec tx_type(activity_type()) -> {'tx', map()} | 'batch'. tx_type(T) -> case T of _ when T==batch; @@ -511,8 +523,8 @@ commit_and_pop(Res) -> end end. --spec abort_and_pop(atom(), any()) -> no_return(). -abort_and_pop(Cat, Err) -> +-spec abort_and_pop(boolean(), atom(), any()) -> no_return(). +abort_and_pop(Compat, Cat, Err) -> %% We can pop the context right away, since there is no %% complex failure handling (like retry-on-busy) for rollback. #{activity := #{type := Type, handle := H}} = pop_ctxt(), @@ -520,23 +532,29 @@ abort_and_pop(Cat, Err) -> tx -> ok = rdb_transaction_rollback(H); batch -> ok = release_batches(H) end, - return_abort(Type, Cat, Err). + return_abort(Compat, Type, Cat, Err). --spec return_abort(batch | tx, atom(), any()) -> no_return(). -return_abort(batch, Cat, Err) -> +-spec return_abort(boolean(), batch | tx, atom(), any()) -> no_return(). +return_abort(_, batch, Cat, Err) -> re_throw(Cat, Err); -return_abort(tx, Cat, Err) -> - case mnesia_compatible_aborts() of +return_abort(Compat, tx, Cat, Err) -> + case Compat of true -> %% Mnesia always captures stack traces, but this could actually become a %% performance issue in some cases (generally, it's better not to lazily - %% produce stack traces.) Since we want to pushe the option checking for + %% produce stack traces.) Since we want to push the option checking for %% mnesia-abort-style compatibility to AFTER detecting an abort, we don't %% order a stack trace initially, and instead insert an empty list. %% (The exact stack trace wouldn't be the same anyway.) + %% NOTE: This behavior changed in mnesia-4.23.5.1, so if we really want + %% to stay compatible, we have to special-case things. Err1 = case Cat of - error -> {fix_error(Err), []}; + error -> + case newer_mnesia() of + true -> fix_error(Err); + false -> {fix_error(Err), []} + end; exit -> fix_error(Err); throw -> {throw, Err} end, @@ -545,6 +563,11 @@ return_abort(tx, Cat, Err) -> re_throw(Cat, Err) end. +%% Some rollback return values changed in mnesia in vsn 4.23.5.1 +newer_mnesia() -> + {ok, Vsn} = application:get_key(mnesia, vsn), + ("4.23.5" < Vsn). + -spec re_throw(atom(), any()) -> no_return(). re_throw(Cat, Err) -> case Cat of diff --git a/test/mnesia_rocksdb_SUITE.erl b/test/mnesia_rocksdb_SUITE.erl index da3e337..825acc5 100644 --- a/test/mnesia_rocksdb_SUITE.erl +++ b/test/mnesia_rocksdb_SUITE.erl @@ -235,6 +235,7 @@ compare_txs(Type, F) -> ct:log("Mrdb = ~p/~p", [Type, EMr]), case {Type, EMn, EMr} of {error, {some_value, [_|_]}, {some_value, []}} -> ok; + {error, E, E} -> ok; {throw, {throw, some_value}, {throw, some_value}} -> ok; {exit, some_value, some_value} -> ok; {abort, some_value, some_value} -> ok @@ -277,21 +278,30 @@ mrdb_abort(Config) -> mrdb:insert(tx_abort, {tx_abort, a, 1}), Pre = mrdb:read(tx_abort, a), D0 = get_dict(), + ActivityF = fun() -> + [{tx_abort, a, N}] = mrdb:read(tx_abort, a), + error(abort_here), + ok = mrdb:insert(tx_abort, [{tx_abort, a, N+1}]), + noooo + end, TRes = try mrdb:activity( {tx, #{mnesia_compatible => true}}, rdb, - fun() -> - [{tx_abort, a, N}] = mrdb:read(tx_abort, a), - error(abort_here), - ok = mrdb:insert(tx_abort, [{tx_abort, a, N+1}]), - noooo - end) + ActivityF) catch - error:abort_here -> + exit:{aborted, {abort_here, []}} -> ok end, dictionary_unchanged(D0), ok = TRes, Pre = mrdb:read(tx_abort, a), + TRes1 = try mrdb:activity(tx, rdb, ActivityF) + catch + error:{abort_here, [_|_]} -> + ok + end, + dictionary_unchanged(D0), + ok = TRes1, + Pre = mrdb:read(tx_abort, a), delete_tabs(Created), ok.