#3691: Add LMDB support
---------------------------+----------------------
  Reporter:  rsc           |      Owner:  mutt-dev
      Type:  enhancement   |     Status:  new
  Priority:  minor         |  Milestone:
 Component:  header cache  |    Version:
Resolution:                |   Keywords:
---------------------------+----------------------

Comment (by gahr2):

 I have an alternative approach, which is, let's keep the last (read /
 write) transaction open and reuse it until a transaction for a different
 mode (write / read) is required. At that point, reset it if it was a read
 transaction and commit it if it was a write transaction. I noticed a
 relevant speed up with this patch.


 {{{#!diff
 diff -r f1f1af650910 configure.ac
 --- a/configure.ac      Tue May 24 12:08:46 2016 -0700
 +++ b/configure.ac      Mon Sep 19 14:18:01 2016 +0000
 @@ -855,6 +855,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
 @@ -899,6 +900,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" \
 @@ -1042,6 +1052,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 f1f1af650910 hcache.c
 --- a/hcache.c  Tue May 24 12:08:46 2016 -0700
 +++ b/hcache.c  Mon Sep 19 14:18:01 2016 +0000
 @@ -32,6 +32,9 @@
  #include <gdbm.h>
  #elif HAVE_DB4
  #include <db.h>
 +#elif HAVE_LMDB
 +#define LMDB_DB_SIZE    (1024 * 1024 * 1024)
 +#include <lmdb.h>
  #endif

  #include <errno.h>
 @@ -83,6 +86,52 @@

  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
 +struct header_cache
 +{
 +  MDB_env *env;
 +  MDB_txn *txn;
 +  MDB_dbi db;
 +  char *folder;
 +  unsigned int crc;
 +  int last_mode; // 0: read, 1: write
 +};
 +
 +static int mdb_get_r_txn(header_cache_t *h)
 +{
 +  if (h->txn && h->last_mode == 1)
 +  {
 +    mdb_txn_commit(h->txn);
 +    h->txn = NULL;
 +  }
 +  h->last_mode = 0;
 +  if (!h->txn)
 +  {
 +    return mdb_txn_begin(h->env, NULL, MDB_RDONLY, &h->txn);
 +  }
 +  else
 +  {
 +    return mdb_txn_renew(h->txn);
 +  }
 +}
 +
 +static int mdb_get_w_txn(header_cache_t *h)
 +{
 +  if (h->txn && h->last_mode == 0)
 +  {
 +    mdb_txn_reset(h->txn);
 +    h->txn = NULL;
 +  }
 +  h->last_mode = 1;
 +  if (h->txn)
 +  {
 +    return MDB_SUCCESS;
 +  }
 +  else
 +  {
 +    return mdb_txn_begin(h->env, NULL, 0, &h->txn);
 +  }
 +}
  #endif

  typedef union
 @@ -732,6 +793,11 @@
  #elif HAVE_DB4
    DBT key;
    DBT data;
 +#elif HAVE_LMDB
 +  MDB_val key;
 +  MDB_val data;
 +  size_t folderlen;
 +  int rc;
  #endif

    if (!h)
 @@ -748,6 +814,43 @@
    h->db->get(h->db, NULL, &key, &data, 0);

    return data.data;
 +#elif HAVE_LMDB
 +  strncpy(path, h->folder, sizeof (path));
 +  safe_strcat(path, sizeof (path), filename);
 +
 +  folderlen = strlen(h->folder);
 +  ksize = folderlen + keylen(path + folderlen);
 +  key.mv_data = (char *)path;
 +  key.mv_size = ksize;
 +  data.mv_data = NULL;
 +  data.mv_size = 0;
 +  rc = mdb_get_r_txn(h);
 +  if (rc != MDB_SUCCESS)
 +  {
 +    h->txn = NULL;
 +    fprintf(stderr, "txn_renew: %s\n", mdb_strerror(rc));
 +    return NULL;
 +  }
 +  rc = mdb_get(h->txn, h->db, &key, &data);
 +  if (rc == MDB_NOTFOUND)
 +  {
 +    mdb_txn_reset(h->txn);
 +    return NULL;
 +  }
 +  if (rc != MDB_SUCCESS)
 +  {
 +    fprintf(stderr, "mdb_get: %s\n", mdb_strerror(rc));
 +    mdb_txn_reset(h->txn);
 +    return NULL;
 +  }
 +  /* Caller frees the data we return, so I MUST make a copy of it */
 +
 +  char *d = safe_malloc(data.mv_size);
 +  memcpy(d, data.mv_data, data.mv_size);
 +  mdb_txn_reset(h->txn);
 +
 +  return d;
 +
  #else
    strncpy(path, h->folder, sizeof (path));
    safe_strcat(path, sizeof (path), filename);
 @@ -813,6 +916,11 @@
  #elif HAVE_DB4
    DBT key;
    DBT databuf;
 +#elif HAVE_LMDB
 +  MDB_val key;
 +  MDB_val databuf;
 +  size_t folderlen;
 +  int rc;
  #endif

    if (!h)
 @@ -831,6 +939,30 @@
    databuf.ulen = dlen;

    return h->db->put(h->db, NULL, &key, &databuf, 0);
 +#elif HAVE_LMDB
 +  folderlen = strlen(h->folder);
 +  strncpy(path, h->folder, sizeof (path));
 +  safe_strcat(path, sizeof (path), filename);
 +  ksize = folderlen + keylen(path + folderlen);
 +
 +  key.mv_data = (char *)path;
 +  key.mv_size = ksize;
 +  databuf.mv_data = data;
 +  databuf.mv_size = dlen;
 +  rc = mdb_get_w_txn(h);
 +  if (rc != MDB_SUCCESS)
 +  {
 +    fprintf(stderr, "txn_begin: %s\n", mdb_strerror(rc));
 +    return rc;
 +  }
 +  rc = mdb_put(h->txn, h->db, &key, &databuf, 0);
 +  if (rc != MDB_SUCCESS)
 +  {
 +    fprintf(stderr, "mdb_put: %s\n", mdb_strerror(rc));
 +    mdb_txn_abort(h->txn);
 +    return rc;
 +  }
 +  return rc;
  #else
    strncpy(path, h->folder, sizeof (path));
    safe_strcat(path, sizeof (path), filename);
 @@ -1134,6 +1266,108 @@
    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;
 +
 +  rc = mdb_env_create(&h->env);
 +  if (rc != MDB_SUCCESS)
 +  {
 +    fprintf(stderr, "hcache_open_lmdb: mdb_env_create: %s",
 mdb_strerror(rc));
 +    return -1;
 +  }
 +
 +  mdb_env_set_mapsize(h->env, LMDB_DB_SIZE);
 +
 +  rc = mdb_env_open(h->env, path, MDB_NOSUBDIR, 0644);
 +  if (rc != MDB_SUCCESS)
 +  {
 +    fprintf(stderr, "hcache_open_lmdb: mdb_env_open: %s",
 mdb_strerror(rc));
 +    goto fail_env;
 +  }
 +
 +  rc = mdb_get_r_txn(h);
 +  if (rc != MDB_SUCCESS)
 +  {
 +      fprintf(stderr, "hcache_open_lmdb: mdb_txn_begin: %s",
 mdb_strerror(rc));
 +      goto fail_env;
 +  }
 +
 +  rc = mdb_dbi_open(h->txn, NULL, MDB_CREATE, &h->db);
 +  if (rc != MDB_SUCCESS)
 +  {
 +    fprintf(stderr, "hcache_open_lmdb: mdb_dbi_open: %s",
 mdb_strerror(rc));
 +    goto fail_dbi;
 +  }
 +
 +  mdb_txn_reset(h->txn);
 +  return 0;
 +
 +fail_dbi:
 +  mdb_txn_abort(h->txn);
 +  h->txn = NULL;
 +
 +fail_env:
 +  mdb_env_close(h->env);
 +  return -1;
 +}
 +
 +void
 +mutt_hcache_close(header_cache_t *h)
 +{
 +  if (!h)
 +    return;
 +
 +  if (h->txn && h->last_mode == 1)
 +  {
 +    mdb_txn_commit(h->txn);
 +  }
 +
 +  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))
 +{
 +  MDB_val key;
 +  MDB_txn *txn;
 +  int rc;
 +
 +  if (!h)
 +    return -1;
 +
 +  if (filename[0] == '/')
 +    filename++;
 +
 +  key.mv_data = (char *)filename;
 +  key.mv_size = strlen(filename);
 +  rc = mdb_get_w_txn(h);
 +  if (rc != MDB_SUCCESS)
 +  {
 +    fprintf(stderr, "txn_begin: %s\n", mdb_strerror(rc));
 +    return rc;
 +  }
 +  rc = mdb_del(txn, h->db, &key, NULL);
 +  if (rc != MDB_SUCCESS)
 +  {
 +    if (rc != MDB_NOTFOUND)
 +    {
 +      fprintf(stderr, "mdb_del: %s\n", mdb_strerror(rc));
 +    }
 +    mdb_txn_abort(txn);
 +    return rc;
 +  }
 +
 +  return rc;
 +}
  #endif

  header_cache_t *
 @@ -1151,6 +1385,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 +1424,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 +1463,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)
  {

 }}}

--
Ticket URL: <https://dev.mutt.org/trac/ticket/3691#comment:8>
Mutt <http://www.mutt.org/>
The Mutt mail user agent

Reply via email to