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