# define MDB_USE_ROBUST 0
# else
# define MDB_USE_ROBUST 1
+# endif
+#endif /* !MDB_USE_ROBUST */
+
+#if defined(MDB_USE_POSIX_MUTEX) && (MDB_USE_ROBUST)
/* glibc < 2.12 only provided _np API */
# if (defined(__GLIBC__) && GLIBC_VER < 0x02000c) || \
(defined(PTHREAD_MUTEX_ROBUST_NP) && !defined(PTHREAD_MUTEX_ROBUST))
# define pthread_mutexattr_setrobust(attr, flag) pthread_mutexattr_setrobust_np(attr, flag)
# define pthread_mutex_consistent(mutex) pthread_mutex_consistent_np(mutex)
# endif
-# endif
-#endif /* MDB_USE_ROBUST */
+#endif /* MDB_USE_POSIX_MUTEX && MDB_USE_ROBUST */
-#if defined(MDB_OWNERDEAD) && MDB_USE_ROBUST
+#if defined(MDB_OWNERDEAD) && (MDB_USE_ROBUST)
#define MDB_ROBUST_SUPPORTED 1
#endif
#define pthread_mutex_lock(x) WaitForSingleObject(*x, INFINITE)
#define pthread_cond_signal(x) SetEvent(*x)
#define pthread_cond_wait(cond,mutex) do{SignalObjectAndWait(*mutex, *cond, INFINITE, FALSE); WaitForSingleObject(*mutex, INFINITE);}while(0)
-#define THREAD_CREATE(thr,start,arg) thr=CreateThread(NULL,0,start,arg,0,NULL)
-#define THREAD_FINISH(thr) WaitForSingleObject(thr, INFINITE)
+#define THREAD_CREATE(thr,start,arg) \
+ (((thr) = CreateThread(NULL, 0, start, arg, 0, NULL)) ? 0 : ErrCode())
+#define THREAD_FINISH(thr) \
+ (WaitForSingleObject(thr, INFINITE) ? ErrCode() : 0)
#define LOCK_MUTEX0(mutex) WaitForSingleObject(mutex, INFINITE)
#define UNLOCK_MUTEX(mutex) ReleaseMutex(mutex)
#define mdb_mutex_consistent(mutex) 0
+ (((MDB_PIDLOCK) != 0) << 16)))
/** @} */
-/** Common header for all page types.
- * Overflow records occupy a number of contiguous pages with no
- * headers on any page after the first.
+/** Common header for all page types. The page type depends on #mp_flags.
+ *
+ * #P_BRANCH and #P_LEAF pages have unsorted '#MDB_node's at the end, with
+ * sorted #mp_ptrs[] entries referring to them. Exception: #P_LEAF2 pages
+ * omit mp_ptrs and pack sorted #MDB_DUPFIXED values after the page header.
+ *
+ * #P_OVERFLOW records occupy one or more contiguous pages where only the
+ * first has a page header. They hold the real data of #F_BIGDATA nodes.
+ *
+ * #P_SUBP sub-pages are small leaf "pages" with duplicate data.
+ * A node with flag #F_DUPDATA but not #F_SUBDATA contains a sub-page.
+ * (Duplicate data can also go in sub-databases, which use normal pages.)
+ *
+ * #P_META pages contain #MDB_meta, the start point of an LMDB snapshot.
+ *
+ * Each non-metapage up to #MDB_meta.%mm_last_pg is reachable exactly once
+ * in the snapshot: Either used by a database or listed in a freeDB record.
*/
typedef struct MDB_page {
#define mp_pgno mp_p.p_pgno
pgno_t p_pgno; /**< page number */
struct MDB_page *p_next; /**< for in-memory list of freed pages */
} mp_p;
- uint16_t mp_pad;
+ uint16_t mp_pad; /**< key size if this is a LEAF2 page */
/** @defgroup mdb_page Page Flags
* @ingroup internal
* Flags for the page headers.
/** The number of overflow pages needed to store the given size. */
#define OVPAGES(size, psize) ((PAGEHDRSZ-1 + (size)) / (psize) + 1)
- /** Link in #MDB_txn.%mt_loose_pgs list */
+ /** Link in #MDB_txn.%mt_loose_pgs list.
+ * Kept outside the page header, which is needed when reusing the page.
+ */
#define NEXT_LOOSE_PAGE(p) (*(MDB_page **)((p) + 2))
/** Header for a single key/data pair within a page.
pgno_t md_root; /**< the root page of this tree */
} MDB_db;
- /** mdb_dbi_open flags */
#define MDB_VALID 0x8000 /**< DB handle is valid, for me_dbflags */
#define PERSISTENT_FLAGS (0xffff & ~(MDB_VALID))
+ /** #mdb_dbi_open() flags */
#define VALID_FLAGS (MDB_REVERSEKEY|MDB_DUPSORT|MDB_INTEGERKEY|MDB_DUPFIXED|\
MDB_INTEGERDUP|MDB_REVERSEDUP|MDB_CREATE)
#define mm_psize mm_dbs[FREE_DBI].md_pad
/** Any persistent environment flags. @ref mdb_env */
#define mm_flags mm_dbs[FREE_DBI].md_flags
- pgno_t mm_last_pg; /**< last used page in file */
+ /** Last used page in the datafile.
+ * Actually the file may be shorter if the freeDB lists the final pages.
+ */
+ pgno_t mm_last_pg;
volatile txnid_t mm_txnid; /**< txnid that committed this page */
} MDB_meta;
* in this transaction, linked through #NEXT_LOOSE_PAGE(page).
*/
MDB_page *mt_loose_pgs;
- /* #Number of loose pages (#mt_loose_pgs) */
+ /** Number of loose pages (#mt_loose_pgs) */
int mt_loose_count;
/** The sorted list of dirty pages we temporarily wrote to disk
* because the dirty list was full. page numbers in here are
* @ingroup internal
* @{
*/
-#define DB_DIRTY 0x01 /**< DB was modified or is DUPSORT data */
+#define DB_DIRTY 0x01 /**< DB was written in this txn */
#define DB_STALE 0x02 /**< Named-DB record is older than txnID */
#define DB_NEW 0x04 /**< Named-DB handle opened in this txn */
#define DB_VALID 0x08 /**< DB handle is valid, see also #MDB_VALID */
#define DB_USRVALID 0x10 /**< As #DB_VALID, but not set for #FREE_DBI */
+#define DB_DUPDATA 0x20 /**< DB is #MDB_DUPSORT data */
/** @} */
/** In write txns, array of cursors for each DB */
MDB_cursor **mt_cursors;
unsigned char mx_dbflag;
} MDB_xcursor;
+ /** Check if there is an inited xcursor, so #XCURSOR_REFRESH() is proper */
+#define XCURSOR_INITED(mc) \
+ ((mc)->mc_xcursor && ((mc)->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))
+
+ /** Update sub-page pointer, if any, in \b mc->mc_xcursor. Needed
+ * when the node which contains the sub-page may have moved. Called
+ * with \b mp = mc->mc_pg[mc->mc_top], \b ki = mc->mc_ki[mc->mc_top].
+ */
+#define XCURSOR_REFRESH(mc, mp, ki) do { \
+ MDB_page *xr_pg = (mp); \
+ MDB_node *xr_node = NODEPTR(xr_pg, ki); \
+ if ((xr_node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) \
+ (mc)->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(xr_node); \
+} while (0)
+
/** State of FreeDB old pages, stored in the MDB_env */
typedef struct MDB_pgstate {
pgno_t *mf_pghead; /**< Reclaimed freeDB pages, or NULL before use */
* and the actual use of the message uses more than 4K of stack.
*/
#define MSGSIZE 1024
-#define PADSIZE 4096
+#define PADSIZE 4096
char buf[MSGSIZE+PADSIZE], *ptr = buf;
#endif
int i;
pgno, ((MDB_meta *)METADATA(mp))->mm_txnid);
return;
default:
- fprintf(stderr, "Bad page %"Z"u flags 0x%u\n", pgno, mp->mp_flags);
+ fprintf(stderr, "Bad page %"Z"u flags 0x%X\n", pgno, mp->mp_flags);
return;
}
}
if (mc->mc_ki[i] >= NUMKEYS(mc->mc_pg[i]))
printf("ack!\n");
- if (mc->mc_xcursor && (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) {
+ if (XCURSOR_INITED(mc)) {
node = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
if (((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) &&
mc->mc_xcursor->mx_cursor.mc_pg[0] != NODEDATA(node)) {
}
}
if (freecount + count + NUM_METAS != txn->mt_next_pgno) {
- fprintf(stderr, "audit: %lu freecount: %lu count: %lu total: %lu next_pgno: %lu\n",
+ fprintf(stderr, "audit: %"Z"u freecount: %"Z"u count: %"Z"u total: %"Z"u next_pgno: %"Z"u\n",
txn->mt_txnid, freecount, count+NUM_METAS,
freecount+count+NUM_METAS, txn->mt_next_pgno);
}
if (m2 == mc) continue;
if (m2->mc_pg[mc->mc_top] == mp) {
m2->mc_pg[mc->mc_top] = np;
- if ((mc->mc_db->md_flags & MDB_DUPSORT) &&
- IS_LEAF(np) &&
- (m2->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))
- {
- MDB_node *leaf = NODEPTR(np, m2->mc_ki[mc->mc_top]);
- if ((leaf->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA)
- m2->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(leaf);
- }
+ if (XCURSOR_INITED(m2) && IS_LEAF(np))
+ XCURSOR_REFRESH(m2, np, m2->mc_ki[mc->mc_top]);
}
}
}
#ifdef O_CLOEXEC /* Linux: Open file and set FD_CLOEXEC atomically */
# define MDB_CLOEXEC O_CLOEXEC
#else
- int fdflags;
# define MDB_CLOEXEC 0
#endif
+ int fdflags;
#endif
int rc;
off_t size, rsize;
if (rc == MDB_ERRCODE_ROFS && (env->me_flags & MDB_RDONLY)) {
return MDB_SUCCESS;
}
- goto fail_errno;
+ goto fail;
}
-#if ! ((MDB_CLOEXEC) || defined(_WIN32))
+#ifndef _WIN32
/* Lose record locks when exec*() */
- if ((fdflags = fcntl(env->me_lfd, F_GETFD) | FD_CLOEXEC) >= 0)
- fcntl(env->me_lfd, F_SETFD, fdflags);
+ if (!(MDB_CLOEXEC) && (fdflags = fcntl(env->me_lfd, F_GETFD)) != -1)
+ fcntl(env->me_lfd, F_SETFD, fdflags | FD_CLOEXEC);
#endif
if (!(env->me_flags & MDB_NOTLS)) {
}
}
rc = 0;
+ mc->mc_flags &= ~C_EOF;
goto set2;
}
}
}
}
break;
+ case MDB_PREV_MULTIPLE:
+ if (data == NULL) {
+ rc = EINVAL;
+ break;
+ }
+ if (!(mc->mc_db->md_flags & MDB_DUPFIXED)) {
+ rc = MDB_INCOMPATIBLE;
+ break;
+ }
+ if (!(mc->mc_flags & C_INITIALIZED))
+ rc = mdb_cursor_last(mc, key, data);
+ else
+ rc = MDB_SUCCESS;
+ if (rc == MDB_SUCCESS) {
+ MDB_cursor *mx = &mc->mc_xcursor->mx_cursor;
+ if (mx->mc_flags & C_INITIALIZED) {
+ rc = mdb_cursor_sibling(mx, 0);
+ if (rc == MDB_SUCCESS)
+ goto fetchm;
+ } else {
+ rc = MDB_NOTFOUND;
+ }
+ }
+ break;
case MDB_NEXT:
case MDB_NEXT_DUP:
case MDB_NEXT_NODUP:
{
int rc = MDB_SUCCESS;
- if (mc->mc_dbi >= CORE_DBS && !(*mc->mc_dbflag & DB_DIRTY)) {
+ if (mc->mc_dbi >= CORE_DBS && !(*mc->mc_dbflag & (DB_DIRTY|DB_DUPDATA))) {
+ /* Touch DB record of named DB */
MDB_cursor mc2;
MDB_xcursor mcx;
if (TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi))
if (m3->mc_ki[i] >= mc->mc_ki[i] && insert_key) {
m3->mc_ki[i]++;
}
- if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) {
- MDB_node *n2 = NODEPTR(mp, m3->mc_ki[i]);
- if ((n2->mn_flags & (F_SUBDATA|F_DUPDATA)) == F_DUPDATA)
- m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(n2);
- }
+ if (XCURSOR_INITED(m3))
+ XCURSOR_REFRESH(m3, mp, m3->mc_ki[i]);
}
}
}
if (m2->mc_ki[i] == mc->mc_ki[i]) {
mdb_xcursor_init2(m2, mx, new_dupdata);
} else if (!insert_key && m2->mc_ki[i] < nkeys) {
- MDB_node *n2 = NODEPTR(mp, m2->mc_ki[i]);
- if ((n2->mn_flags & (F_SUBDATA|F_DUPDATA)) == F_DUPDATA)
- m2->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(n2);
+ XCURSOR_REFRESH(m2, mp, m2->mc_ki[i]);
}
}
}
if (m2 == mc || m2->mc_snum < mc->mc_snum) continue;
if (!(m2->mc_flags & C_INITIALIZED)) continue;
if (m2->mc_pg[mc->mc_top] == mp) {
- if (m2->mc_ki[mc->mc_top] == mc->mc_ki[mc->mc_top]) {
- m2->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(leaf);
- } else {
- MDB_node *n2 = NODEPTR(mp, m2->mc_ki[mc->mc_top]);
- if (!(n2->mn_flags & F_SUBDATA))
- m2->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(n2);
+ MDB_node *n2 = leaf;
+ if (m2->mc_ki[mc->mc_top] != mc->mc_ki[mc->mc_top]) {
+ n2 = NODEPTR(mp, m2->mc_ki[mc->mc_top]);
+ if (n2->mn_flags & F_SUBDATA) continue;
}
+ m2->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(n2);
}
}
}
}
DPRINTF(("Sub-db -%u root page %"Z"u", mx->mx_cursor.mc_dbi,
mx->mx_db.md_root));
- mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DIRTY; /* DB_DIRTY guides mdb_cursor_touch */
+ mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DUPDATA;
#if UINT_MAX < SIZE_MAX
if (mx->mx_dbx.md_cmp == mdb_cmp_int && mx->mx_db.md_pad == sizeof(size_t))
mx->mx_dbx.md_cmp = mdb_cmp_clong;
mx->mx_cursor.mc_top = 0;
mx->mx_cursor.mc_flags |= C_INITIALIZED;
mx->mx_cursor.mc_ki[0] = 0;
- mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DIRTY; /* DB_DIRTY guides mdb_cursor_touch */
+ mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DUPDATA;
#if UINT_MAX < SIZE_MAX
mx->mx_dbx.md_cmp = src_mx->mx_dbx.md_cmp;
#endif
m3->mc_ki[csrc->mc_top] = cdst->mc_ki[cdst->mc_top];
m3->mc_ki[csrc->mc_top-1]++;
}
- if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) &&
- IS_LEAF(mps)) {
- MDB_node *node = NODEPTR(m3->mc_pg[csrc->mc_top], m3->mc_ki[csrc->mc_top]);
- if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA)
- m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node);
- }
+ if (XCURSOR_INITED(m3) && IS_LEAF(mps))
+ XCURSOR_REFRESH(m3, m3->mc_pg[csrc->mc_top], m3->mc_ki[csrc->mc_top]);
}
} else
/* Adding on the right, bump others down */
} else {
m3->mc_ki[csrc->mc_top]--;
}
- if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) &&
- IS_LEAF(mps)) {
- MDB_node *node = NODEPTR(m3->mc_pg[csrc->mc_top], m3->mc_ki[csrc->mc_top]);
- if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA)
- m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node);
- }
+ if (XCURSOR_INITED(m3) && IS_LEAF(mps))
+ XCURSOR_REFRESH(m3, m3->mc_pg[csrc->mc_top], m3->mc_ki[csrc->mc_top]);
}
}
}
m3->mc_ki[top-1] > csrc->mc_ki[top-1]) {
m3->mc_ki[top-1]--;
}
- if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) &&
- IS_LEAF(psrc)) {
- MDB_node *node = NODEPTR(m3->mc_pg[top], m3->mc_ki[top]);
- if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA)
- m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node);
- }
+ if (XCURSOR_INITED(m3) && IS_LEAF(psrc))
+ XCURSOR_REFRESH(m3, m3->mc_pg[top], m3->mc_ki[top]);
}
}
{
if (m3->mc_pg[mc->mc_top] == mp) {
if (m3->mc_ki[mc->mc_top] == ki) {
m3->mc_flags |= C_DEL;
+ if (mc->mc_db->md_flags & MDB_DUPSORT) {
+ /* Sub-cursor referred into dataset which is gone */
+ m3->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF);
+ }
+ continue;
} else if (m3->mc_ki[mc->mc_top] > ki) {
m3->mc_ki[mc->mc_top]--;
}
- if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) {
- MDB_node *node = NODEPTR(m3->mc_pg[mc->mc_top], m3->mc_ki[mc->mc_top]);
- if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA)
- m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node);
- }
+ if (XCURSOR_INITED(m3))
+ XCURSOR_REFRESH(m3, m3->mc_pg[mc->mc_top], m3->mc_ki[mc->mc_top]);
}
}
}
}
if (mc->mc_db->md_flags & MDB_DUPSORT) {
MDB_node *node = NODEPTR(m3->mc_pg[m3->mc_top], m3->mc_ki[m3->mc_top]);
- if (node->mn_flags & F_DUPDATA) {
- mdb_xcursor_init1(m3, node);
- m3->mc_xcursor->mx_cursor.mc_flags |= C_DEL;
+ /* If this node is a fake page, it needs to be reinited
+ * because its data has moved. But just reset mc_pg[0]
+ * if the xcursor is already live.
+ */
+ if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) {
+ if (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)
+ m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node);
+ else
+ mdb_xcursor_init1(m3, node);
}
}
}
m3->mc_ki[ptop] >= mc->mc_ki[ptop]) {
m3->mc_ki[ptop]++;
}
- if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) &&
- IS_LEAF(mp)) {
- MDB_node *node = NODEPTR(m3->mc_pg[mc->mc_top], m3->mc_ki[mc->mc_top]);
- if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA)
- m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node);
- }
+ if (XCURSOR_INITED(m3) && IS_LEAF(mp))
+ XCURSOR_REFRESH(m3, m3->mc_pg[mc->mc_top], m3->mc_ki[mc->mc_top]);
}
}
DPRINTF(("mp left: %d, rp left: %d", SIZELEFT(mp), SIZELEFT(rp)));
#ifndef MDB_WBUF
#define MDB_WBUF (1024*1024)
#endif
+#define MDB_EOF 0x10 /**< #mdb_env_copyfd1() is done reading */
- /** State needed for a compacting copy. */
+ /** State needed for a double-buffering compacting copy. */
typedef struct mdb_copy {
+ MDB_env *mc_env;
+ MDB_txn *mc_txn;
pthread_mutex_t mc_mutex;
- pthread_cond_t mc_cond;
+ pthread_cond_t mc_cond; /**< Condition variable for #mc_new */
char *mc_wbuf[2];
char *mc_over[2];
- MDB_env *mc_env;
- MDB_txn *mc_txn;
int mc_wlen[2];
int mc_olen[2];
pgno_t mc_next_pgno;
HANDLE mc_fd;
- int mc_status;
- volatile int mc_new;
- int mc_toggle;
-
+ int mc_toggle; /**< Buffer number in provider */
+ int mc_new; /**< (0-2 buffers to write) | (#MDB_EOF at end) */
+ /** Error code. Never cleared if set. Both threads can set nonzero
+ * to fail the copy. Not mutex-protected, LMDB expects atomic int.
+ */
+ volatile int mc_error;
} mdb_copy;
/** Dedicated writer thread for compacting copy. */
#endif
pthread_mutex_lock(&my->mc_mutex);
- my->mc_new = 0;
- pthread_cond_signal(&my->mc_cond);
for(;;) {
while (!my->mc_new)
pthread_cond_wait(&my->mc_cond, &my->mc_mutex);
- if (my->mc_new < 0) {
- my->mc_new = 0;
+ if (my->mc_new == 0 + MDB_EOF) /* 0 buffers, just EOF */
break;
- }
- my->mc_new = 0;
wsize = my->mc_wlen[toggle];
ptr = my->mc_wbuf[toggle];
again:
- while (wsize > 0) {
+ rc = MDB_SUCCESS;
+ while (wsize > 0 && !my->mc_error) {
DO_WRITE(rc, my->mc_fd, ptr, wsize, len);
if (!rc) {
rc = ErrCode();
}
}
if (rc) {
- my->mc_status = rc;
- break;
+ my->mc_error = rc;
}
/* If there's an overflow page tail, write it too */
if (my->mc_olen[toggle]) {
}
my->mc_wlen[toggle] = 0;
toggle ^= 1;
+ /* Return the empty buffer to provider */
+ my->mc_new--;
pthread_cond_signal(&my->mc_cond);
}
- pthread_cond_signal(&my->mc_cond);
pthread_mutex_unlock(&my->mc_mutex);
return (THREAD_RET)0;
#undef DO_WRITE
}
- /** Tell the writer thread there's a buffer ready to write */
+ /** Give buffer and/or #MDB_EOF to writer thread, await unused buffer.
+ *
+ * @param[in] my control structure.
+ * @param[in] adjust (1 to hand off 1 buffer) | (MDB_EOF when ending).
+ */
static int ESECT
-mdb_env_cthr_toggle(mdb_copy *my, int st)
+mdb_env_cthr_toggle(mdb_copy *my, int adjust)
{
- int toggle = my->mc_toggle ^ 1;
pthread_mutex_lock(&my->mc_mutex);
- if (my->mc_status) {
- pthread_mutex_unlock(&my->mc_mutex);
- return my->mc_status;
- }
- while (my->mc_new == 1)
- pthread_cond_wait(&my->mc_cond, &my->mc_mutex);
- my->mc_new = st;
- my->mc_toggle = toggle;
+ my->mc_new += adjust;
pthread_cond_signal(&my->mc_cond);
+ while (my->mc_new & 2) /* both buffers in use */
+ pthread_cond_wait(&my->mc_cond, &my->mc_mutex);
pthread_mutex_unlock(&my->mc_mutex);
- return 0;
+
+ my->mc_toggle ^= (adjust & 1);
+ /* Both threads reset mc_wlen, to be safe from threading errors */
+ my->mc_wlen[my->mc_toggle] = 0;
+ return my->mc_error;
}
- /** Depth-first tree traversal for compacting copy. */
+ /** Depth-first tree traversal for compacting copy.
+ * @param[in] my control structure.
+ * @param[in,out] pg database root.
+ * @param[in] flags includes #F_DUPDATA if it is a sorted-duplicate sub-DB.
+ */
static int ESECT
mdb_env_cwalk(mdb_copy *my, pgno_t *pg, int flags)
{
}
memcpy(&pg, NODEDATA(ni), sizeof(pg));
+ memcpy(NODEDATA(ni), &my->mc_next_pgno, sizeof(pgno_t));
rc = mdb_page_get(&mc, pg, &omp, NULL);
if (rc)
goto done;
goto done;
toggle = my->mc_toggle;
}
- memcpy(NODEDATA(ni), &mo->mp_pgno, sizeof(pgno_t));
} else if (ni->mn_flags & F_SUBDATA) {
MDB_db db;
{
MDB_meta *mm;
MDB_page *mp;
- mdb_copy my;
+ mdb_copy my = {0};
MDB_txn *txn = NULL;
pthread_t thr;
- int rc;
+ pgno_t root, new_root;
+ int rc = MDB_SUCCESS;
#ifdef _WIN32
- my.mc_mutex = CreateMutex(NULL, FALSE, NULL);
- my.mc_cond = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (!(my.mc_mutex = CreateMutex(NULL, FALSE, NULL)) ||
+ !(my.mc_cond = CreateEvent(NULL, FALSE, FALSE, NULL))) {
+ rc = ErrCode();
+ goto done;
+ }
my.mc_wbuf[0] = _aligned_malloc(MDB_WBUF*2, env->me_os_psize);
- if (my.mc_wbuf[0] == NULL)
- return errno;
+ if (my.mc_wbuf[0] == NULL) {
+ /* _aligned_malloc() sets errno, but we use Windows error codes */
+ rc = ERROR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
#else
- pthread_mutex_init(&my.mc_mutex, NULL);
- pthread_cond_init(&my.mc_cond, NULL);
+ if ((rc = pthread_mutex_init(&my.mc_mutex, NULL)) != 0)
+ return rc;
+ if ((rc = pthread_cond_init(&my.mc_cond, NULL)) != 0)
+ goto done2;
#ifdef HAVE_MEMALIGN
my.mc_wbuf[0] = memalign(env->me_os_psize, MDB_WBUF*2);
- if (my.mc_wbuf[0] == NULL)
- return errno;
+ if (my.mc_wbuf[0] == NULL) {
+ rc = errno;
+ goto done;
+ }
#else
- rc = posix_memalign((void **)&my.mc_wbuf[0], env->me_os_psize, MDB_WBUF*2);
- if (rc)
- return rc;
+ {
+ void *p;
+ if ((rc = posix_memalign(&p, env->me_os_psize, MDB_WBUF*2)) != 0)
+ goto done;
+ my.mc_wbuf[0] = p;
+ }
#endif
#endif
memset(my.mc_wbuf[0], 0, MDB_WBUF*2);
my.mc_wbuf[1] = my.mc_wbuf[0] + MDB_WBUF;
- my.mc_wlen[0] = 0;
- my.mc_wlen[1] = 0;
- my.mc_olen[0] = 0;
- my.mc_olen[1] = 0;
my.mc_next_pgno = NUM_METAS;
- my.mc_status = 0;
- my.mc_new = 1;
- my.mc_toggle = 0;
my.mc_env = env;
my.mc_fd = fd;
- THREAD_CREATE(thr, mdb_env_copythr, &my);
+ rc = THREAD_CREATE(thr, mdb_env_copythr, &my);
+ if (rc)
+ goto done;
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
if (rc)
- return rc;
+ goto finish;
mp = (MDB_page *)my.mc_wbuf[0];
memset(mp, 0, NUM_METAS * env->me_psize);
*(MDB_meta *)METADATA(mp) = *mm;
mm = (MDB_meta *)METADATA(mp);
- /* Count the number of free pages, subtract from lastpg to find
- * number of active pages
- */
- {
+ /* Set metapage 1 with current main DB */
+ root = new_root = txn->mt_dbs[MAIN_DBI].md_root;
+ if (root != P_INVALID) {
+ /* Count free pages + freeDB pages. Subtract from last_pg
+ * to find the new last_pg, which also becomes the new root.
+ */
MDB_ID freecount = 0;
MDB_cursor mc;
MDB_val key, data;
mdb_cursor_init(&mc, txn, FREE_DBI, NULL);
while ((rc = mdb_cursor_get(&mc, &key, &data, MDB_NEXT)) == 0)
freecount += *(MDB_ID *)data.mv_data;
+ if (rc != MDB_NOTFOUND)
+ goto finish;
freecount += txn->mt_dbs[FREE_DBI].md_branch_pages +
txn->mt_dbs[FREE_DBI].md_leaf_pages +
txn->mt_dbs[FREE_DBI].md_overflow_pages;
- /* Set metapage 1 */
- mm->mm_last_pg = txn->mt_next_pgno - freecount - 1;
+ new_root = txn->mt_next_pgno - 1 - freecount;
+ mm->mm_last_pg = new_root;
mm->mm_dbs[MAIN_DBI] = txn->mt_dbs[MAIN_DBI];
- if (mm->mm_last_pg > NUM_METAS-1) {
- mm->mm_dbs[MAIN_DBI].md_root = mm->mm_last_pg;
- mm->mm_txnid = 1;
- } else {
- mm->mm_dbs[MAIN_DBI].md_root = P_INVALID;
- }
+ mm->mm_dbs[MAIN_DBI].md_root = new_root;
+ } else {
+ /* When the DB is empty, handle it specially to
+ * fix any breakage like page leaks from ITS#8174.
+ */
+ mm->mm_dbs[MAIN_DBI].md_flags = txn->mt_dbs[MAIN_DBI].md_flags;
}
+ if (root != P_INVALID || mm->mm_dbs[MAIN_DBI].md_flags) {
+ mm->mm_txnid = 1; /* use metapage 1 */
+ }
+
my.mc_wlen[0] = env->me_psize * NUM_METAS;
my.mc_txn = txn;
- pthread_mutex_lock(&my.mc_mutex);
- while(my.mc_new)
- pthread_cond_wait(&my.mc_cond, &my.mc_mutex);
- pthread_mutex_unlock(&my.mc_mutex);
- rc = mdb_env_cwalk(&my, &txn->mt_dbs[MAIN_DBI].md_root, 0);
- if (rc == MDB_SUCCESS && my.mc_wlen[my.mc_toggle])
- rc = mdb_env_cthr_toggle(&my, 1);
- mdb_env_cthr_toggle(&my, -1);
- pthread_mutex_lock(&my.mc_mutex);
- while(my.mc_new)
- pthread_cond_wait(&my.mc_cond, &my.mc_mutex);
- pthread_mutex_unlock(&my.mc_mutex);
- THREAD_FINISH(thr);
+ rc = mdb_env_cwalk(&my, &root, 0);
+ if (rc == MDB_SUCCESS && root != new_root) {
+ rc = MDB_INCOMPATIBLE; /* page leak or corrupt DB */
+ }
+finish:
+ if (rc)
+ my.mc_error = rc;
+ mdb_env_cthr_toggle(&my, 1 | MDB_EOF);
+ rc = THREAD_FINISH(thr);
mdb_txn_abort(txn);
+
+done:
#ifdef _WIN32
- CloseHandle(my.mc_cond);
- CloseHandle(my.mc_mutex);
- _aligned_free(my.mc_wbuf[0]);
+ if (my.mc_wbuf[0]) _aligned_free(my.mc_wbuf[0]);
+ if (my.mc_cond) CloseHandle(my.mc_cond);
+ if (my.mc_mutex) CloseHandle(my.mc_mutex);
#else
+ free(my.mc_wbuf[0]);
pthread_cond_destroy(&my.mc_cond);
+done2:
pthread_mutex_destroy(&my.mc_mutex);
- free(my.mc_wbuf[0]);
#endif
- return rc;
+ return rc ? rc : my.mc_error;
}
/** Copy environment as-is. */
}
if (env->me_psize >= env->me_os_psize) {
-#ifdef O_DIRECT
+#ifdef F_NOCACHE /* __APPLE__ */
+ (void) fcntl(newfd, F_NOCACHE, 1);
+#elif defined O_DIRECT
/* Set O_DIRECT if the file system supports it */
if ((rc = fcntl(newfd, F_GETFL)) != -1)
(void) fcntl(newfd, F_SETFL, rc | O_DIRECT);
-#endif
-#ifdef F_NOCACHE /* __APPLE__ */
- rc = fcntl(newfd, F_NOCACHE, 1);
- if (rc) {
- rc = ErrCode();
- goto leave;
- }
#endif
}
return rc;
}
#endif /* MDB_ROBUST_SUPPORTED */
-/** @} */
#if defined(_WIN32)
-static int utf8_to_utf16(const char *src, int srcsize, wchar_t **dst, int *dstsize)
-{
- int need;
- wchar_t *result;
- need = MultiByteToWideChar(CP_UTF8, 0, src, srcsize, NULL, 0);
- if (need == 0xFFFD)
- return EILSEQ;
- if (need == 0)
- return EINVAL;
- result = malloc(sizeof(wchar_t) * need);
- if (!result)
- return ENOMEM;
- MultiByteToWideChar(CP_UTF8, 0, src, srcsize, result, need);
- if (dstsize)
- *dstsize = need;
- *dst = result;
- return 0;
+static int ESECT
+utf8_to_utf16(const char *src, int srcsize, wchar_t **dst, int *dstsize)
+{
+ int rc, need = 0;
+ wchar_t *result = NULL;
+ for (;;) { /* malloc result, then fill it in */
+ need = MultiByteToWideChar(CP_UTF8, 0, src, srcsize, result, need);
+ if (!need) {
+ rc = ErrCode();
+ free(result);
+ return rc;
+ }
+ if (!result) {
+ result = malloc(sizeof(wchar_t) * need);
+ if (!result)
+ return ENOMEM;
+ continue;
+ }
+ if (dstsize)
+ *dstsize = need;
+ *dst = result;
+ return MDB_SUCCESS;
+ }
}
#endif /* defined(_WIN32) */
+/** @} */