[erlang-bugs] Funny behaviour of dirty_next in mnesia?

John Hughes <>
Wed May 18 09:56:48 CEST 2011

  From: Ulf Wiger 

  Well, it's the same process that's calling the functions, so usually one would expect a sequential view of the data.

  However, in the case of transactions vs dirty, it's not quite as clear-cut, and I don't think the documentation has anything to say about this. except to hint that it's unhealthy to mix dirty and transactions too much.

  I would agree with John that this behaviour is surprising on the surface of it. Perhaps using mnesia:activity(sync_transaction, F) or mnesia:sync_transaction(F) * would make a difference? I'll admit that I haven't tried.

  * According to the User Guide, "Synced transactions waits until all active replicas has committed the transaction (to disc) before returning from the mnesia:sync_transaction call."

  Ulf W

Yes, using sync_transaction makes the example behave as I would expect. So does doing the guilty operation in a transaction instead of as a dirty read or next. So it seems the lesson is: don't use dirty operations and transactions together, if you expect a sequential view of the data.


  On 17 May 2011, at 23:32, Ahmed Omar wrote:

    Well, it's not the same process. When mnesia find out that :
    - there are N of updates to commit, 
    - the protocol to use it async 
    - the node of tid  is not the local node 

    it spawns a new process to do the commit.

    On Tue, May 17, 2011 at 10:44 PM, John Hughes <> wrote:

        From: Ahmed Omar 

        I'm not a mnesia expert, but i THINK the race condition is in the test not mnesia. transaction is still being committed and logged, when the dirty read is issued. if you add a sleep in between or better if you use mnesia:sync_transaction (http://www.erlang.org/doc/man/mnesia.html#sync_transaction-3) instead of mnesia:transaction, the test will fail, i.e the case disappear 
        isn't that the expected behavior or am i missing something?

      Adding a sleep (I added a second) or using sync_transaction instead changes the behaviour to what I would expect, so it sounds as though you may be right about what's happening. But even so, it's not the behaviour I would expect, at least!

      There isn't any concurrency in the test. There's only distribution--and there's only one copy of the table, on the slave node. Isn't it weird that when the transaction returns, the SAME process that ran the transaction does not see its side effects?

      By the way, if I swap the last two operations (which Ulf Wiger suggested), then I see the same kind of behaviour... but now the first operation (which is now a dirty_read) actually retrieves the deleted tuple from the table, while the second operation (now the dirty_next) sees no keys in the table.

      This doesn't happen if the table is on the same node as the test is executed on, so distribution certainly is not transparent in this case.


        On Tue, May 17, 2011 at 6:57 PM, John Hughes <> wrote:

          QuickCheck turned up another case of odd behaviour at Klarna.

          The test runs mnesia on two nodes, creates a table on the OTHER node, then adds and deletes a record. After this the record is indeed not IN the table, but dirty_next finds its key anyway! Surely it shouldn't?

          Here's the test:

          test() ->
              Slave = start_mnesia_with_slave(),
              {atomic,ok} = mnesia:create_table(rec,[{type,set},
              ok          = mnesia:dirty_write({rec,4,1}),
              %% The next command MUST be done in a transaction, otherwise dirty_next works
              {atomic,ok} = mnesia:transaction(fun()->mnesia:delete_object({rec,4,1}) end),
              %% Here's the problem: dirty_next returns 4, but this key is not in the table!
              4           = mnesia:dirty_next(rec,0),
              []          = mnesia:dirty_read(rec,4).

          I'm starting mnesia and the slave node like this:

          start_mnesia_with_slave() ->
              {ok,Dir} = file:get_cwd(),
              ok = error_logger:tty(false),
              ok = error_logger:tty(true),
              ok = file:make_dir("mnesia"),
              ok = file:make_dir("slave"),
              Slave = slave(),
              ok = application:set_env(mnesia,dir,Dir++"/mnesia"),
              ok = rpc:call(Slave,application,set_env,[mnesia,dir,Dir++"/slave"]),
              ok = mnesia:create_schema([node(),Slave]),
              ok = mnesia:start(),
              ok = rpc:call(Slave,mnesia,start,[]),

          slave() ->
              case slave:start_link(net_adm:localhost(),"slave") of
           {ok,Slave} ->
           {error,{already_running,Slave}} ->

          I also have code to delete a file or directory, easy on Linux, darn difficult on Windows. You don't need this really, just run the test in an empty directory.

          delete_file(Name) ->
              case filelib:is_dir(Name) of
           true ->
               [delete_file(Name++"/"++X) || X <- list_dir(Name)],
           {error,eaccess} ->
           {error,enoent} ->
               io:format("Could not find ~p\n",[Name]),
           false ->
               case file:delete(Name) of
            {error,enoent} ->
            {error,eacces} ->
                io:format("Could not access ~p\n",[Name]),
            ok ->

          list_dir(Name) ->
              case file:list_dir(Name) of
           {ok,Files} ->
           {error,eacces} ->
               io:format("Could not list directory ~p\n",[Name]),
           {error,enoent} ->
               io:format("Could not find directory ~p\n",[Name]),


          erlang-bugs mailing list

        Best Regards,
        - Ahmed Omar 
        Follow me on twitter

    Best Regards,
    - Ahmed Omar
    Follow me on twitter

    erlang-bugs mailing list

  Ulf Wiger, CTO, Erlang Solutions, Ltd.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-bugs/attachments/20110518/f108ac12/attachment.html>

More information about the erlang-bugs mailing list