]> git.sur5r.net Git - openldap/blobdiff - libraries/liblmdb/mdb.c
Drop spurious Errcode() call
[openldap] / libraries / liblmdb / mdb.c
index 0ce41b845a713b54594fda15c196270172ed7c6a..9c85230a6bcc67b2191ec732b285bd8024310c12 100644 (file)
@@ -258,6 +258,10 @@ typedef SSIZE_T    ssize_t;
 #  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))
@@ -265,10 +269,9 @@ typedef SSIZE_T    ssize_t;
 #   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
 
@@ -291,8 +294,10 @@ typedef HANDLE mdb_mutex_t, mdb_mutexref_t;
 #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
@@ -766,9 +771,23 @@ typedef struct MDB_txninfo {
          + (((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
@@ -777,7 +796,7 @@ typedef struct MDB_page {
                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.
@@ -844,7 +863,9 @@ typedef struct MDB_page {
        /** 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.
@@ -972,9 +993,9 @@ typedef struct MDB_db {
        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)
 
@@ -1005,7 +1026,10 @@ typedef struct MDB_meta {
 #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;
 
@@ -1055,7 +1079,7 @@ struct MDB_txn {
         *      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
@@ -1188,6 +1212,21 @@ typedef struct MDB_xcursor {
        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 */
@@ -1422,7 +1461,7 @@ mdb_strerror(int err)
         *      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;
@@ -1563,7 +1602,7 @@ mdb_page_list(MDB_page *mp)
                        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;
        }
 
@@ -1618,7 +1657,7 @@ mdb_cursor_chk(MDB_cursor *mc)
        }
        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)) {
@@ -1679,7 +1718,7 @@ static void mdb_audit(MDB_txn *txn)
                }
        }
        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);
        }
@@ -2433,14 +2472,8 @@ done:
                        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]);
                        }
                }
        }
@@ -4490,7 +4523,7 @@ mdb_env_setup_locks(MDB_env *env, char *lpath, int mode, int *excl)
                if (rc == MDB_ERRCODE_ROFS && (env->me_flags & MDB_RDONLY)) {
                        return MDB_SUCCESS;
                }
-               goto fail_errno;
+               goto fail;
        }
 #if ! ((MDB_CLOEXEC) || defined(_WIN32))
        /* Lose record locks when exec*() */
@@ -5895,6 +5928,7 @@ mdb_cursor_set(MDB_cursor *mc, MDB_val *key, MDB_val *data,
                                                }
                                        }
                                        rc = 0;
+                                       mc->mc_flags &= ~C_EOF;
                                        goto set2;
                                }
                        }
@@ -6206,6 +6240,30 @@ fetchm:
                        }
                }
                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:
@@ -6726,11 +6784,8 @@ new_sub:
                                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]);
                        }
                }
        }
@@ -6781,9 +6836,7 @@ put_sub:
                                                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]);
                                                }
                                        }
                                }
@@ -6888,13 +6941,12 @@ mdb_cursor_del(MDB_cursor *mc, unsigned int flags)
                                                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);
                                                }
                                        }
                                }
@@ -7731,12 +7783,8 @@ mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst, int fromleft)
                                        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 */
@@ -7757,12 +7805,8 @@ mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst, int fromleft)
                                        } 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]);
                                }
                        }
                }
@@ -7963,12 +8007,8 @@ mdb_page_merge(MDB_cursor *csrc, MDB_cursor *cdst)
                                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]);
                }
        }
        {
@@ -8222,14 +8262,16 @@ mdb_cursor_del0(MDB_cursor *mc)
                        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]);
                        }
                }
        }
@@ -8266,9 +8308,15 @@ mdb_cursor_del0(MDB_cursor *mc)
                                        }
                                        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);
                                                }
                                        }
                                }
@@ -8753,12 +8801,8 @@ mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, pgno_t newpgno
                                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)));
@@ -8799,23 +8843,26 @@ mdb_put(MDB_txn *txn, MDB_dbi dbi,
 #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. */
@@ -8834,20 +8881,16 @@ mdb_env_copythr(void *arg)
 #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();
@@ -8863,8 +8906,7 @@ again:
                        }
                }
                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]) {
@@ -8875,34 +8917,41 @@ again:
                }
                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)
 {
@@ -8964,6 +9013,7 @@ 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;
@@ -8986,7 +9036,6 @@ mdb_env_cwalk(mdb_copy *my, pgno_t *pg, int flags)
                                                                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;
 
@@ -9064,47 +9113,56 @@ mdb_env_copyfd1(MDB_env *env, HANDLE fd)
 {
        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);
@@ -9120,57 +9178,64 @@ mdb_env_copyfd1(MDB_env *env, HANDLE fd)
        *(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. */
@@ -9329,17 +9394,12 @@ mdb_env_copy2(MDB_env *env, const char *path, unsigned int flags)
        }
 
        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
        }