Skip to content

Commit 67fb374

Browse files
committed
mnesia: Release table fixes when coordinator dies
Iterating a table in a transaction (mnesia:first/1 and friends on anything but an ordered_set) makes mnesia_tm fix the table. The fix is held by mnesia_tm and not by the coordinator, so it has to be released explicitly when the coordinator dies. The normal termination path does that, recover_coordinator/2 did not. A fixed table never reclaims the space of deleted objects, so a dets table backing disc_only_copies grows without bound at a constant row count. To verify: mnesia:create_table(foo, [{type, set}, {ram_copies, [node()]}]), P = spawn(fun() -> mnesia:transaction( fun() -> mnesia:first(foo), timer:sleep(infinity) end) end), timer:sleep(100), exit(P, kill), timer:sleep(100), %% the table is still fixed, by the mnesia_tm process {_, [{_, 1}]} = ets:info(foo, safe_fixed_monotonic_time). Added a test case to the atomicity suite.
1 parent 35b6828 commit 67fb374

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

lib/mnesia/src/mnesia_tm.erl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ recover_coordinator(Tid, Etabs) ->
659659
Protocol = asym_trans,
660660
tell_outcome(Tid, Protocol, node(), CheckNodes, TellNodes)
661661
end,
662+
clear_fixtable(Etabs),
662663
erase_ets_tabs(Etabs),
663664
transaction_terminated(Tid),
664665
mnesia_locker:release_tid(Tid).

lib/mnesia/test/mnesia_atomicity_test.erl

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
runtime_error_in_middle_of_trans/1,
3636
mnesia_down_during_infinite_trans/1,
3737
kill_self_in_middle_of_trans/1, throw_in_middle_of_trans/1,
38+
fixtable_released_when_coordinator_dies/1,
3839
lock_waiter_sw_r/1, lock_waiter_sw_rt/1, lock_waiter_sw_wt/1,
3940
lock_waiter_wr_r/1, lock_waiter_srw_r/1, lock_waiter_sw_sw/1,
4041
lock_waiter_sw_w/1, lock_waiter_sw_wr/1, lock_waiter_sw_srw/1,
@@ -68,6 +69,7 @@ all() ->
6869
[explicit_abort_in_middle_of_trans,
6970
runtime_error_in_middle_of_trans,
7071
kill_self_in_middle_of_trans, throw_in_middle_of_trans,
72+
fixtable_released_when_coordinator_dies,
7173
{group, mnesia_down_in_middle_of_trans}].
7274

7375
groups() ->
@@ -260,6 +262,25 @@ kill_self_in_middle_of_trans(Config) when is_list(Config) ->
260262

261263
?verify_mnesia(Nodes, []).
262264

265+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
266+
fixtable_released_when_coordinator_dies(doc) ->
267+
["A table fixed by a transaction is unfixed when the coordinator dies"];
268+
fixtable_released_when_coordinator_dies(suite) -> [];
269+
fixtable_released_when_coordinator_dies(Config) when is_list(Config) ->
270+
[Node1] = Nodes = ?acquire_nodes(1, Config),
271+
Ram = fixtable_released_ram,
272+
Disc = fixtable_released_disc,
273+
274+
?match({atomic, ok}, mnesia:create_table([{name, Ram}, {type, set},
275+
{ram_copies, [Node1]}])),
276+
?match({atomic, ok}, mnesia:create_table([{name, Disc}, {type, set},
277+
{disc_only_copies, [Node1]}])),
278+
279+
check_fixtable_release(Ram),
280+
check_fixtable_release(Disc),
281+
282+
?verify_mnesia(Nodes, []).
283+
263284
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
264285
throw_in_middle_of_trans(suite) -> [];
265286
throw_in_middle_of_trans(Config) when is_list(Config) ->
@@ -825,3 +846,42 @@ sync_tid_release() ->
825846
sys:get_status(whereis(mnesia_locker)),
826847
ok.
827848

849+
check_fixtable_release(Tab) ->
850+
?match(ok, mnesia:dirty_write({Tab, 1, a})),
851+
852+
%% a transaction that ends by itself releases its fixes
853+
?match({atomic, ok},
854+
mnesia:transaction(fun() -> mnesia:first(Tab), ok end)),
855+
?match({Tab, false}, {Tab, wait_until_fixed(Tab, false)}),
856+
857+
%% and so must a coordinator that is killed while holding one
858+
Coord = spawn(fun() ->
859+
mnesia:transaction(
860+
fun() ->
861+
mnesia:first(Tab),
862+
timer:sleep(infinity)
863+
end)
864+
end),
865+
?match({Tab, true}, {Tab, wait_until_fixed(Tab, true)}),
866+
exit(Coord, kill),
867+
?match({Tab, false}, {Tab, wait_until_fixed(Tab, false)}).
868+
869+
%% The fix is released asynchronously
870+
wait_until_fixed(Tab, Expected) ->
871+
wait_until_fixed(Tab, Expected, 100).
872+
873+
wait_until_fixed(Tab, _Expected, 0) ->
874+
is_fixed(Tab, mnesia:table_info(Tab, storage_type));
875+
wait_until_fixed(Tab, Expected, N) ->
876+
case is_fixed(Tab, mnesia:table_info(Tab, storage_type)) of
877+
Expected ->
878+
Expected;
879+
_ ->
880+
timer:sleep(50),
881+
wait_until_fixed(Tab, Expected, N - 1)
882+
end.
883+
884+
is_fixed(Tab, disc_only_copies) ->
885+
dets:info(Tab, safe_fixed_monotonic_time) =/= false;
886+
is_fixed(Tab, _Storage) ->
887+
ets:info(Tab, safe_fixed_monotonic_time) =/= false.

0 commit comments

Comments
 (0)