MDB can act on P_DIRTY without checking whether the page is dirty in this txn's dirty_list, or only some ancestor txn's:
* mdb_cursor_put() line 5094, overwriting an F_BIGDATA node: Let a nested txn replace a big item whose page is dirty only in an ancestor txn. The parent will see the new value if the old page had room for it, even if the child aborted. Test program enclosed.
Proposed fix: No need to search dirty_list, mdb_page_get() just did. Make it return where the page was found instead of an error code: <0 not found, 0 in map, >0 txn nesting level of the dirty_list with the page. Branch mdb/its7515 in http://folk.uio.no/hbf/OpenLDAP/openldap.git, except the commit message may need tweaks.
* mdb_xcursor_init1(): Sets DB_DIRTY if mc_top has P_DIRTY. Seems to cancel some MDB_PS_MODIFY activity. Is that OK?
#include <lmdb.h> #include <stdio.h>
int main(void) { static char pTxt[9999] = "parent", cTxt[9999] = "child"; MDB_val key = {1,""}, pVal = {9999,pTxt}, cVal = {9999,cTxt}, val; MDB_env *env; MDB_txn *txn, *ctxn; MDB_dbi dbi; mdb_env_create(&env); mdb_env_open(env, "test.mdb", MDB_NOSUBDIR, 0666); mdb_txn_begin(env, 0, 0, &txn); mdb_dbi_open(txn, 0, 0, &dbi); mdb_put(txn, dbi, &key, &pVal, 0); /* put "parent" */ mdb_txn_begin(env, txn, 0, &ctxn); mdb_put(ctxn, dbi, &key, &cVal, 0); /* overwrite with "child" */ mdb_txn_abort(ctxn); mdb_get(txn, dbi, &key, &val); /* should get "parent" */ puts(val.mv_data); /* ...but got "child" */ mdb_txn_commit(txn); mdb_env_close(env); return 0; }