diff --git a/libmdbx.files b/libmdbx.files index a07ea48d..26798029 100644 --- a/libmdbx.files +++ b/libmdbx.files @@ -44,6 +44,7 @@ test/osal-windows.cc test/osal.h test/test.cc test/test.h +test/try.cc test/utils.cc test/utils.h tutorial/README.md diff --git a/mdbx.h b/mdbx.h index 4d95ae25..d8453d05 100644 --- a/mdbx.h +++ b/mdbx.h @@ -327,6 +327,10 @@ typedef int(MDBX_cmp_func)(const MDBX_val *a, const MDBX_val *b); /* Store multiple data items in one call. Only for MDBX_DUPFIXED. */ #define MDBX_MULTIPLE 0x80000u +/* Transaction Flags*/ +/* Do not block when starting a write transaction */ +#define MDBX_TRYTXN 0x10000000u + /* Copy Flags */ /* Compacting copy: Omit free space from copy, and renumber all * pages sequentially. */ @@ -420,8 +424,10 @@ typedef enum MDBX_cursor_op { #define MDBX_BAD_DBI (-30780) /* Unexpected problem - txn should abort */ #define MDBX_PROBLEM (-30779) +/* Unexpected problem - txn should abort */ +#define MDBX_BUSY (-30778) /* The last defined error code */ -#define MDBX_LAST_ERRCODE MDBX_PROBLEM +#define MDBX_LAST_ERRCODE MDBX_BUSY /* The mdbx_put() or mdbx_replace() was called for key, that has more that one associated value. */ @@ -953,6 +959,9 @@ LIBMDBX_API int mdbx_env_set_assert(MDBX_env *env, MDBX_assert_func *func); * - MDBX_RDONLY * This transaction will not perform any write operations. * + * - MDBX_TRYTXN + * Do not block when starting a write transaction + * * [out] txn Address where the new MDBX_txn handle will be stored * * Returns A non-zero error value on failure and 0 on success, some @@ -964,7 +973,8 @@ LIBMDBX_API int mdbx_env_set_assert(MDBX_env *env, MDBX_assert_func *func); * as well. See mdbx_env_set_mapsize(). * - MDBX_READERS_FULL - a read-only transaction was requested and the reader * lock table is full. See mdbx_env_set_maxreaders(). - * - MDBX_ENOMEM - out of memory. */ + * - MDBX_ENOMEM - out of memory. + * - MDBX_BUSY - a write transaction is already started. */ LIBMDBX_API int mdbx_txn_begin(MDBX_env *env, MDBX_txn *parent, unsigned flags, MDBX_txn **txn); diff --git a/src/bits.h b/src/bits.h index 43c02ed7..88a8032d 100644 --- a/src/bits.h +++ b/src/bits.h @@ -553,7 +553,8 @@ struct MDBX_txn { /* Transaction Flags */ /* mdbx_txn_begin() flags */ -#define MDBX_TXN_BEGIN_FLAGS (MDBX_NOMETASYNC | MDBX_NOSYNC | MDBX_RDONLY) +#define MDBX_TXN_BEGIN_FLAGS_PERSISTENT (MDBX_NOMETASYNC | MDBX_NOSYNC | MDBX_RDONLY) +#define MDBX_TXN_BEGIN_FLAGS (MDBX_TXN_BEGIN_FLAGS_PERSISTENT | MDBX_TRYTXN) #define MDBX_TXN_NOMETASYNC \ MDBX_NOMETASYNC /* don't sync meta for this txn on commit */ #define MDBX_TXN_NOSYNC MDBX_NOSYNC /* don't sync this txn on commit */ diff --git a/src/lck-posix.c b/src/lck-posix.c index 5b882b0d..0c8cb41b 100644 --- a/src/lck-posix.c +++ b/src/lck-posix.c @@ -184,6 +184,13 @@ static int mdbx_robust_lock(MDBX_env *env, pthread_mutex_t *mutex) { return rc; } +static int mdbx_robust_trylock(MDBX_env *env, pthread_mutex_t *mutex) { + int rc = pthread_mutex_trylock(mutex); + if (unlikely(rc != 0)) + rc = mdbx_mutex_failed(env, mutex, rc); + return rc; +} + static int mdbx_robust_unlock(MDBX_env *env, pthread_mutex_t *mutex) { int rc = pthread_mutex_unlock(mutex); if (unlikely(rc != 0)) @@ -213,6 +220,13 @@ int mdbx_txn_lock(MDBX_env *env) { return MDBX_IS_ERROR(rc) ? rc : MDBX_SUCCESS; } +int mdbx_txn_trylock(MDBX_env *env) { + mdbx_trace(">>"); + int rc = mdbx_robust_trylock(env, &env->me_lck->mti_wmutex); + mdbx_trace("<< rc %d", rc); + return MDBX_IS_ERROR(rc) ? rc : MDBX_SUCCESS; +} + void mdbx_txn_unlock(MDBX_env *env) { mdbx_trace(">>"); int rc = mdbx_robust_unlock(env, &env->me_lck->mti_wmutex); @@ -313,7 +327,9 @@ static int __cold mdbx_mutex_failed(MDBX_env *env, pthread_mutex_t *mutex, #endif /* MDBX_USE_ROBUST */ mdbx_error("mutex (un)lock failed, %s", mdbx_strerror(rc)); - if (rc != EDEADLK) { + if (rc == EBUSY) { + rc = MDBX_BUSY; + } else if (rc != EDEADLK && rc != EBUSY) { env->me_flags |= MDBX_FATAL_ERROR; rc = MDBX_PANIC; } diff --git a/src/lck-windows.c b/src/lck-windows.c index 66591db9..d3340d58 100644 --- a/src/lck-windows.c +++ b/src/lck-windows.c @@ -132,6 +132,16 @@ int mdbx_txn_lock(MDBX_env *env) { return GetLastError(); } +int mdbx_txn_trylock(MDBX_env *env) { + if (flock(env->me_fd, LCK_EXCLUSIVE | LCK_DONTWAIT, LCK_BODY)) + return MDBX_SUCCESS; + int rc = GetLastError(); + if (rc == ERROR_LOCK_VIOLATION) { + rc = MDBX_BUSY; + } + return rc; +} + void mdbx_txn_unlock(MDBX_env *env) { if (!funlock(env->me_fd, LCK_BODY)) mdbx_panic("%s failed: errcode %u", mdbx_func_, GetLastError()); diff --git a/src/mdbx.c b/src/mdbx.c index f510c4ee..a74c9376 100644 --- a/src/mdbx.c +++ b/src/mdbx.c @@ -708,6 +708,7 @@ static const char *__mdbx_strerr(int errnum) { "DUPFIXED size", "MDBX_BAD_DBI: The specified DBI handle was closed/changed unexpectedly", "MDBX_PROBLEM: Unexpected problem - txn should abort", + "MDBX_BUSY: Another write transation is started", }; if (errnum >= MDBX_KEYEXIST && errnum <= MDBX_LAST_ERRCODE) { @@ -2562,7 +2563,7 @@ static int mdbx_txn_renew0(MDBX_txn *txn, unsigned flags) { } else { /* Not yet touching txn == env->me_txn0, it may be active */ mdbx_jitter4testing(false); - rc = mdbx_txn_lock(env); + rc = F_ISSET(flags, MDBX_TRYTXN) ? mdbx_txn_trylock(env) : mdbx_txn_lock(env); if (unlikely(rc)) return rc; @@ -2664,6 +2665,7 @@ int mdbx_txn_begin(MDBX_env *env, MDBX_txn *parent, unsigned flags, MDBX_txn **ret) { MDBX_txn *txn; MDBX_ntxn *ntxn; + //unsigned pflags; int rc, size, tsize; if (unlikely(!env || !ret)) @@ -2718,7 +2720,7 @@ int mdbx_txn_begin(MDBX_env *env, MDBX_txn *parent, unsigned flags, txn->mt_dbxs = env->me_dbxs; /* static */ txn->mt_dbs = (MDBX_db *)((char *)txn + tsize); txn->mt_dbflags = (uint8_t *)txn + size - env->me_maxdbs; - txn->mt_flags = flags; + txn->mt_flags = flags & MDBX_TXN_BEGIN_FLAGS_PERSISTENT; txn->mt_env = env; if (parent) { diff --git a/src/osal.h b/src/osal.h index 4216a534..ad9fd275 100644 --- a/src/osal.h +++ b/src/osal.h @@ -506,6 +506,7 @@ int mdbx_rdt_lock(MDBX_env *env); void mdbx_rdt_unlock(MDBX_env *env); int mdbx_txn_lock(MDBX_env *env); +int mdbx_txn_trylock(MDBX_env *env); void mdbx_txn_unlock(MDBX_env *env); int mdbx_rpid_set(MDBX_env *env); diff --git a/test/cases.cc b/test/cases.cc index 1311f12e..a1953f53 100644 --- a/test/cases.cc +++ b/test/cases.cc @@ -67,6 +67,7 @@ void testcase_setup(const char *casename, actor_params ¶ms, configure_actor(last_space_id, ac_hill, nullptr, params); configure_actor(last_space_id, ac_jitter, nullptr, params); configure_actor(last_space_id, ac_hill, nullptr, params); + configure_actor(last_space_id, ac_try, nullptr, params); log_notice("<<< testcase_setup(%s): done", casename); } else { failure("unknown testcase `%s`", casename); diff --git a/test/config.h b/test/config.h index 483fe9b5..aecfcd3e 100644 --- a/test/config.h +++ b/test/config.h @@ -20,7 +20,7 @@ #define ACTOR_ID_MAX INT16_MAX -enum actor_testcase { ac_none, ac_hill, ac_deadread, ac_deadwrite, ac_jitter }; +enum actor_testcase { ac_none, ac_hill, ac_deadread, ac_deadwrite, ac_jitter, ac_try }; enum actor_status { as_unknown, diff --git a/test/dead.cc b/test/dead.cc index f713b8b3..acea1193 100644 --- a/test/dead.cc +++ b/test/dead.cc @@ -25,7 +25,7 @@ bool testcase_deadread::setup() { bool testcase_deadread::run() { db_open(); - txn_begin(true); + txn_begin(MDBX_RDONLY); return true; } @@ -50,7 +50,7 @@ bool testcase_deadwrite::setup() { bool testcase_deadwrite::run() { db_open(); - txn_begin(false); + txn_begin(0); return true; } diff --git a/test/hill.cc b/test/hill.cc index 360eb29d..2b7aeb00 100644 --- a/test/hill.cc +++ b/test/hill.cc @@ -28,7 +28,7 @@ bool testcase_hill::setup() { bool testcase_hill::run() { db_open(); - txn_begin(false); + txn_begin(0); MDBX_dbi dbi = db_table_open(true); txn_end(false); @@ -70,7 +70,7 @@ bool testcase_hill::run() { uint64_t serial_count = 0; unsigned txn_nops = 0; if (!txn_guard) - txn_begin(false); + txn_begin(0); while (should_continue()) { const keygen::serial_t a_serial = serial_count; @@ -91,7 +91,7 @@ bool testcase_hill::run() { failure_perror("mdbx_put(insert-a.1)", rc); if (++txn_nops >= config.params.batch_write) { - txn_restart(false, false); + txn_restart(false, 0); txn_nops = 0; } @@ -104,7 +104,7 @@ bool testcase_hill::run() { failure_perror("mdbx_put(insert-b)", rc); if (++txn_nops >= config.params.batch_write) { - txn_restart(false, false); + txn_restart(false, 0); txn_nops = 0; } @@ -118,7 +118,7 @@ bool testcase_hill::run() { failure_perror("mdbx_put(update-a: 1->0)", rc); if (++txn_nops >= config.params.batch_write) { - txn_restart(false, false); + txn_restart(false, 0); txn_nops = 0; } @@ -129,7 +129,7 @@ bool testcase_hill::run() { failure_perror("mdbx_del(b)", rc); if (++txn_nops >= config.params.batch_write) { - txn_restart(false, false); + txn_restart(false, 0); txn_nops = 0; } @@ -164,7 +164,7 @@ bool testcase_hill::run() { failure_perror("mdbx_put(update-a: 0->1)", rc); if (++txn_nops >= config.params.batch_write) { - txn_restart(false, false); + txn_restart(false, 0); txn_nops = 0; } @@ -177,7 +177,7 @@ bool testcase_hill::run() { failure_perror("mdbx_put(insert-b)", rc); if (++txn_nops >= config.params.batch_write) { - txn_restart(false, false); + txn_restart(false, 0); txn_nops = 0; } @@ -189,7 +189,7 @@ bool testcase_hill::run() { failure_perror("mdbx_del(a)", rc); if (++txn_nops >= config.params.batch_write) { - txn_restart(false, false); + txn_restart(false, 0); txn_nops = 0; } @@ -200,7 +200,7 @@ bool testcase_hill::run() { failure_perror("mdbx_del(b)", rc); if (++txn_nops >= config.params.batch_write) { - txn_restart(false, false); + txn_restart(false, 0); txn_nops = 0; } @@ -212,7 +212,7 @@ bool testcase_hill::run() { if (dbi) { if (config.params.drop_table && !mode_readonly()) { - txn_begin(false); + txn_begin(0); db_table_drop(dbi); txn_end(false); } else diff --git a/test/jitter.cc b/test/jitter.cc index 92d272e1..27f208a3 100644 --- a/test/jitter.cc +++ b/test/jitter.cc @@ -30,7 +30,7 @@ bool testcase_jitter::run() { if (flipcoin()) { jitter_delay(); - txn_begin(true); + txn_begin(MDBX_RDONLY); fetch_canary(); jitter_delay(); txn_end(flipcoin()); @@ -51,7 +51,7 @@ bool testcase_jitter::run() { if (flipcoin()) { jitter_delay(); - txn_begin(true); + txn_begin(MDBX_RDONLY); jitter_delay(); txn_end(flipcoin()); } diff --git a/test/test.cc b/test/test.cc index 73e96986..6bb17735 100644 --- a/test/test.cc +++ b/test/test.cc @@ -29,6 +29,8 @@ const char *testcase2str(const actor_testcase testcase) { return "deadwrite"; case ac_jitter: return "jitter"; + case ac_try: + return "try"; } } @@ -175,18 +177,18 @@ void testcase::db_close() { log_trace("<< db_close"); } -void testcase::txn_begin(bool readonly) { - log_trace(">> txn_begin(%s)", readonly ? "read-only" : "read-write"); +void testcase::txn_begin(unsigned flags) { + log_trace(">> txn_begin(%s)", flags & MDBX_RDONLY ? "read-only" : "read-write"); assert(!txn_guard); MDBX_txn *txn = nullptr; int rc = - mdbx_txn_begin(db_guard.get(), nullptr, readonly ? MDBX_RDONLY : 0, &txn); + mdbx_txn_begin(db_guard.get(), nullptr, flags, &txn); if (unlikely(rc != MDBX_SUCCESS)) failure_perror("mdbx_txn_begin()", rc); txn_guard.reset(txn); - log_trace("<< txn_begin(%s)", readonly ? "read-only" : "read-write"); + log_trace("<< txn_begin(%s)", flags & MDBX_RDONLY ? "read-only" : "read-write"); } void testcase::txn_end(bool abort) { @@ -207,10 +209,10 @@ void testcase::txn_end(bool abort) { log_trace("<< txn_end(%s)", abort ? "abort" : "commit"); } -void testcase::txn_restart(bool abort, bool readonly) { +void testcase::txn_restart(bool abort, unsigned flags) { if (txn_guard) txn_end(abort); - txn_begin(readonly); + txn_begin(flags); } bool testcase::wait4start() { @@ -443,6 +445,9 @@ bool test_execute(const actor_config &config) { case ac_jitter: test.reset(new testcase_jitter(config, pid)); break; + case ac_try: + test.reset(new testcase_try(config, pid)); + break; default: test.reset(new testcase(config, pid)); break; diff --git a/test/test.h b/test/test.h index bae5eb0d..500caef0 100644 --- a/test/test.h +++ b/test/test.h @@ -104,9 +104,9 @@ protected: void db_prepare(); void db_open(); void db_close(); - void txn_begin(bool readonly); + void txn_begin(unsigned flags); void txn_end(bool abort); - void txn_restart(bool abort, bool readonly); + void txn_restart(bool abort, unsigned flags); void fetch_canary(); void update_canary(uint64_t increment); void kick_progress(bool active) const; @@ -130,8 +130,8 @@ protected: generate_pair(serial, key, data, data_age); } - bool mode_readonly() const { - return (config.params.mode_flags & MDBX_RDONLY) ? true : false; + unsigned mode_readonly() const { + return (config.params.mode_flags & MDBX_RDONLY) ? MDBX_RDONLY : 0; } public: @@ -190,3 +190,14 @@ public: bool run(); bool teardown(); }; + +class testcase_try : public testcase { + typedef testcase inherited; + +public: + testcase_try(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool setup(); + bool run(); + bool teardown(); +}; diff --git a/test/test.vcxproj b/test/test.vcxproj index 19f272b8..3ee13cf8 100644 --- a/test/test.vcxproj +++ b/test/test.vcxproj @@ -182,6 +182,7 @@ + diff --git a/test/try.cc b/test/try.cc new file mode 100644 index 00000000..4071c24c --- /dev/null +++ b/test/try.cc @@ -0,0 +1,38 @@ +#include "test.h" + +bool testcase_try::setup() { + log_trace(">> setup"); + if (!inherited::setup()) + return false; + + log_trace("<< setup"); + return true; +} + +bool testcase_try::run() { + db_open(); + assert(!txn_guard); + + MDBX_txn *txn = nullptr; + MDBX_txn *txn2 = nullptr; + int rc = + mdbx_txn_begin(db_guard.get(), nullptr, 0, &txn); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_txn_begin(MDBX_TRYTXN)", rc); + else { + rc = mdbx_txn_begin(db_guard.get(), nullptr, MDBX_TRYTXN, &txn2); + if (unlikely(rc != MDBX_BUSY)) + failure_perror("mdbx_txn_begin(MDBX_TRYTXN)", rc); + } + + txn_guard.reset(txn); + return true; +} + +bool testcase_try::teardown() { + log_trace(">> teardown"); + cursor_guard.release(); + txn_guard.release(); + db_guard.release(); + return inherited::teardown(); +}