diff options
Diffstat (limited to 'tcl8.6/pkgs/thread2.8.4/generic/psLmdb.c')
-rw-r--r-- | tcl8.6/pkgs/thread2.8.4/generic/psLmdb.c | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/tcl8.6/pkgs/thread2.8.4/generic/psLmdb.c b/tcl8.6/pkgs/thread2.8.4/generic/psLmdb.c new file mode 100644 index 0000000..90900e6 --- /dev/null +++ b/tcl8.6/pkgs/thread2.8.4/generic/psLmdb.c @@ -0,0 +1,545 @@ +/* + * This file implements wrappers for persistent lmdb storage for the + * shared variable arrays. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * ---------------------------------------------------------------------------- + */ + +#ifdef HAVE_LMDB + +#include "threadSvCmd.h" +#include <lmdb.h> + +/* + * Structure keeping the lmdb environment context + */ +typedef struct { + MDB_env * env; // Environment + MDB_txn * txn; // Last active read transaction + MDB_cursor * cur; // Cursor used for ps_lmdb_first and ps_lmdb_next + MDB_dbi dbi; // Open database (default db) + int err; // Last error (used in ps_lmdb_geterr) +} * LmdbCtx; + +/* + * Transaction and DB open mode + */ +enum LmdbOpenMode { LmdbRead, LmdbWrite }; + +// Initialize or renew a transaction. +static void LmdbTxnGet(LmdbCtx ctx, enum LmdbOpenMode mode); + +// Commit a transaction. +static void LmdbTxnCommit(LmdbCtx ctx); + +// Abort a transaction +static void LmdbTxnAbort(LmdbCtx ctx); + +void LmdbTxnGet(LmdbCtx ctx, enum LmdbOpenMode mode) +{ + // Read transactions are reused, if possible + if (ctx->txn && mode == LmdbRead) + { + ctx->err = mdb_txn_renew(ctx->txn); + if (ctx->err) + { + ctx->txn = NULL; + } + } + else if (ctx->txn && mode == LmdbWrite) + { + LmdbTxnAbort(ctx); + } + + if (ctx->txn == NULL) + { + ctx->err = mdb_txn_begin(ctx->env, NULL, 0, &ctx->txn); + } + + if (ctx->err) + { + ctx->txn = NULL; + return; + } + + // Given the setup above, and the arguments given, this won't fail. + mdb_dbi_open(ctx->txn, NULL, 0, &ctx->dbi); +} + +void LmdbTxnCommit(LmdbCtx ctx) +{ + ctx->err = mdb_txn_commit(ctx->txn); + ctx->txn = NULL; +} + +void LmdbTxnAbort(LmdbCtx ctx) +{ + mdb_txn_abort(ctx->txn); + ctx->txn = NULL; +} + +/* + * Functions implementing the persistent store interface + */ + +static ps_open_proc ps_lmdb_open; +static ps_close_proc ps_lmdb_close; +static ps_get_proc ps_lmdb_get; +static ps_put_proc ps_lmdb_put; +static ps_first_proc ps_lmdb_first; +static ps_next_proc ps_lmdb_next; +static ps_delete_proc ps_lmdb_delete; +static ps_free_proc ps_lmdb_free; +static ps_geterr_proc ps_lmdb_geterr; + +/* + * This structure collects all the various pointers + * to the functions implementing the lmdb store. + */ + +const PsStore LmdbStore = { + "lmdb", + NULL, + ps_lmdb_open, + ps_lmdb_get, + ps_lmdb_put, + ps_lmdb_first, + ps_lmdb_next, + ps_lmdb_delete, + ps_lmdb_close, + ps_lmdb_free, + ps_lmdb_geterr, + NULL +}; + +/* + *----------------------------------------------------------------------------- + * + * Sv_RegisterLmdbStore -- + * + * Register the lmdb store with shared variable implementation. + * + * Results: + * None. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ +void +Sv_RegisterLmdbStore(void) +{ + Sv_RegisterPsStore(&LmdbStore); +} + +/* + *----------------------------------------------------------------------------- + * + * ps_lmdb_open -- + * + * Opens the lmdb-based persistent storage. + * + * Results: + * Opaque handle for LmdbCtx. + * + * Side effects: + * The lmdb file might be created if not found. + * + *----------------------------------------------------------------------------- + */ +static ClientData +ps_lmdb_open( + const char *path) +{ + LmdbCtx ctx; + + char *ext; + Tcl_DString toext; + + ctx = ckalloc(sizeof(*ctx)); + if (ctx == NULL) + { + return NULL; + } + + ctx->env = NULL; + ctx->txn = NULL; + ctx->cur = NULL; + ctx->dbi = 0; + + ctx->err = mdb_env_create(&ctx->env); + if (ctx->err) + { + ckfree(ctx); + return NULL; + } + + Tcl_DStringInit(&toext); + ext = Tcl_UtfToExternalDString(NULL, path, strlen(path), &toext); + ctx->err = mdb_env_open(ctx->env, ext, MDB_NOSUBDIR|MDB_NOLOCK, 0666); + Tcl_DStringFree(&toext); + + if (ctx->err) + { + ckfree(ctx); + return NULL; + } + + return (ClientData)ctx; +} + +/* + *----------------------------------------------------------------------------- + * + * ps_lmdb_close -- + * + * Closes the lmdb-based persistent storage. + * + * Results: + * 0 - ok + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ +static int +ps_lmdb_close( + ClientData handle) +{ + LmdbCtx ctx = (LmdbCtx)handle; + if (ctx->cur) + { + mdb_cursor_close(ctx->cur); + } + if (ctx->txn) + { + LmdbTxnAbort(ctx); + } + + mdb_env_close(ctx->env); + ckfree(ctx); + + return 0; +} + +/* + *----------------------------------------------------------------------------- + * + * ps_lmdb_get -- + * + * Retrieves data for the key from the lmdb storage. + * + * Results: + * 1 - no such key + * 0 - ok + * + * Side effects: + * Data returned must be copied, then psFree must be called. + * + *----------------------------------------------------------------------------- + */ +static int +ps_lmdb_get( + ClientData handle, + const char *keyptr, + char **dataptrptr, + size_t *lenptr) +{ + LmdbCtx ctx = (LmdbCtx)handle; + MDB_val key, data; + + LmdbTxnGet(ctx, LmdbRead); + if (ctx->err) + { + return 1; + } + + key.mv_data = (void *)keyptr; + key.mv_size = strlen(keyptr) + 1; + + ctx->err = mdb_get(ctx->txn, ctx->dbi, &key, &data); + if (ctx->err) + { + mdb_txn_reset(ctx->txn); + return 1; + } + + *dataptrptr = data.mv_data; + *lenptr = data.mv_size; + + /* + * Transaction is left open at this point, so that the caller can get ahold + * of the data and make a copy of it. Afterwards, it will call ps_lmdb_free + * to free the data, and we'll catch the chance to reset the transaction + * there. + */ + + return 0; +} + +/* + *----------------------------------------------------------------------------- + * + * ps_lmdb_first -- + * + * Starts the iterator over the lmdb file and returns the first record. + * + * Results: + * 1 - no more records in the iterator + * 0 - ok + * + * Side effects: + * Data returned must be copied, then psFree must be called. + * + *----------------------------------------------------------------------------- + */ +static int +ps_lmdb_first( + ClientData handle, + char **keyptrptr, + char **dataptrptr, + size_t *lenptr) +{ + LmdbCtx ctx = (LmdbCtx)handle; + MDB_val key, data; + + LmdbTxnGet(ctx, LmdbRead); + if (ctx->err) + { + return 1; + } + + ctx->err = mdb_cursor_open(ctx->txn, ctx->dbi, &ctx->cur); + if (ctx->err) + { + return 1; + } + + ctx->err = mdb_cursor_get(ctx->cur, &key, &data, MDB_FIRST); + if (ctx->err) + { + mdb_txn_reset(ctx->txn); + mdb_cursor_close(ctx->cur); + ctx->cur = NULL; + return 1; + } + + *dataptrptr = data.mv_data; + *lenptr = data.mv_size; + *keyptrptr = key.mv_data; + + return 0; +} + +/* + *----------------------------------------------------------------------------- + * + * ps_lmdb_next -- + * + * Uses the iterator over the lmdb file and returns the next record. + * + * Results: + * 1 - no more records in the iterator + * 0 - ok + * + * Side effects: + * Data returned must be copied, then psFree must be called. + * + *----------------------------------------------------------------------------- + */ +static int ps_lmdb_next( + ClientData handle, + char **keyptrptr, + char **dataptrptr, + size_t *lenptr) +{ + LmdbCtx ctx = (LmdbCtx)handle; + MDB_val key, data; + + ctx->err = mdb_cursor_get(ctx->cur, &key, &data, MDB_NEXT); + if (ctx->err) + { + mdb_txn_reset(ctx->txn); + mdb_cursor_close(ctx->cur); + ctx->cur = NULL; + return 1; + } + + *dataptrptr = data.mv_data; + *lenptr = data.mv_size; + *keyptrptr = key.mv_data; + + return 0; +} + +/* + *----------------------------------------------------------------------------- + * + * ps_lmdb_put -- + * + * Stores used data bound to a key in lmdb storage. + * + * Results: + * 0 - ok + * -1 - error; use ps_lmdb_geterr to retrieve the error message + * + * Side effects: + * If the key is already associated with some user data, this will + * be replaced by the new data chunk. + * + *----------------------------------------------------------------------------- + */ +static int +ps_lmdb_put( + ClientData handle, + const char *keyptr, + char *dataptr, + size_t len) +{ + LmdbCtx ctx = (LmdbCtx)handle; + MDB_val key, data; + + LmdbTxnGet(ctx, LmdbWrite); + if (ctx->err) + { + return -1; + } + + key.mv_data = (void*)keyptr; + key.mv_size = strlen(keyptr) + 1; + + data.mv_data = dataptr; + data.mv_size = len; + + ctx->err = mdb_put(ctx->txn, ctx->dbi, &key, &data, 0); + if (ctx->err) + { + LmdbTxnAbort(ctx); + } + else + { + LmdbTxnCommit(ctx); + } + + return ctx->err ? -1 : 0; +} + +/* + *----------------------------------------------------------------------------- + * + * ps_lmdb_delete -- + * + * Deletes the key and associated data from the lmdb storage. + * + * Results: + * 0 - ok + * -1 - error; use ps_lmdb_geterr to retrieve the error message + * + * Side effects: + * If the key is already associated with some user data, this will + * be replaced by the new data chunk. + * + *----------------------------------------------------------------------------- + */ +static int +ps_lmdb_delete( + ClientData handle, + const char *keyptr) +{ + LmdbCtx ctx = (LmdbCtx)handle; + MDB_val key; + + LmdbTxnGet(ctx, LmdbWrite); + if (ctx->err) + { + return -1; + } + + key.mv_data = (void*)keyptr; + key.mv_size = strlen(keyptr) + 1; + + ctx->err = mdb_del(ctx->txn, ctx->dbi, &key, NULL); + if (ctx->err) + { + LmdbTxnAbort(ctx); + } + else + { + LmdbTxnCommit(ctx); + } + + ctx->txn = NULL; + return ctx->err ? -1 : 0; +} + +/* + *----------------------------------------------------------------------------- + * + * ps_lmdb_free -- + * + * This function is called to free data returned by the persistent store + * after calls to psFirst, psNext, or psGet. Lmdb doesn't need to free any + * data, as the data returned is owned by lmdb. On the other hand, this + * method is required to reset the read transaction. This is done only + * when iteration is over (ctx->cur == NULL). + * + * Results: + * None. + * + * Side effects: + * Memory gets reclaimed. + * + *----------------------------------------------------------------------------- + */ +static void +ps_lmdb_free( + ClientData handle, + void *data) +{ + LmdbCtx ctx = (LmdbCtx)handle; + (void)data; + + if (ctx->cur == NULL) + { + mdb_txn_reset(ctx->txn); + } +} + +/* + *----------------------------------------------------------------------------- + * + * ps_lmdb_geterr -- + * + * Retrieves the textual representation of the error caused + * by the last lmdb command. + * + * Results: + * Pointer to the string message. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ +static const char* +ps_lmdb_geterr( + ClientData handle) +{ + LmdbCtx ctx = (LmdbCtx)handle; + return mdb_strerror(ctx->err); +} + +#endif /* HAVE_LMDB */ + +/* EOF $RCSfile*/ + +/* Emacs Setup Variables */ +/* Local Variables: */ +/* mode: C */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ |