[erlang-bugs] mnesia can corrupt tables if the VM runs out of file descriptors
Mikael Pettersson
mikpelinux@REDACTED
Sun May 31 12:06:30 CEST 2015
Mikael Pettersson writes:
> -module(mnesia_corrupts_data).
> -compile(export_all).
>
> -record(foo, {key, data}).
>
> doit() ->
> %% set up initial db with foo.DCD and foo.DCL,
> %% foo contains records with keys [0,N[
> N = init_db(),
> io:format("Initial DB:\n~s", [os:cmd("ls -l Mnesia.nonode@REDACTED")]),
> FDs = open_all_available_fds(),
> %% free enough FDs so mnesia can start, but not enough for it
> %% to be able to open foo.DCL
> close_some_fds(FDs),
> check_db(N).
>
> init_db() ->
> ok = mnesia:create_schema([node()]),
> ok = mnesia:start(),
> {atomic, ok} = mnesia:create_table(foo,
> [{attributes, record_info(fields, foo)},
> {disc_copies, [node()]}]),
> N = create_dcl(0),
> stopped = mnesia:stop(),
> N.
>
> create_dcl(N) ->
> case prim_file:read_file_info("Mnesia.nonode@REDACTED/foo.DCL") of
> {error, enoent} ->
> {atomic, ok} =
> mnesia:transaction(fun() -> mnesia:write(#foo{key = N, data = "bar"}) end),
> create_dcl(N + 1);
> {ok, _} ->
> io:format("DCL created after ~p records written\n", [N]),
> N
> end.
>
> check_db(N) ->
> ok = mnesia:start(),
> ok = mnesia:wait_for_tables([foo], 5*1000),
> check_foo(0, N).
>
> check_foo(I, N) when I < N ->
> [{foo, I, "bar"}] = mnesia:dirty_read(foo, I),
> check_foo(I + 1, N);
> check_foo(I, N) when I >= N ->
> ok.
>
> open_all_available_fds() ->
> ok = file:write_file("dummy.txt", <<"whatever\n">>),
> open_all_available_fds([], 0).
>
> open_all_available_fds(FDs, N) ->
> case prim_file:open("dummy.txt", [read]) of
> {ok, FD} ->
> open_all_available_fds([FD | FDs], N + 1);
> {error, emfile} ->
> io:format("managed to open ~p files\n", [N]),
> FDs
> end.
>
> close_some_fds([FD1, FD2 | _]) ->
> prim_file:close(FD1),
> prim_file:close(FD2).
>
> ----------------------------------------------------------------------
> If the Erlang VM is close to its file descriptor limit, and mnesia tries
> to open a disc_copies table's .DCL file, the open fails with {error, emfile}
> which mnesia_log:open_log/6 incorrectly interprets as a corrupt file, and it
> then DELETES the perfectly valid .DCL file.
>
> This is reproducible with (at least) OTP 18.0-rc2 and R15B03.
>
> (This is not a hypothetical problem, it hit us and corrupted three tables,
> though we managed to recover through luck and manual emergency procedures.)
>
> I'm attaching a standalone module which reproduces the corruption for me
> with OTP 18.0-rc2 on a Fedora 20 Linux / x86_64 desktop system. Here's a
> transcript from a run of that module (foo.DCL is the interesting file):
>
> Script started on Sat 30 May 2015 05:47:41 PM CEST
> _1_/tmp/otp/bin/erlc mnesia_corrupts_data.erl
> _2_/tmp/otp/bin/erl
> Erlang/OTP 18 [RELEASE CANDIDATE 2] [erts-7.0] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]
>
> Eshell V7.0 (abort with ^G)
> 1> mnesia_corrupts_data:doit().
> DCL created after 1024 records written
>
> =INFO REPORT==== 30-May-2015::17:48:23 ===
> application: mnesia
> exited: stopped
> type: temporary
> Initial DB:
> total 152
> -rw-r--r-- 1 mikpe mikpe 2715 May 30 17:48 LATEST.LOG
> -rw-r--r-- 1 mikpe mikpe 104101 May 30 17:48 PREVIOUS.LOG
> -rw-r--r-- 1 mikpe mikpe 8 May 30 17:48 foo.DCD
> -rw-r--r-- 1 mikpe mikpe 31039 May 30 17:48 foo.DCL
> -rw-r--r-- 1 mikpe mikpe 6750 May 30 17:48 schema.DAT
> managed to open 1014 files
> Mnesia(nonode@REDACTED): Data may be missing, Corrupt logfile deleted: "/tmp/Mnesia.nonode@REDACTED/foo.DCL", {file_error,
> "/tmp/Mnesia.nonode@REDACTED/foo.DCL",
> emfile}
>
> =ERROR REPORT==== 30-May-2015::17:48:25 ===
> Mnesia(nonode@REDACTED): ** ERROR ** (could not write core file: emfile)
> ** FATAL ** Cannot open log file "/tmp/Mnesia.nonode@REDACTED/foo.DCL": {file_error,
> "/tmp/Mnesia.nonode@REDACTED/foo.DCL",
> emfile}
>
> =ERROR REPORT==== 30-May-2015::17:48:35 ===
> ** Generic server mnesia_monitor terminating
> ** Last message in was {'EXIT',<0.95.0>,killed}
> ** When Server state == {state,<0.95.0>,[],[],true,[],undefined,[],[]}
> ** Reason for termination ==
> ** killed
>
> =ERROR REPORT==== 30-May-2015::17:48:35 ===
> ** Generic server mnesia_recover terminating
> ** Last message in was {'EXIT',<0.95.0>,killed}
> ** When Server state == {state,<0.95.0>,undefined,undefined,undefined,0,false,
> true,[]}
> ** Reason for termination ==
> ** killed
>
> =INFO REPORT==== 30-May-2015::17:48:35 ===
> application: mnesia
> exited: {killed,{mnesia_sup,start,[normal,[]]}}
> type: temporary
> ** exception exit: {badmatch,{error,{killed,{mnesia_sup,start,[normal,[]]}}}}
> in function mnesia_corrupts_data:check_db/1 (mnesia_corrupts_data.erl, line 39)
> 2>
> BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
> (v)ersion (k)ill (D)b-tables (d)istribution
> ^C
> _3_ls -l Mnesia.nonode@REDACTED/
> total 120
> -rw-r--r-- 1 mikpe mikpe 2715 May 30 17:48 LATEST.LOG
> -rw-r--r-- 1 mikpe mikpe 104101 May 30 17:48 PREVIOUS.LOG
> -rw-r--r-- 1 mikpe mikpe 8 May 30 17:48 foo.DCD
> -rw-r--r-- 1 mikpe mikpe 6750 May 30 17:48 schema.DAT
> _4_
>
> /Mikael
https://github.com/erlang/otp/pull/752
Tested with 18.0-rc2 and 17.5.3.
More information about the erlang-bugs
mailing list