Another race condition in dets--open_file causes inconsistent view in another process

John Hughes john.hughes@REDACTED
Tue Sep 21 15:17:31 CEST 2010


QuickChecking dets has turned up another race condition (this one found by the Chalmers ProTest team).

Calling open_file in one process can cause another process using the same table to see an inconsistent view of the data. The simplest example is illustrated by

bug1a() ->
    dets:close(dets_table),
    file:delete(dets_table),
    {ok,T} = dets:open_file(dets_table,[{type,bag}]),
    spawn(fun() -> dets:open_file(dets_table,[{type,bag}]) end),
    spawn(fun() ->
          dets:insert(dets_table,[{0,0}]),
          io:format("~p\n",[get_contents(dets_table)])
    end).

which should always print [{0,0}], but sometimes prints []. get_contents just fetches the entire table contents:

get_contents(Name) ->
    dets:traverse(Name,fun(X)->{continue,X}end).

Since the insertion and the traversal occur in the SAME process, one would expect the traversal to see the inserted data... but it doesn't always.

Presumably the same bug is visible in this example, where the change that's lost is a deletion instead:

bug1b() ->
    dets:close(dets_table),
    file:delete(dets_table),
    {ok,T} = dets:open_file(dets_table,[{type,bag}]),
    dets:insert(dets_table,{7,0}),
    spawn(fun() -> dets:open_file(dets_table,[{type,bag}]) end),
    spawn(fun() ->
          dets:delete(dets_table,7),
          io:format("~p\n",[get_contents(dets_table)])
    end).

which should always print [], but sometimes prints [{7,0}].

These examples do fail, but I'm afraid they fail pretty rarely--in the nature of race conditions, I suppose. It's a lot easier to provoke the failures with QuickCheck than by running bug1a and bug1b, because they have to be run so many times. When running them repeatedly, you also need to worry about one test case overlapping with the next. Here's a version of bug1a() with enough synchronization to prevent that:

bug1c() ->
    Self = self(),
    spawn(fun() ->
           [dets:close(dets_table) || _ <- "abcdefghijkl"],
            file:delete(dets_table),
            Parent = self(),
            {ok,T} = dets:open_file(dets_table,[{type,bag}]),
            spawn(fun() -> dets:open_file(dets_table,[{type,bag}]),
                           Parent ! done
                  end),
            spawn(fun() ->
                           dets:insert(dets_table,[{0,0}]),
                           io:format("~p\n",[get_contents(dets_table)]),
                           Parent ! done
                  end),
            receive done -> receive done -> ok end end,
            Self ! ok
           end),
    receive ok -> ok end.

Running this 1,000 times will print [] a few times, revealing the bug.

Alternatively, compiling the attached file with options {d,'BUG_OPEN_FILE',true} and running QuickCheck on prop_parallel() provokes the bug within a few hundred tests. This needs the latest version of QuickCheck, 1.22, for the latest race condition testing code.

John
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-bugs/attachments/20100921/be70425e/attachment.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: dets_eqc.erl
Type: application/octet-stream
Size: 5328 bytes
Desc: not available
URL: <http://erlang.org/pipermail/erlang-bugs/attachments/20100921/be70425e/attachment.obj>


More information about the erlang-bugs mailing list