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

(ITS#7238) Multiple txns in same thread break mdb



Full_Name: Hallvard B Furuseth
Version: 
OS: 
URL: 
Submission from: (NULL) (195.1.106.125)
Submitted by: hallvard


mdb.h says a transaction must only be used by a single thread,
but libmdb also assumes a thread only has one transaction:

If there have been some commits since the oldest txn, and
a new txn then starts in the same thread as an oldest txn,
the oldest txnid is overwritten in the reader table.

Writers can then modify pages the oldest txn still uses.

Example: This should print A, but prints AEFG.  It commits an "A",
starts a reader and reads the A, then several commits delete the
lowest letter and add two newer letter: -A +BC, -B +DE, -C +FG...
while starting new readers, then reads more with 1st reader.

#include "mdb.h"
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#define E(expr) assert(!(expr))

enum { Count = 6 };
static char dbname[] = "testdb.mdb";
static MDB_env *env;
static MDB_dbi dbi;
static pthread_barrier_t barrier;

static void *reader(void *arg) {
    int i, rc;
    MDB_txn *txns[Count];
    MDB_cursor *cursor;
    MDB_val key, data;

    pthread_barrier_wait(&barrier);
    E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txns[0]));
    E(mdb_cursor_open(txns[0], dbi, &cursor));
    E(mdb_cursor_get(cursor, &key, &data, MDB_NEXT));
    fputs((char *) key.mv_data, stdout);
    pthread_barrier_wait(&barrier);

    for (i = 1; i < Count; i++) {
        pthread_barrier_wait(&barrier);
        E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txns[i]));
        pthread_barrier_wait(&barrier);
    }

    while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0)
        fputs((char *) key.mv_data, stdout);
    puts(".");
    assert(rc == MDB_NOTFOUND);
    return arg;
}

int main(void) {
    char add[2] = "A", del[2] = "A";
    MDB_val key = {2, add}, dkey = {2, del}, data = {3, "Hi"};
    MDB_txn *txn;
    pthread_t tid;
    int i;

    unlink(dbname);
    E(mdb_env_create(&env));
    E(mdb_env_open(env, dbname, MDB_NOSUBDIR, 0660));

    E(mdb_txn_begin(env, NULL, 0, &txn));
    E(mdb_open(txn, NULL, 0, &dbi));
    E(mdb_put(txn, dbi, &key, &data, 0)); ++*add;
    E(mdb_txn_commit(txn));

    E(pthread_barrier_init(&barrier, NULL, 2));
    E(pthread_create(&tid, NULL, reader, NULL));
    pthread_barrier_wait(&barrier);
        
    for (i = 1; i < Count; i++) {
        pthread_barrier_wait(&barrier);
        E(mdb_txn_begin(env, NULL, 0, &txn));
        E(mdb_del(txn, dbi, &dkey, NULL   )); ++*del;
        E(mdb_put(txn, dbi, &key, &data, 0)); ++*add;
        E(mdb_put(txn, dbi, &key, &data, 0)); ++*add;
        E(mdb_txn_commit(txn));
        pthread_barrier_wait(&barrier);
    }

    pthread_barrier_wait(&barrier);
    E(pthread_join(tid, NULL));
    return 0;
}

I have not looked at what happens if some of the readers are
in the same thread as the writer.