changeset: 6923:42aa8b19da95
user:      Kevin McCarthy <ke...@8t8.us>
date:      Sat Feb 04 12:53:38 2017 -0800
link:      http://dev.mutt.org/hg/mutt/rev/42aa8b19da95

Add LMDB backend support for header cache. (see #3691)

Based on the original from JP Mens:
https://gist.github.com/jpmens/15969d9d678a3d450e4e

The following performance patch was manually applied on top of the
original patch:
https://github.com/neomutt/neomutt/commit/7e5380cd4c40d119ff83b2cf5f51f2cdb8a95ab3

A variant of this patch was added to handle larger mailboxes:
https://github.com/neomutt/neomutt/commit/6d337642e701b1dde4b5d0812e01c85f41ba65ca

Thanks to all the developers and contributors to this patch, and to
Fabian Groffen for bundling and posting this to mutt-dev.

changeset: 6924:fca7e504ab6a
user:      Kevin McCarthy <ke...@8t8.us>
date:      Sat Feb 04 12:53:52 2017 -0800
link:      http://dev.mutt.org/hg/mutt/rev/fca7e504ab6a

Fixes to the LMDB header cache. (closes #3691)

Use mdb_txn_abort() to free up readonly/reset transactions, and avoid
leaking memory.

Fix hcache_delete() key generation - looks like this was an incorrect
copy/paste from the bdb code.

Use dprint, not fprintf, for debugging messages.

Remove strange blending of enum and bitfield logic for the txn_mode
state.

Remove some duplicate code from store/fetch raw.

diffs (458 lines):

diff -r 142a87f0c855 -r fca7e504ab6a configure.ac
--- a/configure.ac      Tue Jan 31 15:02:21 2017 -0800
+++ b/configure.ac      Sat Feb 04 12:53:52 2017 -0800
@@ -881,6 +881,7 @@
 AC_ARG_WITH(qdbm, AS_HELP_STRING([--without-qdbm],[Don't use qdbm even if it 
is available]))
 AC_ARG_WITH(gdbm, AS_HELP_STRING([--without-gdbm],[Don't use gdbm even if it 
is available]))
 AC_ARG_WITH(bdb, AS_HELP_STRING([--with-bdb@<:@=DIR@:>@],[Use BerkeleyDB4 if 
gdbm is not available]))
+AC_ARG_WITH(lmdb, AS_HELP_STRING([--with-lmdb@<:@=DIR@:>@],[Use LMDB if gdbm 
is not available]))
 
 db_found=no
 if test x$enable_hcache = xyes
@@ -925,6 +926,15 @@
         db_requested=bdb
       fi
     fi
+    if test -n "$with_lmdb" && test "$with_lmdb" != "no"
+    then
+      if test "$db_requested" != "auto"
+      then
+        AC_MSG_ERROR([more than one header cache engine requested.])
+      else
+        db_requested=lmdb
+      fi
+    fi
     
     dnl -- Tokyo Cabinet --
     if test "$with_tokyocabinet" != "no" \
@@ -1012,7 +1022,8 @@
 
     dnl -- BDB --
     ac_bdb_prefix="$with_bdb"
-    if test x$ac_bdb_prefix != xno && test $db_found = no
+    if test x$with_bdb != xno && test $db_found = no \
+           && test "$db_requested" = auto -o "$db_requested" = bdb
     then
         if test x$ac_bdb_prefix = xyes || test x$ac_bdb_prefix = x
         then
@@ -1068,6 +1079,34 @@
         fi
     fi
 
+    dnl -- LMDB --
+    if test x$with_lmdb != xno && test $db_found = no \
+           && test "$db_requested" = auto -o "$db_requested" = lmdb
+    then
+        if test "$with_lmdb" != "yes"
+        then
+          CPPFLAGS="$CPPFLAGS -I$with_lmdb/include"
+          LDFLAGS="$LDFLAGS -L$with_lmdb/lib"
+        fi
+        saved_LIBS="$LIBS"
+        LIBS="$LIBS -llmdb"
+        AC_CACHE_CHECK(for mdb_env_create, ac_cv_mdbenvcreate,[
+            ac_cv_mdbenvcreate=no
+            AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <lmdb.h>]], 
[[mdb_env_create(0);]])],[ac_cv_mdbenvcreate=yes],[])
+        ])
+        LIBS="$saved_LIBS"
+        if test "$ac_cv_mdbenvcreate" = yes
+        then
+          AC_DEFINE(HAVE_LMDB, 1, [LMDB Support])
+          MUTTLIBS="$MUTTLIBS -llmdb"
+          db_found=lmdb
+        fi
+        if test "$db_requested" != auto && test "$db_found" != "$db_requested"
+        then
+          AC_MSG_ERROR([LMDB could not be used. Check config.log for details.])
+        fi
+    fi
+
     if test $db_found = no
     then
         AC_MSG_ERROR([You need Tokyo Cabinet, QDBM, GDBM or Berkeley DB4 for 
hcache])
diff -r 142a87f0c855 -r fca7e504ab6a hcache.c
--- a/hcache.c  Tue Jan 31 15:02:21 2017 -0800
+++ b/hcache.c  Sat Feb 04 12:53:52 2017 -0800
@@ -32,6 +32,12 @@
 #include <gdbm.h>
 #elif HAVE_DB4
 #include <db.h>
+#elif HAVE_LMDB
+/* This is the maximum size of the database file (2GiB) which is
+ * mmap(2)'ed into memory.  This limit should be good for ~800,000
+ * emails. */
+#define LMDB_DB_SIZE    2147483648
+#include <lmdb.h>
 #endif
 
 #include <errno.h>
@@ -83,6 +89,78 @@
 
 static void mutt_hcache_dbt_init(DBT * dbt, void *data, size_t len);
 static void mutt_hcache_dbt_empty_init(DBT * dbt);
+#elif HAVE_LMDB
+enum mdb_txn_mode
+{
+  txn_uninitialized = 0,
+  txn_read,
+  txn_write
+};
+struct header_cache
+{
+  MDB_env *env;
+  MDB_txn *txn;
+  MDB_dbi db;
+  char *folder;
+  unsigned int crc;
+  enum mdb_txn_mode txn_mode;
+};
+
+static int mdb_get_r_txn(header_cache_t *h)
+{
+  int rc;
+
+  if (h->txn)
+  {
+    if (h->txn_mode == txn_read || h->txn_mode == txn_write)
+      return MDB_SUCCESS;
+
+    if ((rc = mdb_txn_renew (h->txn)) != MDB_SUCCESS)
+    {
+      h->txn = NULL;
+      dprint (2, (debugfile, "mdb_get_r_txn: mdb_txn_renew: %s\n",
+                  mdb_strerror (rc)));
+      return rc;
+    }
+    h->txn_mode = txn_read;
+    return rc;
+  }
+
+  if ((rc = mdb_txn_begin (h->env, NULL, MDB_RDONLY, &h->txn)) != MDB_SUCCESS)
+  {
+    h->txn = NULL;
+    dprint (2, (debugfile, "mdb_get_r_txn: mdb_txn_begin: %s\n",
+                mdb_strerror (rc)));
+    return rc;
+  }
+  h->txn_mode = txn_read;
+  return rc;
+}
+
+static int mdb_get_w_txn(header_cache_t *h)
+{
+  int rc;
+
+  if (h->txn)
+  {
+    if (h->txn_mode == txn_write)
+      return MDB_SUCCESS;
+
+    /* Free up the memory for readonly or reset transactions */
+    mdb_txn_abort (h->txn);
+  }
+
+  if ((rc = mdb_txn_begin (h->env, NULL, 0, &h->txn)) != MDB_SUCCESS)
+  {
+    h->txn = NULL;
+    dprint (2, (debugfile, "mdb_get_w_txn: mdb_txn_begin %s\n",
+                mdb_strerror (rc)));
+    return rc;
+  }
+
+  h->txn_mode = txn_write;
+  return rc;
+}
 #endif
 
 typedef union
@@ -732,11 +810,14 @@
 #elif HAVE_DB4
   DBT key;
   DBT data;
+#elif HAVE_LMDB
+  MDB_val key;
+  MDB_val data;
 #endif
-  
+
   if (!h)
     return NULL;
-  
+
 #ifdef HAVE_DB4
   if (filename[0] == '/')
     filename++;
@@ -744,19 +825,20 @@
   mutt_hcache_dbt_init(&key, (void *) filename, keylen(filename));
   mutt_hcache_dbt_empty_init(&data);
   data.flags = DB_DBT_MALLOC;
-  
+
   h->db->get(h->db, NULL, &key, &data, 0);
-  
+
   return data.data;
-#else
+#endif
+
   strncpy(path, h->folder, sizeof (path));
   safe_strcat(path, sizeof (path), filename);
 
   ksize = strlen (h->folder) + keylen (path + strlen (h->folder));  
-#endif
+
 #ifdef HAVE_QDBM
   data = vlget(h->db, path, ksize, NULL);
-  
+
   return data;
 #elif HAVE_TC
   data = tcbdbget(h->db, path, ksize, &sp);
@@ -765,10 +847,21 @@
 #elif HAVE_GDBM
   key.dptr = path;
   key.dsize = ksize;
-  
+
   data = gdbm_fetch(h->db, key);
-  
+
   return data.dptr;
+#elif HAVE_LMDB
+  key.mv_data = path;
+  key.mv_size = ksize;
+  if ((mdb_get_r_txn (h) != MDB_SUCCESS) ||
+      (mdb_get (h->txn, h->db, &key, &data) != MDB_SUCCESS))
+    return NULL;
+
+  /* Unlike other dbs, LMDB claims ownership of the returned data */
+  char *d = safe_malloc (data.mv_size);
+  memcpy (d, data.mv_data, data.mv_size);
+  return d;
 #endif
 }
 
@@ -813,30 +906,35 @@
 #elif HAVE_DB4
   DBT key;
   DBT databuf;
+#elif HAVE_LMDB
+  MDB_val key;
+  MDB_val databuf;
+  int rc;
 #endif
-  
+
   if (!h)
     return -1;
 
 #if HAVE_DB4
   if (filename[0] == '/')
     filename++;
-  
+
   mutt_hcache_dbt_init(&key, (void *) filename, keylen(filename));
-  
+
   mutt_hcache_dbt_empty_init(&databuf);
   databuf.flags = DB_DBT_USERMEM;
   databuf.data = data;
   databuf.size = dlen;
   databuf.ulen = dlen;
-  
+
   return h->db->put(h->db, NULL, &key, &databuf, 0);
-#else
+#endif
+
   strncpy(path, h->folder, sizeof (path));
   safe_strcat(path, sizeof (path), filename);
 
   ksize = strlen(h->folder) + keylen(path + strlen(h->folder));
-#endif
+
 #if HAVE_QDBM
   return vlput(h->db, path, ksize, data, dlen, VL_DOVER);
 #elif HAVE_TC
@@ -844,11 +942,28 @@
 #elif HAVE_GDBM
   key.dptr = path;
   key.dsize = ksize;
-  
+
   databuf.dsize = dlen;
   databuf.dptr = data;
-  
+
   return gdbm_store(h->db, key, databuf, GDBM_REPLACE);
+#elif HAVE_LMDB
+  key.mv_data = path;
+  key.mv_size = ksize;
+  databuf.mv_data = data;
+  databuf.mv_size = dlen;
+  if ((rc = mdb_get_w_txn (h)) != MDB_SUCCESS)
+    return rc;
+
+  if ((rc = mdb_put (h->txn, h->db, &key, &databuf, 0)) != MDB_SUCCESS)
+  {
+    dprint (2, (debugfile, "mutt_hcache_store_raw: mdb_put: %s\n",
+                mdb_strerror(rc)));
+    mdb_txn_abort (h->txn);
+    h->txn_mode = txn_uninitialized;
+    h->txn = NULL;
+  }
+  return rc;
 #endif
 }
 
@@ -1134,6 +1249,117 @@
   mutt_hcache_dbt_init(&key, (void *) filename, keylen(filename));
   return h->db->del(h->db, NULL, &key, 0);
 }
+#elif HAVE_LMDB
+
+static int
+hcache_open_lmdb (struct header_cache* h, const char* path)
+{
+  int rc;
+
+  h->txn = NULL;
+
+  if ((rc = mdb_env_create(&h->env)) != MDB_SUCCESS)
+  {
+    dprint (2, (debugfile, "hcache_open_lmdb: mdb_env_create: %s\n",
+                mdb_strerror(rc)));
+    return -1;
+  }
+
+  mdb_env_set_mapsize(h->env, LMDB_DB_SIZE);
+
+  if ((rc = mdb_env_open(h->env, path, MDB_NOSUBDIR, 0644)) != MDB_SUCCESS)
+  {
+    dprint (2, (debugfile, "hcache_open_lmdb: mdb_env_open: %s\n",
+                mdb_strerror(rc)));
+    goto fail_env;
+  }
+
+  if ((rc = mdb_get_r_txn(h)) != MDB_SUCCESS)
+    goto fail_env;
+
+  if ((rc = mdb_dbi_open(h->txn, NULL, MDB_CREATE, &h->db)) != MDB_SUCCESS)
+  {
+    dprint (2, (debugfile, "hcache_open_lmdb: mdb_dbi_open: %s\n",
+                mdb_strerror(rc)));
+    goto fail_dbi;
+  }
+
+  mdb_txn_reset(h->txn);
+  h->txn_mode = txn_uninitialized;
+  return 0;
+
+fail_dbi:
+  mdb_txn_abort(h->txn);
+  h->txn_mode = txn_uninitialized;
+  h->txn = NULL;
+
+fail_env:
+  mdb_env_close(h->env);
+  return -1;
+}
+
+void
+mutt_hcache_close(header_cache_t *h)
+{
+  int rc;
+
+  if (!h)
+    return;
+
+  if (h->txn)
+  {
+    if (h->txn_mode == txn_write)
+    {
+      if ((rc = mdb_txn_commit (h->txn)) != MDB_SUCCESS)
+      {
+        dprint (2, (debugfile, "mutt_hcache_close: mdb_txn_commit: %s\n",
+                    mdb_strerror (rc)));
+      }
+    }
+    else
+      mdb_txn_abort (h->txn);
+    h->txn_mode = txn_uninitialized;
+    h->txn = NULL;
+  }
+
+  mdb_env_close(h->env);
+  FREE (&h->folder);
+  FREE (&h);
+}
+
+int
+mutt_hcache_delete(header_cache_t *h, const char *filename,
+                  size_t(*keylen) (const char *fn))
+{
+  char path[_POSIX_PATH_MAX];
+  int ksize;
+  MDB_val key;
+  int rc;
+
+  if (!h)
+    return -1;
+
+  strncpy(path, h->folder, sizeof (path));
+  safe_strcat(path, sizeof (path), filename);
+  ksize = strlen (h->folder) + keylen (path + strlen (h->folder));
+
+  key.mv_data = path;
+  key.mv_size = ksize;
+  if (mdb_get_w_txn(h) != MDB_SUCCESS)
+    return -1;
+  rc = mdb_del(h->txn, h->db, &key, NULL);
+  if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND)
+  {
+    dprint (2, (debugfile, "mutt_hcache_delete: mdb_del: %s\n",
+                mdb_strerror (rc)));
+    mdb_txn_abort(h->txn);
+    h->txn_mode = txn_uninitialized;
+    h->txn = NULL;
+    return -1;
+  }
+
+  return 0;
+}
 #endif
 
 header_cache_t *
@@ -1151,6 +1377,8 @@
   hcache_open = hcache_open_gdbm;
 #elif HAVE_DB4
   hcache_open = hcache_open_db4;
+#elif HAVE_LMDB
+  hcache_open = hcache_open_lmdb;
 #endif
 
   /* Calculate the current hcache version from dynamic configuration */
@@ -1188,7 +1416,11 @@
     hcachever = digest.intval;
   }
 
+#if HAVE_LMDB
+  h->db = 0;
+#else
   h->db = NULL;
+#endif
   h->folder = get_foldername(folder);
   h->crc = hcachever;
 
@@ -1223,6 +1455,11 @@
 {
   return DB_VERSION_STRING;
 }
+#elif HAVE_LMDB
+const char *mutt_hcache_backend (void)
+{
+  return "lmdb " MDB_VERSION_STRING;
+}
 #elif HAVE_GDBM
 const char *mutt_hcache_backend (void)
 {

Reply via email to