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; }