lmdb: bigbang for sync-to-disk path and related.

This is 4/9 for https://github.com/ReOpen/ReOpenLDAP/issues/1
and https://github.com/ReOpen/ReOpenLDAP/issues/2

Here a lot of changes:
 - deleted a secondary DSYNC-mode file descriptor;
 - deleted a pointers to meta-pages and toggle-selection of ones;
 - removed MDB_FDATASYNC_WORKS/FDATASYNC_MAYBE_BROKEN stuff;
 - reworked use of fdatasync/fsync for safety without performance degradation;
 - txn-to-meta updating moved info mdb_txn_commit();
 - removed mdb_env_write_meta();
 - rewrited mdb_env_sync0() for clarify;

Change-Id: I985464baa3cf486a2ceccf2c9dcb7a7fea698c46
This commit is contained in:
Leo Yuriev 2015-05-09 01:42:46 +03:00
parent 1c37139b2a
commit a283d782f6

579
mdb.c
View File

@ -188,15 +188,6 @@ static MDB_INLINE void mdb_invalidate_cache(void *addr, int nbytes) {
/*****************************************************************************/ /*****************************************************************************/
#if defined(__linux) && !defined(MDB_FDATASYNC_WORKS)
/** fdatasync is broken on ext3/ext4fs on older kernels, see
* description in #mdb_env_open2 comments. You can safely
* define MDB_FDATASYNC_WORKS if this code will only be run
* on kernels 3.6 and newer.
*/
# define FDATASYNC_MAYBE_BROKEN
#endif
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <stddef.h> #include <stddef.h>
@ -321,36 +312,6 @@ static int mdb_mutex_lock(MDB_env *env, pthread_mutex_t *mutex);
static int mdb_mutex_failed(MDB_env *env, pthread_mutex_t *mutex, int rc); static int mdb_mutex_failed(MDB_env *env, pthread_mutex_t *mutex, int rc);
static void mdb_mutex_unlock(MDB_env *env, pthread_mutex_t *mutex); static void mdb_mutex_unlock(MDB_env *env, pthread_mutex_t *mutex);
/** A flag for opening a file and requesting synchronous data writes.
* This is only used when writing a meta page. It's not strictly needed;
* we could just do a normal write and then immediately perform a flush.
* But if this flag is available it saves us an extra system call.
*/
#ifdef O_DSYNC
# define MDB_DSYNC O_DSYNC
#else
# define MDB_DSYNC O_SYNC
#endif
/** Function for flushing the data of a file. Define this to fsync
* if fdatasync() is not supported.
*/
#ifndef MDB_FDATASYNC
# define MDB_FDATASYNC fdatasync
#endif
#ifndef MDB_MSYNC
# define MDB_MSYNC(addr,len,flags) msync(addr,len,flags)
#endif
#ifndef MS_SYNC
# define MS_SYNC 1
#endif
#ifndef MS_ASYNC
# define MS_ASYNC 0
#endif
/** A page number in the database. /** A page number in the database.
* Note that 64 bit page numbers are overkill, since pages themselves * Note that 64 bit page numbers are overkill, since pages themselves
* already represent 12-13 bits of addressable memory, and the OS will * already represent 12-13 bits of addressable memory, and the OS will
@ -1054,17 +1015,12 @@ typedef struct MDB_pgstate {
struct MDB_env { struct MDB_env {
HANDLE me_fd; /**< The main data file */ HANDLE me_fd; /**< The main data file */
HANDLE me_lfd; /**< The lock file */ HANDLE me_lfd; /**< The lock file */
HANDLE me_mfd; /**< just for writing the meta pages */
/** Failed to update the meta page. Probably an I/O error. */ /** Failed to update the meta page. Probably an I/O error. */
#define MDB_FATAL_ERROR 0x80000000U #define MDB_FATAL_ERROR 0x80000000U
/** Some fields are initialized. */ /** Some fields are initialized. */
#define MDB_ENV_ACTIVE 0x20000000U #define MDB_ENV_ACTIVE 0x20000000U
/** me_txkey is set */ /** me_txkey is set */
#define MDB_ENV_TXKEY 0x10000000U #define MDB_ENV_TXKEY 0x10000000U
#ifdef FDATASYNC_MAYBE_BROKEN
/** fdatasync may be unreliable */
# define MDB_BROKEN_DATASYNC 0x08000000U
#endif /* FDATASYNC_MAYBE_BROKEN */
uint32_t me_flags; /**< @ref mdb_env */ uint32_t me_flags; /**< @ref mdb_env */
unsigned me_psize; /**< DB page size, inited from me_os_psize */ unsigned me_psize; /**< DB page size, inited from me_os_psize */
unsigned me_os_psize; /**< OS page size, from #GET_PAGESIZE */ unsigned me_os_psize; /**< OS page size, from #GET_PAGESIZE */
@ -1077,7 +1033,6 @@ struct MDB_env {
char *me_path; /**< path to the DB files */ char *me_path; /**< path to the DB files */
char *me_map; /**< the memory map of the data file */ char *me_map; /**< the memory map of the data file */
MDB_txninfo *me_txns; /**< the memory map of the lock file */ MDB_txninfo *me_txns; /**< the memory map of the lock file */
MDB_meta *me_metas[2]; /**< pointers to the two meta pages */
void *me_pbuf; /**< scratch area for DUPSORT put() */ void *me_pbuf; /**< scratch area for DUPSORT put() */
MDB_txn *me_txn; /**< current write transaction */ MDB_txn *me_txn; /**< current write transaction */
MDB_txn *me_txn0; /**< prealloc'd write transaction */ MDB_txn *me_txn0; /**< prealloc'd write transaction */
@ -1110,9 +1065,6 @@ struct MDB_env {
#endif #endif
uint64_t me_sync_pending; /**< Total dirty/commited bytes since the last mdb_env_sync() */ uint64_t me_sync_pending; /**< Total dirty/commited bytes since the last mdb_env_sync() */
uint64_t me_sync_threshold; /**< Treshold of above to force synchronous flush */ uint64_t me_sync_threshold; /**< Treshold of above to force synchronous flush */
#ifdef FDATASYNC_MAYBE_BROKEN
size_t me_sync_size; /**< Tracking file size at last sync to decide when fsync() is needed */
#endif /* FDATASYNC_MAYBE_BROKEN */
MDB_oom_func *me_oom_func; /**< Callback for kicking laggard readers */ MDB_oom_func *me_oom_func; /**< Callback for kicking laggard readers */
#ifdef USE_VALGRIND #ifdef USE_VALGRIND
int me_valgrind_handle; int me_valgrind_handle;
@ -1143,6 +1095,12 @@ typedef struct MDB_ntxn {
#define TXN_DBI_CHANGED(txn, dbi) \ #define TXN_DBI_CHANGED(txn, dbi) \
((txn)->mt_dbiseqs[dbi] != (txn)->mt_env->me_dbiseqs[dbi]) ((txn)->mt_dbiseqs[dbi] != (txn)->mt_env->me_dbiseqs[dbi])
#define METAPAGE_1(env) \
(&((MDB_metabuf*) (env)->me_map)->mb_metabuf.mm_meta)
#define METAPAGE_2(env) \
(&((MDB_metabuf*) ((env)->me_map + env->me_psize))->mb_metabuf.mm_meta)
static int mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp); static int mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp);
static int mdb_page_new(MDB_cursor *mc, uint32_t flags, int num, MDB_page **mp); static int mdb_page_new(MDB_cursor *mc, uint32_t flags, int num, MDB_page **mp);
static int mdb_page_touch(MDB_cursor *mc); static int mdb_page_touch(MDB_cursor *mc);
@ -1163,8 +1121,7 @@ static int mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata,
pgno_t newpgno, unsigned nflags); pgno_t newpgno, unsigned nflags);
static int mdb_env_read_header(MDB_env *env, MDB_meta *meta); static int mdb_env_read_header(MDB_env *env, MDB_meta *meta);
static int mdb_env_pick_meta(const MDB_env *env); static int mdb_env_sync0(MDB_env *env, unsigned flags, MDB_meta *pending);
static int mdb_env_write_meta(MDB_txn *txn, int force);
static void mdb_env_close0(MDB_env *env); static void mdb_env_close0(MDB_env *env);
static MDB_node *mdb_node_search(MDB_cursor *mc, MDB_val *key, int *exactp); static MDB_node *mdb_node_search(MDB_cursor *mc, MDB_val *key, int *exactp);
@ -1858,8 +1815,9 @@ mdb_page_spill(MDB_cursor *m0, MDB_val *key, MDB_val *data)
} }
/* Preserve pages which may soon be dirtied again */ /* Preserve pages which may soon be dirtied again */
if ((rc = mdb_pages_xkeep(m0, P_DIRTY, 1)) != MDB_SUCCESS) rc = mdb_pages_xkeep(m0, P_DIRTY, 1);
goto done; if (unlikely(rc != MDB_SUCCESS))
goto bailout;
/* Less aggressive spill - we originally spilled the entire dirty list, /* Less aggressive spill - we originally spilled the entire dirty list,
* with a few exceptions for cursor pages and DB root pages. But this * with a few exceptions for cursor pages and DB root pages. But this
@ -1895,24 +1853,41 @@ mdb_page_spill(MDB_cursor *m0, MDB_val *key, MDB_val *data)
if (tx2) if (tx2)
continue; continue;
} }
if ((rc = mdb_midl_append(&txn->mt_spill_pgs, pn))) rc = mdb_midl_append(&txn->mt_spill_pgs, pn);
goto done; if (unlikely(rc != MDB_SUCCESS))
goto bailout;
need--; need--;
} }
mdb_midl_sort(txn->mt_spill_pgs); mdb_midl_sort(txn->mt_spill_pgs);
/* Flush the spilled part of dirty list */ /* Flush the spilled part of dirty list */
if ((rc = mdb_page_flush(txn, i)) != MDB_SUCCESS) rc = mdb_page_flush(txn, i);
goto done; if (unlikely(rc != MDB_SUCCESS))
goto bailout;
/* Reset any dirty pages we kept that page_flush didn't see */ /* Reset any dirty pages we kept that page_flush didn't see */
rc = mdb_pages_xkeep(m0, P_DIRTY|P_KEEP, i); rc = mdb_pages_xkeep(m0, P_DIRTY|P_KEEP, i);
done: bailout:
txn->mt_flags |= rc ? MDB_TXN_ERROR : MDB_TXN_SPILLS; txn->mt_flags |= rc ? MDB_TXN_ERROR : MDB_TXN_SPILLS;
return rc; return rc;
} }
/** Check both meta pages to see which one is newer.
* @param[in] env the environment handle
* @return pointer to last meta-page.
*/
static MDB_meta*
mdb_env_meta_head(const MDB_env *env) {
MDB_meta* a = METAPAGE_1(env);
MDB_meta* b = METAPAGE_2(env);
return (a->mm_txnid > b->mm_txnid) ? a : b;
}
static MDB_meta* mdb_env_meta_flipflop(const MDB_env *env, MDB_meta* meta) {
return (meta == METAPAGE_1(env)) ? METAPAGE_2(env) : METAPAGE_1(env);
}
/** Find oldest txnid still referenced. */ /** Find oldest txnid still referenced. */
static txnid_t static txnid_t
mdb_find_oldest(MDB_env *env, int *laggard) mdb_find_oldest(MDB_env *env, int *laggard)
@ -1967,7 +1942,7 @@ mdb_oomkick_laggard(MDB_env *env)
continue; continue;
rc = env->me_oom_func(env, pid, (void*) tid, oldest, rc = env->me_oom_func(env, pid, (void*) tid, oldest,
env->me_metas[ mdb_env_pick_meta(env) ]->mm_txnid - oldest, retry); mdb_env_meta_head(env)->mm_txnid - oldest, retry);
if (rc < 0) if (rc < 0)
break; break;
@ -2481,79 +2456,56 @@ fail:
return rc; return rc;
} }
static int
mdb_env_sync0(MDB_env *env, int *force)
{
int rc = 0;
if (env->me_flags & MDB_RDONLY)
return EACCES;
if (env->me_sync_threshold && env->me_sync_pending >= env->me_sync_threshold)
*force = 1;
if (*force || !F_ISSET(env->me_flags, MDB_NOSYNC)) {
if (env->me_flags & MDB_WRITEMAP) {
int mode = (!*force && (env->me_flags & MDB_MAPASYNC)) ? MS_ASYNC : MS_SYNC;
/* LY: skip meta-pages, sync ones explicit later */
size_t data_offset = (env->me_psize * 2 + env->me_os_psize - 1) & ~(env->me_os_psize - 1);
if (MDB_MSYNC(env->me_map + data_offset, env->me_mapsize - data_offset, mode))
rc = errno;
} else {
/* (LY) TODO: sync_file_range() for data and later fdatasync() for meta,
ALSO sync_file_range() needed before calling fsync().
*/
#ifdef FDATASYNC_MAYBE_BROKEN
if (env->me_sync_size != env->me_mapsize && (env->me_flags & MDB_BROKEN_DATASYNC)) {
if (fsync(env->me_fd))
rc = errno;
else
env->me_sync_size = env->me_mapsize;
} else
#endif /* FDATASYNC_MAYBE_BROKEN */
if (MDB_FDATASYNC(env->me_fd))
rc = errno;
}
if (! rc)
env->me_sync_pending = 0;
}
return rc;
}
int int
mdb_env_sync(MDB_env *env, int force) mdb_env_sync(MDB_env *env, int force)
{ {
MDB_meta *meta; int rc;
txnid_t checkpoint; pthread_mutex_t *mutex;
int rc, lockfree_countdown = 3; MDB_meta *head;
pthread_mutex_t *mutex = NULL; unsigned flags = env->me_flags & ~MDB_NOMETASYNC;
if (env->me_flags & MDB_RDONLY) if (unlikely(flags & (MDB_RDONLY | MDB_FATAL_ERROR)))
return 0; return EACCES;
do { head = mdb_env_meta_head(env);
if (!mutex && --lockfree_countdown == 0) { if (force || head->mm_mapsize != env->me_mapsize)
mutex = MDB_MUTEX(env, w); flags &= MDB_WRITEMAP;
rc = mdb_mutex_lock(env, mutex);
if (unlikely(rc))
return rc;
}
meta = env->me_metas[ mdb_env_pick_meta(env) ]; /* LY: just only for 'early sync' to reduce writer latency */
checkpoint = meta->mm_txnid; if (env->me_sync_threshold && env->me_sync_pending >= env->me_sync_threshold)
flags &= MDB_WRITEMAP;
/* first sync data. */ if ((flags & MDB_NOSYNC) == 0) {
rc = mdb_env_sync0(env, &force); /* LY: early sync before acquiring the mutex,
* this reduces latency for writer */
if (flags & MDB_WRITEMAP) {
if (msync(env->me_map, env->me_mapsize,
(flags & MDB_MAPASYNC) ? MS_ASYNC : MS_SYNC))
return errno;
} else if (fdatasync(env->me_fd))
return errno;
/* LY: head may be changed during the sync. */
head = mdb_env_meta_head(env);
}
/* then sync meta-pages. */ if (env->me_sync_pending == 0 && env->me_mapsize == head->mm_mapsize)
if (rc == 0 && (env->me_flags & MDB_WRITEMAP)) { /* LY: nothing to do */
int mode = (!force && (env->me_flags & MDB_MAPASYNC)) ? MS_ASYNC : MS_SYNC; return MDB_SUCCESS;
if (MDB_MSYNC(env->me_map, env->me_psize * 2, mode))
rc = errno;
}
} while (rc == 0 && checkpoint != meta->mm_txnid);
if (mutex) mutex = MDB_MUTEX(env, w);
mdb_mutex_unlock(env, mutex); rc = mdb_mutex_lock(env, mutex);
if (unlikely(rc))
return rc;
/* LY: head may be changed while the mutex has been acquired. */
head = mdb_env_meta_head(env);
rc = MDB_SUCCESS;
if (env->me_sync_pending || env->me_mapsize != head->mm_mapsize) {
MDB_meta meta = *head;
rc = mdb_env_sync0(env, flags, &meta);
}
mdb_mutex_unlock(env, mutex);
return rc; return rc;
} }
@ -2739,7 +2691,7 @@ mdb_txn_renew0(MDB_txn *txn)
} }
do { /* LY: Retry on a race, ITS#7970. */ do { /* LY: Retry on a race, ITS#7970. */
meta = env->me_metas[ mdb_env_pick_meta(env) ]; meta = mdb_env_meta_head(env);
r->mr_txnid = meta->mm_txnid; r->mr_txnid = meta->mm_txnid;
mdb_coherent_barrier(); mdb_coherent_barrier();
} while(unlikely(r->mr_txnid != env->me_txns->mti_txnid)); } while(unlikely(r->mr_txnid != env->me_txns->mti_txnid));
@ -2751,7 +2703,7 @@ mdb_txn_renew0(MDB_txn *txn)
if (unlikely(rc)) if (unlikely(rc))
return rc; return rc;
meta = env->me_metas[ mdb_env_pick_meta(env) ]; meta = mdb_env_meta_head(env);
txn->mt_txnid = meta->mm_txnid; txn->mt_txnid = meta->mm_txnid;
/* Setup db info */ /* Setup db info */
@ -3000,7 +2952,7 @@ mdb_txn_straggler(MDB_txn *txn, int *percent)
return -1; return -1;
env = txn->mt_env; env = txn->mt_env;
meta = env->me_metas[ mdb_env_pick_meta(env) ]; meta = mdb_env_meta_head(env);
if (percent) { if (percent) {
long cent = env->me_maxpg / 100; long cent = env->me_maxpg / 100;
long last = env->me_txn ? env->me_txn0->mt_next_pgno : meta->mm_last_pg; long last = env->me_txn ? env->me_txn0->mt_next_pgno : meta->mm_last_pg;
@ -3489,14 +3441,14 @@ mdb_page_flush(MDB_txn *txn, int keep)
/* Write up to MDB_COMMIT_PAGES dirty pages at a time. */ /* Write up to MDB_COMMIT_PAGES dirty pages at a time. */
if (pos!=next_pos || n==MDB_COMMIT_PAGES || wsize+size>MAX_WRITE) { if (pos!=next_pos || n==MDB_COMMIT_PAGES || wsize+size>MAX_WRITE) {
if (n) { if (n) {
retry_write: retry:
/* Write previous page(s) */ /* Write previous page(s) */
wres = pwritev(env->me_fd, iov, n, wpos); wres = pwritev(env->me_fd, iov, n, wpos);
if (wres != wsize) { if (unlikely(wres != wsize)) {
if (wres < 0) { if (wres < 0) {
rc = errno; rc = errno;
if (rc == EINTR) if (rc == EINTR)
goto retry_write; goto retry;
mdb_debug("Write error: %s", strerror(rc)); mdb_debug("Write error: %s", strerror(rc));
} else { } else {
rc = EIO; /* TODO: Use which error code? */ rc = EIO; /* TODO: Use which error code? */
@ -3542,23 +3494,21 @@ done:
int int
mdb_txn_commit(MDB_txn *txn) mdb_txn_commit(MDB_txn *txn)
{ {
int rc, force = 0; int rc;
unsigned i; unsigned i;
MDB_env *env; MDB_env *env;
if (txn == NULL || txn->mt_env == NULL) if (unlikely(txn == NULL || txn->mt_env == NULL))
return EINVAL; return EINVAL;
if (txn->mt_child) { if (txn->mt_child) {
rc = mdb_txn_commit(txn->mt_child); rc = mdb_txn_commit(txn->mt_child);
txn->mt_child = NULL; txn->mt_child = NULL;
if (rc) if (unlikely(rc != MDB_SUCCESS))
goto fail; goto fail;
} }
env = txn->mt_env; if (unlikely(F_ISSET(txn->mt_flags, MDB_TXN_RDONLY))) {
if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) {
mdb_dbis_update(txn, 1); mdb_dbis_update(txn, 1);
txn->mt_numdbs = 2; /* so txn_abort() doesn't close any new handles */ txn->mt_numdbs = 2; /* so txn_abort() doesn't close any new handles */
mdb_txn_abort(txn); mdb_txn_abort(txn);
@ -3584,7 +3534,7 @@ mdb_txn_commit(MDB_txn *txn)
if (txn->mt_lifo_reclaimed) { if (txn->mt_lifo_reclaimed) {
if (parent->mt_lifo_reclaimed) { if (parent->mt_lifo_reclaimed) {
rc = mdb_midl_append_list(&parent->mt_lifo_reclaimed, txn->mt_lifo_reclaimed); rc = mdb_midl_append_list(&parent->mt_lifo_reclaimed, txn->mt_lifo_reclaimed);
if (rc) if (unlikely(rc != MDB_SUCCESS))
goto fail; goto fail;
mdb_midl_free(txn->mt_lifo_reclaimed); mdb_midl_free(txn->mt_lifo_reclaimed);
} else } else
@ -3594,7 +3544,7 @@ mdb_txn_commit(MDB_txn *txn)
/* Append our free list to parent's */ /* Append our free list to parent's */
rc = mdb_midl_append_list(&parent->mt_free_pgs, txn->mt_free_pgs); rc = mdb_midl_append_list(&parent->mt_free_pgs, txn->mt_free_pgs);
if (rc) if (unlikely(rc != MDB_SUCCESS))
goto fail; goto fail;
mdb_midl_free(txn->mt_free_pgs); mdb_midl_free(txn->mt_free_pgs);
/* Failures after this must either undo the changes /* Failures after this must either undo the changes
@ -3676,7 +3626,7 @@ mdb_txn_commit(MDB_txn *txn)
if (parent->mt_spill_pgs) { if (parent->mt_spill_pgs) {
/* TODO: Prevent failure here, so parent does not fail */ /* TODO: Prevent failure here, so parent does not fail */
rc = mdb_midl_append_list(&parent->mt_spill_pgs, txn->mt_spill_pgs); rc = mdb_midl_append_list(&parent->mt_spill_pgs, txn->mt_spill_pgs);
if (rc) if (unlikely(rc != MDB_SUCCESS))
parent->mt_flags |= MDB_TXN_ERROR; parent->mt_flags |= MDB_TXN_ERROR;
mdb_midl_free(txn->mt_spill_pgs); mdb_midl_free(txn->mt_spill_pgs);
mdb_midl_sort(parent->mt_spill_pgs); mdb_midl_sort(parent->mt_spill_pgs);
@ -3697,7 +3647,8 @@ mdb_txn_commit(MDB_txn *txn)
return rc; return rc;
} }
if (txn != env->me_txn) { env = txn->mt_env;
if (unlikely(txn != env->me_txn)) {
mdb_debug("attempt to commit unknown transaction"); mdb_debug("attempt to commit unknown transaction");
rc = EINVAL; rc = EINVAL;
goto fail; goto fail;
@ -3722,20 +3673,20 @@ mdb_txn_commit(MDB_txn *txn)
mdb_cursor_init(&mc, txn, MAIN_DBI, NULL); mdb_cursor_init(&mc, txn, MAIN_DBI, NULL);
for (i = 2; i < txn->mt_numdbs; i++) { for (i = 2; i < txn->mt_numdbs; i++) {
if (txn->mt_dbflags[i] & DB_DIRTY) { if (txn->mt_dbflags[i] & DB_DIRTY) {
if (TXN_DBI_CHANGED(txn, i)) { if (unlikely(TXN_DBI_CHANGED(txn, i))) {
rc = MDB_BAD_DBI; rc = MDB_BAD_DBI;
goto fail; goto fail;
} }
data.mv_data = &txn->mt_dbs[i]; data.mv_data = &txn->mt_dbs[i];
rc = mdb_cursor_put(&mc, &txn->mt_dbxs[i].md_name, &data, 0); rc = mdb_cursor_put(&mc, &txn->mt_dbxs[i].md_name, &data, 0);
if (rc) if (unlikely(rc != MDB_SUCCESS))
goto fail; goto fail;
} }
} }
} }
rc = mdb_freelist_save(txn); rc = mdb_freelist_save(txn);
if (rc) if (unlikely(rc != MDB_SUCCESS))
goto fail; goto fail;
mdb_midl_free(env->me_pghead); mdb_midl_free(env->me_pghead);
@ -3746,9 +3697,18 @@ mdb_txn_commit(MDB_txn *txn)
if (mdb_audit_enabled()) if (mdb_audit_enabled())
mdb_audit(txn); mdb_audit(txn);
if ((rc = mdb_page_flush(txn, 0)) || rc = mdb_page_flush(txn, 0);
(rc = mdb_env_sync0(env, &force)) || if (likely(rc == MDB_SUCCESS)) {
(rc = mdb_env_write_meta(txn, force))) MDB_meta meta;
meta.mm_dbs[0] = txn->mt_dbs[0];
meta.mm_dbs[1] = txn->mt_dbs[1];
meta.mm_last_pg = txn->mt_next_pgno - 1;
meta.mm_txnid = txn->mt_txnid;
rc = mdb_env_sync0(env, env->me_flags | txn->mt_flags, &meta);
}
if (unlikely(rc != MDB_SUCCESS))
goto fail; goto fail;
/* Free P_LOOSE pages left behind in dirty_list */ /* Free P_LOOSE pages left behind in dirty_list */
@ -3763,7 +3723,6 @@ done:
mdb_mutex_unlock(env, MDB_MUTEX(env, w)); mdb_mutex_unlock(env, MDB_MUTEX(env, w));
if (txn != env->me_txn0) if (txn != env->me_txn0)
free(txn); free(txn);
return MDB_SUCCESS; return MDB_SUCCESS;
fail: fail:
@ -3887,119 +3846,144 @@ mdb_env_init_meta(MDB_env *env, MDB_meta *meta)
return rc; return rc;
} }
/** Update the environment info to commit a transaction.
* @param[in] txn the transaction that's being committed
* @return 0 on success, non-zero on failure.
*/
static int static int
mdb_env_write_meta(MDB_txn *txn, int force) mdb_env_sync0(MDB_env *env, unsigned flags, MDB_meta *pending)
{ {
MDB_env *env; int rc;
MDB_meta meta, metab, *mp; MDB_meta* head = mdb_env_meta_head(env);
int syncflush; MDB_meta* tail = mdb_env_meta_flipflop(env, head);
size_t mapsize; off_t offset = (char*) tail - env->me_map;
off_t off;
int rc, len, toggle;
char *ptr;
HANDLE mfd;
toggle = txn->mt_txnid & 1; mdb_assert(env, (env->me_flags & (MDB_RDONLY | MDB_FATAL_ERROR)) == 0);
mdb_debug("writing meta page %d for root page %zu", mdb_assert(env, env->me_sync_pending != 0 || env->me_mapsize != head->mm_mapsize);
toggle, txn->mt_dbs[MAIN_DBI].md_root); mdb_assert(env, pending->mm_txnid > head->mm_txnid);
mdb_assert(env, pending->mm_txnid > tail->mm_txnid);
env = txn->mt_env; pending->mm_mapsize = env->me_mapsize;
syncflush = force || if (unlikely(pending->mm_mapsize != head->mm_mapsize)) {
! ((txn->mt_flags | env->me_flags) & (MDB_NOMETASYNC | MDB_NOSYNC)); if (pending->mm_mapsize < head->mm_mapsize) {
mp = env->me_metas[toggle]; /* LY: currently this can't happen, but force full-sync. */
mapsize = env->me_metas[toggle ^ 1]->mm_mapsize; flags &= MDB_WRITEMAP;
/* Persist any increases of mapsize config */ } else {
if (mapsize < env->me_mapsize) /* Persist any increases of mapsize config */
mapsize = env->me_mapsize; }
}
if (env->me_flags & MDB_WRITEMAP) { if (env->me_sync_threshold && env->me_sync_pending >= env->me_sync_threshold)
mp->mm_mapsize = mapsize; flags &= MDB_WRITEMAP;
mp->mm_dbs[0] = txn->mt_dbs[0];
mp->mm_dbs[1] = txn->mt_dbs[1]; /* LY: step#1 - sync previously written/updated data-pages */
mp->mm_last_pg = txn->mt_next_pgno - 1; if (env->me_sync_pending && (flags & MDB_NOSYNC) == 0) {
/* (LY) ITS#7969: issue a memory barrier, it is noop for x86. */ if (env->me_flags & MDB_WRITEMAP) {
mdb_coherent_barrier(); int mode = (flags & MDB_MAPASYNC) ? MS_ASYNC : MS_SYNC;
mp->mm_txnid = txn->mt_txnid; if (unlikely(msync(env->me_map, pending->mm_mapsize, mode))) {
if ( syncflush ) {
unsigned meta_size = env->me_psize;
int mode = (!force && (env->me_flags & MDB_MAPASYNC)) ? MS_ASYNC : MS_SYNC;
ptr = env->me_map;
if (toggle) {
/* POSIX msync() requires ptr = start of OS page */
if (meta_size < env->me_os_psize)
meta_size += meta_size;
else
ptr += meta_size;
}
if (MDB_MSYNC(ptr, meta_size, mode)) {
rc = errno; rc = errno;
goto fail; goto fail;
} }
if ((flags & MDB_MAPASYNC) == 0)
env->me_sync_pending = 0;
} else {
int (*sync_fd)(int fd) = fdatasync;
if (unlikely(head->mm_mapsize != pending->mm_mapsize)) {
/* LY: It is no reason to use fdatasync() here, even in case
* no such bug in a kernel. Because "no-bug" mean that a kernel
* internally do nearly the same.
*
* So, this code is always safe and without appreciable
* performance degradation.
*
* For more info about of a corresponding fdatasync() bug
* see http://www.spinics.net/lists/linux-ext4/msg33714.html */
sync_fd = fsync;
}
while(unlikely(sync_fd(env->me_fd) < 0)) {
rc = errno;
if (rc != EINTR)
goto undo;
}
env->me_sync_pending = 0;
} }
goto done;
} }
metab.mm_txnid = env->me_metas[toggle]->mm_txnid;
metab.mm_last_pg = env->me_metas[toggle]->mm_last_pg;
meta.mm_mapsize = mapsize; /* LY: step#2 - update meta-page. */
meta.mm_dbs[0] = txn->mt_dbs[0]; mdb_debug("writing meta page %d for root page %zu",
meta.mm_dbs[1] = txn->mt_dbs[1]; offset >= env->me_psize, pending->mm_dbs[MAIN_DBI].md_root);
meta.mm_last_pg = txn->mt_next_pgno - 1; if (env->me_flags & MDB_WRITEMAP) {
meta.mm_txnid = txn->mt_txnid; tail->mm_mapsize = pending->mm_mapsize;
tail->mm_dbs[0] = pending->mm_dbs[0];
tail->mm_dbs[1] = pending->mm_dbs[1];
tail->mm_last_pg = pending->mm_last_pg;
/* (LY) ITS#7969: issue a memory barrier, it is noop for x86. */
mdb_coherent_barrier();
tail->mm_txnid = pending->mm_txnid;
} else {
pending->mm_magic = MDB_MAGIC;
pending->mm_version = MDB_DATA_VERSION;
pending->mm_address = head->mm_address;
retry:
rc = pwrite(env->me_fd, pending, sizeof(MDB_meta), offset);
if (unlikely(rc != sizeof(MDB_meta))) {
rc = (rc < 0) ? errno : EIO;
if (rc == EINTR)
goto retry;
off = offsetof(MDB_meta, mm_mapsize); undo:
ptr = (char *)&meta + off; mdb_debug("write failed, disk error?");
len = sizeof(MDB_meta) - off; /* On a failure, the pagecache still contains the new data.
if (toggle) * Write some old data back, to prevent it from being used. */
off += env->me_psize; if (pwrite(env->me_fd, tail, sizeof(MDB_meta), offset) == sizeof(MDB_meta)) {
off += PAGEHDRSZ; /* LY: take a chance, if write succeeds at a magic ;) */
goto retry;
/* Write to the SYNC fd */ }
mfd = syncflush ? env->me_mfd : env->me_fd; goto fail;
retry_write: }
rc = pwrite(mfd, ptr, len, off); mdb_invalidate_cache(env->me_map + offset, sizeof(MDB_meta));
if (rc != len) {
int ignore_it;
rc = rc < 0 ? errno : EIO;
if (rc == EINTR)
goto retry_write;
mdb_debug("write failed, disk error?");
/* On a failure, the pagecache still contains the new data.
* Write some old data back, to prevent it from being used.
* Use the non-SYNC fd; we know it will fail anyway.
*/
meta.mm_last_pg = metab.mm_last_pg;
meta.mm_txnid = metab.mm_txnid;
ignore_it = pwrite(env->me_fd, ptr, len, off);
(void) ignore_it; /* Silence warnings. We don't care about pwrite's return value */
fail:
env->me_flags |= MDB_FATAL_ERROR;
return rc;
} }
mdb_invalidate_cache(env->me_map + off, len);
done: /* LY: step#3 - sync updated meta-pages. */
if ((flags & (MDB_NOSYNC | MDB_NOMETASYNC)) == 0) {
if (env->me_flags & MDB_WRITEMAP) {
char* ptr = env->me_map + (offset & ~(env->me_os_psize - 1));
int mode = (flags & MDB_MAPASYNC) ? MS_ASYNC : MS_SYNC;
if (unlikely(msync(ptr, env->me_os_psize, mode) < 0)) {
rc = errno;
goto fail;
}
} else {
while(unlikely(fdatasync(env->me_fd) < 0)) {
rc = errno;
if (rc != EINTR)
goto undo;
}
}
}
/* LY: currently this can't happen, but... */
if (unlikely(pending->mm_mapsize < head->mm_mapsize)) {
mdb_assert(env, pending->mm_mapsize == env->me_mapsize);
if (unlikely(mremap(env->me_map, head->mm_mapsize, pending->mm_mapsize,
MREMAP_FIXED, pending->mm_address) == MAP_FAILED)) {
rc = errno;
goto fail;
}
if (unlikely(ftruncate(env->me_fd, pending->mm_mapsize) < 0)) {
rc = errno;
goto fail;
}
}
/* Memory ordering issues are irrelevant; since the entire writer /* Memory ordering issues are irrelevant; since the entire writer
* is wrapped by wmutex, all of these changes will become visible * is wrapped by wmutex, all of these changes will become visible
* after the wmutex is unlocked. Since the DB is multi-version, * after the wmutex is unlocked. Since the DB is multi-version,
* readers will get consistent data regardless of how fresh or * readers will get consistent data regardless of how fresh or
* how stale their view of these values is. * how stale their view of these values is.
*/ */
env->me_txns->mti_txnid = txn->mt_txnid; env->me_txns->mti_txnid = pending->mm_txnid;
return MDB_SUCCESS; return MDB_SUCCESS;
}
/** Check both meta pages to see which one is newer. fail:
* @param[in] env the environment handle env->me_flags |= MDB_FATAL_ERROR;
* @return meta toggle (0 or 1). return rc;
*/
static int
mdb_env_pick_meta(const MDB_env *env)
{
return (env->me_metas[0]->mm_txnid < env->me_metas[1]->mm_txnid);
} }
int ESECT int ESECT
@ -4015,7 +3999,6 @@ mdb_env_create(MDB_env **env)
e->me_maxdbs = e->me_numdbs = 2; e->me_maxdbs = e->me_numdbs = 2;
e->me_fd = INVALID_HANDLE_VALUE; e->me_fd = INVALID_HANDLE_VALUE;
e->me_lfd = INVALID_HANDLE_VALUE; e->me_lfd = INVALID_HANDLE_VALUE;
e->me_mfd = INVALID_HANDLE_VALUE;
e->me_pid = getpid(); e->me_pid = getpid();
GET_PAGESIZE(e->me_os_psize); GET_PAGESIZE(e->me_os_psize);
VALGRIND_CREATE_MEMPOOL(e,0,0); VALGRIND_CREATE_MEMPOOL(e,0,0);
@ -4026,7 +4009,6 @@ mdb_env_create(MDB_env **env)
static int ESECT static int ESECT
mdb_env_map(MDB_env *env, void *addr) mdb_env_map(MDB_env *env, void *addr)
{ {
MDB_page *p;
unsigned flags = env->me_flags; unsigned flags = env->me_flags;
int prot = PROT_READ; int prot = PROT_READ;
@ -4061,10 +4043,6 @@ mdb_env_map(MDB_env *env, void *addr)
if (addr && env->me_map != addr) if (addr && env->me_map != addr)
return EBUSY; /* TODO: Make a new MDB_* error code? */ return EBUSY; /* TODO: Make a new MDB_* error code? */
p = (MDB_page *)env->me_map;
env->me_metas[0] = PAGEDATA(p);
env->me_metas[1] = (MDB_meta *)((char *)env->me_metas[0] + env->me_psize);
/* Lock meta pages to avoid unexpected write, /* Lock meta pages to avoid unexpected write,
* before the data pages would be synchronized. */ * before the data pages would be synchronized. */
if ((flags & MDB_WRITEMAP) && mlock(env->me_map, env->me_psize * 2)) if ((flags & MDB_WRITEMAP) && mlock(env->me_map, env->me_psize * 2))
@ -4090,7 +4068,7 @@ mdb_env_set_mapsize(MDB_env *env, size_t size)
void *old; void *old;
if (env->me_txn) if (env->me_txn)
return EINVAL; return EINVAL;
meta = env->me_metas[mdb_env_pick_meta(env)]; meta = mdb_env_meta_head(env);
if (!size) if (!size)
size = meta->mm_mapsize; size = meta->mm_mapsize;
{ {
@ -4155,11 +4133,6 @@ mdb_fsize(HANDLE fd, size_t *size)
return MDB_SUCCESS; return MDB_SUCCESS;
} }
#ifdef FDATASYNC_MAYBE_BROKEN
# include <sys/utsname.h>
# include <sys/vfs.h>
#endif /* FDATASYNC_MAYBE_BROKEN */
/** Further setup required for opening an LMDB environment /** Further setup required for opening an LMDB environment
*/ */
static int ESECT static int ESECT
@ -4169,54 +4142,6 @@ mdb_env_open2(MDB_env *env)
int i, newenv = 0, rc; int i, newenv = 0, rc;
MDB_meta meta; MDB_meta meta;
#ifdef FDATASYNC_MAYBE_BROKEN
/* ext3/ext4 fdatasync is broken on some older Linux kernels.
* https://lkml.org/lkml/2012/9/3/83
* Kernels after 3.6-rc6 are known good.
* https://lkml.org/lkml/2012/9/10/556
* See if the DB is on ext3/ext4, then check for new enough kernel
* Kernels 2.6.32.60, 2.6.34.15, 3.2.30, and 3.5.4 are also known
* to be patched.
*/
{
struct statfs st;
fstatfs(env->me_fd, &st);
while (st.f_type == 0xEF53) {
struct utsname uts;
int i;
uname(&uts);
if (uts.release[0] < '3') {
if (!strncmp(uts.release, "2.6.32.", 7)) {
i = atoi(uts.release+7);
if (i >= 60)
break; /* 2.6.32.60 and newer is OK */
} else if (!strncmp(uts.release, "2.6.34.", 7)) {
i = atoi(uts.release+7);
if (i >= 15)
break; /* 2.6.34.15 and newer is OK */
}
} else if (uts.release[0] == '3') {
i = atoi(uts.release+2);
if (i > 5)
break; /* 3.6 and newer is OK */
if (i == 5) {
i = atoi(uts.release+4);
if (i >= 4)
break; /* 3.5.4 and newer is OK */
} else if (i == 2) {
i = atoi(uts.release+4);
if (i >= 30)
break; /* 3.2.30 and newer is OK */
}
} else { /* 4.x and newer is OK */
break;
}
env->me_flags |= MDB_BROKEN_DATASYNC;
break;
}
}
#endif /* FDATASYNC_MAYBE_BROKEN */
if ((i = mdb_env_read_header(env, &meta)) != 0) { if ((i = mdb_env_read_header(env, &meta)) != 0) {
if (i != ENOENT) if (i != ENOENT)
return i; return i;
@ -4280,18 +4205,16 @@ mdb_env_open2(MDB_env *env)
env->me_maxkey = env->me_nodemax - (NODESIZE + sizeof(MDB_db)); env->me_maxkey = env->me_nodemax - (NODESIZE + sizeof(MDB_db));
#endif #endif
env->me_maxpg = env->me_mapsize / env->me_psize; env->me_maxpg = env->me_mapsize / env->me_psize;
#ifdef FDATASYNC_MAYBE_BROKEN
env->me_sync_size = env->me_mapsize;
#endif /* FDATASYNC_MAYBE_BROKEN */
#if MDB_DEBUG #if MDB_DEBUG
{ {
int toggle = mdb_env_pick_meta(env); MDB_meta *meta = mdb_env_meta_head(env);
MDB_db *db = &env->me_metas[toggle]->mm_dbs[MAIN_DBI]; MDB_db *db = &meta->mm_dbs[MAIN_DBI];
int toggle = ((char*) meta == PAGEDATA(env->me_map)) ? 0 : 1;
mdb_debug("opened database version %u, pagesize %u", mdb_debug("opened database version %u, pagesize %u",
env->me_metas[0]->mm_version, env->me_psize); meta->mm_version, env->me_psize);
mdb_debug("using meta page %d", toggle); mdb_debug("using meta page %d, txn %zu", toggle, meta->mm_txnid);
mdb_debug("depth: %u", db->md_depth); mdb_debug("depth: %u", db->md_depth);
mdb_debug("entries: %zu", db->md_entries); mdb_debug("entries: %zu", db->md_entries);
mdb_debug("branch pages: %zu", db->md_branch_pages); mdb_debug("branch pages: %zu", db->md_branch_pages);
@ -4323,9 +4246,10 @@ static int ESECT
mdb_env_share_locks(MDB_env *env, int *excl) mdb_env_share_locks(MDB_env *env, int *excl)
{ {
struct flock lock_info; struct flock lock_info;
int rc = 0, toggle = mdb_env_pick_meta(env); MDB_meta *meta = mdb_env_meta_head(env);
int rc = 0;
env->me_txns->mti_txnid = env->me_metas[toggle]->mm_txnid; env->me_txns->mti_txnid = meta->mm_txnid;
/* The shared lock replaces the existing lock */ /* The shared lock replaces the existing lock */
memset((void *)&lock_info, 0, sizeof(lock_info)); memset((void *)&lock_info, 0, sizeof(lock_info));
@ -4533,7 +4457,6 @@ mdb_env_setup_locks(MDB_env *env, char *lpath, int mode, int *excl)
env->me_txns->mti_format = MDB_LOCK_FORMAT; env->me_txns->mti_format = MDB_LOCK_FORMAT;
env->me_txns->mti_txnid = 0; env->me_txns->mti_txnid = 0;
env->me_txns->mti_numreaders = 0; env->me_txns->mti_numreaders = 0;
} else { } else {
if (env->me_txns->mti_magic != MDB_MAGIC) { if (env->me_txns->mti_magic != MDB_MAGIC) {
mdb_debug("lock region has invalid magic"); mdb_debug("lock region has invalid magic");
@ -4610,8 +4533,8 @@ mdb_env_open(MDB_env *env, const char *path, unsigned flags, mode_t mode)
rc = MDB_SUCCESS; rc = MDB_SUCCESS;
flags |= env->me_flags; flags |= env->me_flags;
if (flags & MDB_RDONLY) { if (flags & MDB_RDONLY) {
/* silently ignore WRITEMAP when we're only getting read access */ /* silently ignore irrelevant flags when we're only getting read access */
flags &= ~MDB_WRITEMAP; flags &= ~(MDB_WRITEMAP | MDB_MAPASYNC | MDB_NOSYNC | MDB_NOMETASYNC);
} else { } else {
if (!((env->me_free_pgs = mdb_midl_alloc(MDB_IDL_UM_MAX)) && if (!((env->me_free_pgs = mdb_midl_alloc(MDB_IDL_UM_MAX)) &&
(env->me_dirty_list = calloc(MDB_IDL_UM_SIZE, sizeof(MDB_ID2))))) (env->me_dirty_list = calloc(MDB_IDL_UM_SIZE, sizeof(MDB_ID2)))))
@ -4655,19 +4578,6 @@ mdb_env_open(MDB_env *env, const char *path, unsigned flags, mode_t mode)
} }
if ((rc = mdb_env_open2(env)) == MDB_SUCCESS) { if ((rc = mdb_env_open2(env)) == MDB_SUCCESS) {
if (flags & (MDB_RDONLY|MDB_WRITEMAP)) {
env->me_mfd = env->me_fd;
} else {
/* Synchronous fd for meta writes. Needed even with
* MDB_NOSYNC/MDB_NOMETASYNC, in case these get reset.
*/
oflags &= ~O_CREAT;
env->me_mfd = open(dpath, oflags | MDB_DSYNC, mode);
if (env->me_mfd == INVALID_HANDLE_VALUE) {
rc = errno;
goto leave;
}
}
mdb_debug("opened dbenv %p", (void *) env); mdb_debug("opened dbenv %p", (void *) env);
if (excl > 0) { if (excl > 0) {
rc = mdb_env_share_locks(env, &excl); rc = mdb_env_share_locks(env, &excl);
@ -4738,8 +4648,6 @@ mdb_env_close0(MDB_env *env)
env->me_valgrind_handle = -1; env->me_valgrind_handle = -1;
#endif #endif
} }
if (env->me_mfd != env->me_fd && env->me_mfd != INVALID_HANDLE_VALUE)
(void) close(env->me_mfd);
if (env->me_fd != INVALID_HANDLE_VALUE) if (env->me_fd != INVALID_HANDLE_VALUE)
(void) close(env->me_fd); (void) close(env->me_fd);
@ -8701,7 +8609,7 @@ mdb_env_copyfd1(MDB_env *env, HANDLE fd)
mp->mp_flags = P_META; mp->mp_flags = P_META;
mm = (MDB_meta *)PAGEDATA(mp); mm = (MDB_meta *)PAGEDATA(mp);
mdb_env_init_meta0(env, mm); mdb_env_init_meta0(env, mm);
mm->mm_address = env->me_metas[0]->mm_address; mm->mm_address = METAPAGE_1(env)->mm_address;
mp = (MDB_page *)(my.mc_wbuf[0] + env->me_psize); mp = (MDB_page *)(my.mc_wbuf[0] + env->me_psize);
mp->mp_pgno = 1; mp->mp_pgno = 1;
@ -9014,32 +8922,31 @@ mdb_stat0(MDB_env *env, MDB_db *db, MDB_stat *arg)
int ESECT int ESECT
mdb_env_stat(MDB_env *env, MDB_stat *arg) mdb_env_stat(MDB_env *env, MDB_stat *arg)
{ {
int toggle; MDB_meta *meta;
if (env == NULL || arg == NULL) if (env == NULL || arg == NULL)
return EINVAL; return EINVAL;
toggle = mdb_env_pick_meta(env); meta = mdb_env_meta_head(env);
return mdb_stat0(env, &meta->mm_dbs[MAIN_DBI], arg);
return mdb_stat0(env, &env->me_metas[toggle]->mm_dbs[MAIN_DBI], arg);
} }
int ESECT int ESECT
mdb_env_info(MDB_env *env, MDB_envinfo *arg) mdb_env_info(MDB_env *env, MDB_envinfo *arg)
{ {
int toggle; MDB_meta *meta;
if (env == NULL || arg == NULL) if (env == NULL || arg == NULL)
return EINVAL; return EINVAL;
toggle = mdb_env_pick_meta(env); meta = mdb_env_meta_head(env);
arg->me_mapaddr = env->me_metas[toggle]->mm_address; arg->me_mapaddr = meta->mm_address;
arg->me_mapsize = env->me_mapsize; arg->me_mapsize = env->me_mapsize;
arg->me_maxreaders = env->me_maxreaders; arg->me_maxreaders = env->me_maxreaders;
arg->me_numreaders = env->me_txns->mti_numreaders; arg->me_numreaders = env->me_txns->mti_numreaders;
arg->me_last_pgno = env->me_metas[toggle]->mm_last_pg; arg->me_last_pgno = meta->mm_last_pg;
arg->me_last_txnid = env->me_metas[toggle]->mm_txnid; arg->me_last_txnid = meta->mm_txnid;
arg->me_tail_txnid = 0; arg->me_tail_txnid = 0;
MDB_reader *r = env->me_txns->mti_readers; MDB_reader *r = env->me_txns->mti_readers;
@ -9551,6 +9458,8 @@ static int mdb_mutex_failed(MDB_env *env, pthread_mutex_t *mutex, int rc)
#ifdef EOWNERDEAD #ifdef EOWNERDEAD
if (unlikely(rc == EOWNERDEAD)) { if (unlikely(rc == EOWNERDEAD)) {
int rlocked, rc2; int rlocked, rc2;
MDB_meta *meta;
/* We own the mutex. Clean up after dead previous owner. */ /* We own the mutex. Clean up after dead previous owner. */
rc = MDB_SUCCESS; rc = MDB_SUCCESS;
rlocked = (mutex == MDB_MUTEX(env, r)); rlocked = (mutex == MDB_MUTEX(env, r));
@ -9558,8 +9467,8 @@ static int mdb_mutex_failed(MDB_env *env, pthread_mutex_t *mutex, int rc)
/* Keep mti_txnid updated, otherwise next writer can /* Keep mti_txnid updated, otherwise next writer can
* overwrite data which latest meta page refers to. * overwrite data which latest meta page refers to.
*/ */
int toggle = mdb_env_pick_meta(env); meta = mdb_env_meta_head(env);
env->me_txns->mti_txnid = env->me_metas[toggle]->mm_txnid; env->me_txns->mti_txnid = meta->mm_txnid;
/* env is hosed if the dead thread was ours */ /* env is hosed if the dead thread was ours */
if (env->me_txn) { if (env->me_txn) {
env->me_flags |= MDB_FATAL_ERROR; env->me_flags |= MDB_FATAL_ERROR;