[Date Prev][Date Next] [Chronological] [Thread] [Top]

(ITS#7515) Nested liblmdb transaction bugs



Full_Name: Hallvard B Furuseth
Version: mdb.master 27aaecc744955d08d2bfe7a3ca786d742267c5bd
OS: Linux x86_64
URL: 
Submission from: (NULL) (193.69.163.163)
Submitted by: hallvard


Here are some crashes with nested liblmdb transactions.

The ./bug test program takes the following commands:
    R       Remove old database file, if any.
    [,],}   Begin/commit/abort txn - nested if already in a txn.
    O       Open main DB.
    c,o     Create/open named DB "db".
    p,g,d   Put/get/del {key="foo\0": data="bar\0"} to last opened DB.

$ ./bug  R[Op[d     # Put an item, delete it in a child txn
Bus Error

Replace mdb_page_get() with the version below, and it works.  But:

$ ./bug  R[Op[d]p   # As above, then put the item again in top txn
bug: mdb.c:3786: mdb_node_search: Assertion `nkeys > 0' failed.

#3  0x0000003911c2bae0 in __assert_fail () from /lib64/libc.so.6
#4  0x0000000000406c49 in mdb_node_search (mc=0x7fffffffde80, 
    key=0x7fffffffe070, exactp=0x7fffffffc934) at mdb.c:3786
#5  0x0000000000408846 in mdb_cursor_set (mc=0x7fffffffde80, 
    key=0x7fffffffe070, data=0x7fffffffc920, op=MDB_SET, exactp=0x7fffffffc934)
    at mdb.c:4472
#6  0x0000000000409c7a in mdb_cursor_put (mc=0x7fffffffde80, 
    key=0x7fffffffe070, data=0x7fffffffe060, flags=0) at mdb.c:4879
#7  0x00000000004109b4 in mdb_put (txn=0x615110, dbi=1, key=0x7fffffffe070, 
    data=0x7fffffffe060, flags=0) at mdb.c:6852
#8  0x00000000004016fb in main (argc=2, argv=0x7fffffffe1e8) at bug.c:39


Successful child txns can create broken output:

$ ./bug  R[O[p]]    # Put an item in a child txn.  Success, but:
$ ../mdb_stat -n -a -f test.mdb; du test.mdb
db_stat reports only zeroes, but the DB is 12 K.

$ ./bug  R[Op][[d]][g] # Put, delete in child, then Get incorrectly succeeds
$ ../mdb_stat -n -a -f test.mdb
<prints freelist status, then coredump>

$ ./bug  R[Op][d][g]   # The get fails as expected if the del is not in a child
40: mdb_get(txn, dbi, &key, &rdata): MDB_NOTFOUND: No matching key/data pair
found


Can't use a DBI from an aborted child txn, unlike aborted main txn:

$ ./bug  R[cp]      # Create a named DB and put an item there
$ ./bug  [[o}g]     # Open that DB in a child, abort, use the DBI
40: mdb_get(txn, dbi, &key, &rdata): Invalid argument
$ ./bug  [[o]g]     # Success: Commit instead of abort the child
$ ./bug  [o}[g]     # Success: Use the DBI from an aborted main txn


static int
mdb_page_get(MDB_txn *txn, pgno_t pgno, MDB_page **ret)
{
	MDB_page *p = NULL;

	if (!((txn->mt_flags & MDB_TXN_RDONLY) |
		  (txn->mt_env->me_flags & MDB_WRITEMAP)))
	{
		MDB_txn *tx2 = txn;
		do {
			MDB_ID2L dl = tx2->mt_u.dirty_list;
			if (dl[0].mid) {
				unsigned x = mdb_mid2l_search(dl, pgno);
				if (x <= dl[0].mid && dl[x].mid == pgno) {
					p = dl[x].mptr;
					goto done;
				}
			}
		} while ((tx2 = tx2->mt_parent) != NULL);
	}

	if (pgno < txn->mt_next_pgno) {
		p = (MDB_page *)(txn->mt_env->me_map + txn->mt_env->me_psize * pgno);
	} else {
		DPRINTF("page %zu not found", pgno);
		assert(p != NULL);
	}

done:
	*ret = p;
	return (p != NULL) ? MDB_SUCCESS : MDB_PAGE_NOTFOUND;
}


bug.c:
/* Commands:
 *  R       Remove old database file, if any.
 *  [,],}   Begin/commit/abort txn - nested if already in a txn.
 *  O       Open main DB.
 *  c,o     Create/open named DB "db".
 *  p,g,d   Put/get/del {key="foo\0": data="bar\0"} to last opened DB.
 */
#include <lmdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv) {
    char c, *cmd = argc < 2 ? "" : argv[1];
    int rc, ti = 0;
    MDB_txn *txn, *txs[9] = {NULL}; /* The loop maintains txn==txs[ti] */
    MDB_val key = {4, "foo"}, data = {4, "bar"}, rdata;
    MDB_env *env;
    MDB_dbi dbi;
#   define envname "test.mdb"
#   define E(e) { rc = (e); if (rc) { fprintf(stderr, "%d: %s: %s\n", \
        __LINE__, #e, mdb_strerror(rc)); abort(); } }

    if (*cmd == 'R') { cmd++; remove(envname); }
    E(mdb_env_create(&env));
    E(mdb_env_set_maxdbs(env, 1));
    E(mdb_env_open(env, envname, MDB_NOSYNC|MDB_NOSUBDIR, 0666));

#   define C(ch, expr) case ch: E(expr); break
    while (txn=txs[ti], c=*cmd++) switch (c) {
        C('[', mdb_txn_begin(env, txn, 0, &txs[++ti]));
        C(']', mdb_txn_commit(txs[ti--]));
        C('}', (mdb_txn_abort(txs[ti--]), 0));

        C('O', mdb_dbi_open(txn, NULL, 0,          &dbi));
        C('c', mdb_dbi_open(txn, "db", MDB_CREATE, &dbi));
        C('o', mdb_dbi_open(txn, "db", 0,          &dbi));

        C('p', mdb_put(txn, dbi, &key, &data, 0));
        C('g', mdb_get(txn, dbi, &key, &rdata));
        C('d', mdb_del(txn, dbi, &key, NULL));
    }
    if (txn) E(mdb_txn_commit(txs[1]));

    mdb_env_close(env);
    return 0;
}