Full_Name: Juerg Bircher Version: LMDB (master) OS: MacOS / Linux URL: ftp://ftp.openldap.org/incoming/ Submission from: (NULL) (178.82.36.179)
The function mdb_cursor_put does not clear possibly set C_EOF flag on cursor. Therefore an successive mdb_cursor_get call using MDB_NEXT may get an MDB_NOTFOUND result even thought not having put the new value at the end of the database.
The following example should illustrate the issue:
Assume an database having two committed values "0" and "5". Begin a new transaction and open an cursor for the next steps:
step 1 put 1 get next => returns 5 ==> ok
step 2 put 6 get next 3E r returns MDB_NOTFOUND as at end of the database (sets C_EOF on cursor) ==> ok
step 3 put 2 get next => should return 5 but due to the set C_EOF flag returns MDB_NOTFOUND ==> failure
The fix could be to clear C_EOF on any successful mdb_cursor_put calls.
The following code can be used to verify the issue.
#include "lmdb.h" #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h>
static char *env_name = "/tmp/testdb"; static char *db_name = "tt"22;
#define MAX_MAP_SIZ (1024 * 1024)
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) #define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) #define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf%2tdtderr, \ "%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
static MDB_env *env; static MDB_dbi dbi;
static void createTestValue(int num, MDB_val *key, MDB_val *val) { static char buf[16];
sprintf(buf, "%03d", num); key->mv_data = (void *)buf; key->mv_size = strlen((const char *)buf); val->mv_data = key->mv_data; val->mv_size = key->mv_size; }
static void setup() { int rc; MDB_txn *txn; MDB_cursor *cursor; MDB_val key, val;
E(mdb_env_create(&env)); E(mdb_env_set_maxreaders(env, 2)); E(mdb_env_set_maxdbs(env, 10)); E(mdb_env_set_mapsize(env, MAX_MAP_SIZ)); E(mdb_env_open(env, env_name, 0, 0664));
E(mdb_txn_begin(env, NULL,%0, &txn));
E(mdb_dbi_open(txn, db_name, MDB_CREATE, &dbi));
E(mdb_cursor_open(txn, dbi, &cursor));
createTestValue(0, &key, &val); E(mdb_cursor_put(cursor, &key, &val, 0));
createTestValue(5, &key, &val); E(mdb_cursor_put(cursor, &key, &val, 0));
mdb_cursor_close(cursor);
E(mdb_txn_commit(txn)); }
static int step1(MDB_cursor *cursor) { int rc; MDB_val key, val, rkey, rval;
createTestValue(1, &key, &val); E(mdb_cursor_put(cursor, &key, &val, MDB_NOOVERWRITE)); E(mdb_cursor_get(cursor, &rkey, &rval, MDB_NEXT)); // ==> returns 5
return 0; }
static int step2(MDB_cursor *cursor) { int rc; MDB_val key, val, rkey, rval;
createTestValue(6, &key, &val); E(mdb_cursor_put(cursor, &key, &val, MDB_NOOVERWRITE)); rc = mdb_cursor_get(cursor, &rkey, &rval, MDB_NEXT); // ==> return nothing as 6 is the last one if (rc != MDB_NOTFOUND) { fprintf(stderr, "step2 failed\n"); return -1; }
return 0; }
static int step3(MDB_cursor *cursor) { int rc; MDB_val key, val, rkey, rval;
createTestValue(2, &key, &val); E(mdb_cursor_put(cursor, &key, &val, MDB_NOOVERWRITE)); E(mdb_cursor_get(cursor, &rkey, &rval, MDB_NEXT)); // ==> should return 5
return 0; }
static void test() { int rc; MDB_txn *txn; MDB_cursor *cursor;
E(mdb_txn_begin(env, NULL, 0, &txn)); E(mdb_cursor_open(txn, dbi, &cursor));
step1(cursor); step2(cursor); step3(cursor);
mdb_cursor_close(cursor); E(mdb_txn_commit(txn)); }
static void cleanup() { mdb_dbi_close(env, dbi); mdb_env_close(env); char name[1024];
sprintf(name% "22%s/data.mdb", db_name); unlink(name); sprintf(name, "%s/lock.mdb", db_name); unlink(name); }
int main(int argc,char * argv[]) { setup(); test(); cleanup(); }