diff --git a/cc/ss2pl/CMakeLists.txt b/cc/ss2pl/CMakeLists.txt index 4906a488..5aa2e28f 100644 --- a/cc/ss2pl/CMakeLists.txt +++ b/cc/ss2pl/CMakeLists.txt @@ -1,7 +1,15 @@ +if(CCBENCH_SS2PL_DLR STREQUAL "0") + set(SS2PL_DLR_MARKER DLR0) +elseif(CCBENCH_SS2PL_DLR STREQUAL "1") + set(SS2PL_DLR_MARKER DLR1) +else() + message(FATAL_ERROR "CCBENCH_SS2PL_DLR must be 0 or 1") +endif() + ccbench_add_protocol(ss2pl SOURCES transaction.cc util.cc WORKLOADS ycsb bomb tpcc OPTIONS - DLR1 + ${SS2PL_DLR_MARKER} KEY_SORT=${CCBENCH_KEY_SORT} ) diff --git a/cc/ss2pl/README.md b/cc/ss2pl/README.md index 73c470b1..d559f175 100644 --- a/cc/ss2pl/README.md +++ b/cc/ss2pl/README.md @@ -40,8 +40,15 @@ default : `0` default : `1` - `VAL_SIZE` : Value of key-value size. In other words, payload size.
default : `4` -- `DLR0` : Dead lock resolution is timeout. -- `DLR1` : Dead lock resolution is no-wait. +- Dead lock resolution is selected at configure time with the +`CCBENCH_SS2PL_DLR` cache variable, which defines exactly one of the +markers below.
+default : `1` +- `DLR0` : Timeout. Every record lock acquisition retries until it +succeeds or `-ss2pl_dlr0_timeout_us` elapses; on expiry the transaction +aborts through the same path as a no-wait abort. +- `DLR1` : No-wait. A lock acquisition that cannot succeed immediately +aborts the transaction. ## Optimizations - Backoff. diff --git a/cc/ss2pl/include/common.hh b/cc/ss2pl/include/common.hh index 5b504661..0918982b 100644 --- a/cc/ss2pl/include/common.hh +++ b/cc/ss2pl/include/common.hh @@ -52,4 +52,13 @@ DECLARE_bool(ycsb); DECLARE_double(zipf_skew); #endif +#if defined(DLR0) +#ifdef GLOBAL_VALUE_DEFINE +DEFINE_uint64(ss2pl_dlr0_timeout_us, 1000, + "ss2pl DLR0 lock timeout in microseconds."); +#else +DECLARE_uint64(ss2pl_dlr0_timeout_us); +#endif +#endif + alignas(CACHE_LINE_SIZE) GLOBAL uint32_t TotalThreadNum; diff --git a/cc/ss2pl/include/dlr0_timeout.hh b/cc/ss2pl/include/dlr0_timeout.hh new file mode 100644 index 00000000..805121bb --- /dev/null +++ b/cc/ss2pl/include/dlr0_timeout.hh @@ -0,0 +1,49 @@ +#pragma once + +#if defined(DLR0) + +#include +#include + +#include "../../../include/tsc.hh" +#include "common.hh" + +inline bool ss2pl_dlr0_try_read_once(ReaderWriteLock& lock) { + int expected = lock.counter.load(std::memory_order_acquire); + if (expected == -1) return false; + + return lock.counter.compare_exchange_strong(expected, expected + 1, + std::memory_order_acq_rel, + std::memory_order_acquire); +} + +inline bool ss2pl_dlr0_try_write_once(ReaderWriteLock& lock) { + int expected = lock.counter.load(std::memory_order_acquire); + if (expected != 0) return false; + + return lock.counter.compare_exchange_strong( + expected, -1, std::memory_order_acq_rel, std::memory_order_acquire); +} + +inline bool ss2pl_dlr0_try_upgrade_once(ReaderWriteLock& lock) { + int expected = lock.counter.load(std::memory_order_acquire); + if (expected != 1) return false; + + return lock.counter.compare_exchange_strong( + expected, -1, std::memory_order_acq_rel, std::memory_order_acquire); +} + +template +inline bool ss2pl_dlr0_lock_until_timeout(TryOnce&& try_once) { + const std::uint64_t timeout_clocks = + FLAGS_ss2pl_dlr0_timeout_us * FLAGS_clocks_per_us; + const std::uint64_t start = rdtscp(); + + for (;;) { + if (try_once()) return true; + if (rdtscp() - start >= timeout_clocks) return false; + _mm_pause(); + } +} + +#endif diff --git a/cc/ss2pl/transaction.cc b/cc/ss2pl/transaction.cc index 4e5a7ed8..8936b2f1 100644 --- a/cc/ss2pl/transaction.cc +++ b/cc/ss2pl/transaction.cc @@ -9,6 +9,9 @@ #include "../../include/procedure.hh" #include "../../include/result.hh" #include "include/common.hh" +#if defined(DLR0) +#include "include/dlr0_timeout.hh" +#endif #include "include/transaction.hh" using namespace std; @@ -155,6 +158,11 @@ Status TxExecutor::read(Storage s, std::string_view key, TupleBody** body) { if (tuple == nullptr) return Status::WARN_NOT_FOUND; read_internal(s, key, tuple); +#if defined(DLR0) + if (this->status_ == TransactionStatus::aborted) { + return Status::ERROR_LOCK_FAILED; + } +#endif *body = &(read_set_.back().body_); FINISH_READ: @@ -169,11 +177,12 @@ void TxExecutor::read_internal(Storage s, std::string_view key, Tuple* tuple) { if (reconnoitering_) goto FINISH_READ_LOCK; -#ifdef DLR0 - /** - * Acquire lock with wait. - */ - tuple->lock_.r_lock(); +#if defined(DLR0) + if (!ss2pl_dlr0_lock_until_timeout( + [&]() { return ss2pl_dlr0_try_read_once(tuple->lock_); })) { + this->status_ = TransactionStatus::aborted; + goto FINISH_READ; + } r_lock_list_.emplace_back(&tuple->lock_); #elif defined(DLR1) if (tuple->lock_.r_trylock()) { @@ -260,9 +269,10 @@ Status TxExecutor::update(Storage s, std::string_view key, TupleBody&& body) { for (auto rItr = read_set_.begin(); rItr != read_set_.end(); ++rItr) { if ((*rItr).storage_ != s) continue; if ((*rItr).key_ == key) { // hit -#if DLR0 - // Workaround for handling static BoMB properly - if (!(*rItr).rcdptr_->lock_.tryupgrade()) { +#if defined(DLR0) + if (!ss2pl_dlr0_lock_until_timeout([&]() { + return ss2pl_dlr0_try_upgrade_once((*rItr).rcdptr_->lock_); + })) { this->status_ = TransactionStatus::aborted; goto FINISH_WRITE; } @@ -306,11 +316,12 @@ Status TxExecutor::update(Storage s, std::string_view key, TupleBody&& body) { #endif if (tuple == nullptr) return Status::WARN_NOT_FOUND; -#if DLR0 - /** - * Lock with wait. - */ - tuple->lock_.w_lock(); +#if defined(DLR0) + if (!ss2pl_dlr0_lock_until_timeout( + [&]() { return ss2pl_dlr0_try_write_once(tuple->lock_); })) { + this->status_ = TransactionStatus::aborted; + goto FINISH_WRITE; + } #elif defined(DLR1) if (!tuple->lock_.w_trylock()) { /** @@ -331,6 +342,11 @@ Status TxExecutor::update(Storage s, std::string_view key, TupleBody&& body) { #if ADD_ANALYSIS result_->local_write_latency_ += rdtscp() - start; #endif // ADD_ANALYSIS +#if defined(DLR0) + if (this->status_ == TransactionStatus::aborted) { + return Status::ERROR_LOCK_FAILED; + } +#endif return Status::OK; } @@ -408,8 +424,12 @@ Status TxExecutor::read_lock(Storage s, std::string_view key) { if (w_lock == &tuple->lock_) { return Status::OK; } } -#ifdef DLR0 - tuple->lock_.r_lock(); +#if defined(DLR0) + if (!ss2pl_dlr0_lock_until_timeout( + [&]() { return ss2pl_dlr0_try_read_once(tuple->lock_); })) { + this->status_ = TransactionStatus::aborted; + return Status::ERROR_LOCK_FAILED; + } #elif defined(DLR1) if (!tuple->lock_.r_trylock()) { this->status_ = TransactionStatus::aborted; @@ -433,8 +453,12 @@ Status TxExecutor::write_lock(Storage s, std::string_view key) { if (w_lock == &tuple->lock_) { return Status::OK; } } -#if DLR0 - tuple->lock_.w_lock(); +#if defined(DLR0) + if (!ss2pl_dlr0_lock_until_timeout( + [&]() { return ss2pl_dlr0_try_write_once(tuple->lock_); })) { + this->status_ = TransactionStatus::aborted; + return Status::ERROR_LOCK_FAILED; + } #elif defined(DLR1) if (!tuple->lock_.w_trylock()) { this->status_ = TransactionStatus::aborted; diff --git a/cc/ss2pl/util.cc b/cc/ss2pl/util.cc index a2b20f4e..9ca5ca53 100644 --- a/cc/ss2pl/util.cc +++ b/cc/ss2pl/util.cc @@ -30,6 +30,14 @@ void chkArg() { if (FLAGS_rratio > 100) { ERR; } +#if defined(DLR0) + if (FLAGS_clocks_per_us == 0 || + FLAGS_ss2pl_dlr0_timeout_us > + std::numeric_limits::max() / FLAGS_clocks_per_us) { + ERR; + } +#endif + TotalThreadNum = FLAGS_thread_num; if (FLAGS_clocks_per_us < 100) { @@ -50,6 +58,10 @@ void displayParameter() { cout << "#FLAGS_tuple_num:\t" << FLAGS_tuple_num << endl; cout << "#FLAGS_ycsb:\t\t" << FLAGS_ycsb << endl; cout << "#FLAGS_zipf_skew:\t" << FLAGS_zipf_skew << endl; +#if defined(DLR0) + cout << "#FLAGS_ss2pl_dlr0_timeout_us:\t" << FLAGS_ss2pl_dlr0_timeout_us + << endl; +#endif } void partTableInit([[maybe_unused]] size_t thid, diff --git a/cmake/Options.cmake b/cmake/Options.cmake index 194a8cf6..842c1bf3 100644 --- a/cmake/Options.cmake +++ b/cmake/Options.cmake @@ -42,6 +42,7 @@ set(CCBENCH_PROCEDURE_SORT 0 CACHE STRING "silo") set(CCBENCH_WAL 0 CACHE STRING "silo") set(CCBENCH_TEMPERATURE_RESET_OPT 1 CACHE STRING "mocc") set(CCBENCH_WORKER1_INSERT_DELAY_RPHASE 0 CACHE STRING "cicada") +set(CCBENCH_SS2PL_DLR 1 CACHE STRING "ss2pl: 0=timeout, 1=no-wait") # Cicada and Oze share many flags but disagree on this one's default. # Keep two separate cache entries; protocols pick the one they want. diff --git a/docs/runtime-args_en.md b/docs/runtime-args_en.md index 34f25510..45f1446d 100644 --- a/docs/runtime-args_en.md +++ b/docs/runtime-args_en.md @@ -64,6 +64,17 @@ BoMB has many knobs; the ones below are the most common. See | `-bomb_use_cache` | `false` | Use cached BoM tree (static setting). | | `-bomb_rate_control` | `false` | Enable rate-limited request injection. | +## SS2PL-specific flags (`*_ss2pl.exe`, only when built with `CCBENCH_SS2PL_DLR=0`) + +The deadlock resolution strategy is chosen at configure time with +`-DCCBENCH_SS2PL_DLR=<0|1>`. The default is `1` (no-wait), which is the +historical behaviour. Only a binary built with `0` (timeout) accepts the flag +below. + +| Flag | Default | Meaning | +|---|---|---| +| `-ss2pl_dlr0_timeout_us` | `1000` | Upper bound, in microseconds, on how long a single record lock acquisition waits. The transaction aborts when it expires. The bound is converted to TSC ticks with `-clocks_per_us`, so that value has to be correct. | + ## Examples TPC-C on Silo with 8 warehouses: diff --git a/docs/runtime-args_ja.md b/docs/runtime-args_ja.md index 06e00536..09170a3f 100644 --- a/docs/runtime-args_ja.md +++ b/docs/runtime-args_ja.md @@ -56,6 +56,16 @@ BoMB には多くの knob がある。下記はよく使うものだけ。ワー | `-bomb_use_cache` | `false` | キャッシュ済みの BoM tree を使う (静的設定)。 | | `-bomb_rate_control` | `false` | レート制限付きの request injection を有効化。 | +## SS2PL 固有のフラグ (`*_ss2pl.exe`、`CCBENCH_SS2PL_DLR=0` でビルドしたときだけ) + +デッドロック解決方式は configure 時に `-DCCBENCH_SS2PL_DLR=<0|1>` で選ぶ。既定は `1` +(no-wait) で、これは従来の挙動である。`0` (timeout) でビルドしたバイナリだけが下記の +フラグを受け付ける。 + +| Flag | デフォルト | 意味 | +|---|---|---| +| `-ss2pl_dlr0_timeout_us` | `1000` | 1 回のレコードロック取得を待つ上限 (マイクロ秒)。到達するとトランザクションを abort する。`-clocks_per_us` を使って TSC tick へ換算するので、その値が正しいことが前提。 | + ## 例 8 warehouse で Silo の TPC-C: