diff --git a/src/communication/listener.hpp b/src/communication/listener.hpp index e428207ce..e37f38af1 100644 --- a/src/communication/listener.hpp +++ b/src/communication/listener.hpp @@ -14,8 +14,8 @@ #include "io/network/epoll.hpp" #include "io/network/socket.hpp" #include "utils/signals.hpp" +#include "utils/spin_lock.hpp" #include "utils/thread.hpp" -#include "utils/thread/sync.hpp" namespace communication { diff --git a/src/communication/session.hpp b/src/communication/session.hpp index 1deaf90c9..782de384c 100644 --- a/src/communication/session.hpp +++ b/src/communication/session.hpp @@ -20,7 +20,7 @@ #include "io/network/socket.hpp" #include "io/network/stream_buffer.hpp" #include "utils/on_scope_exit.hpp" -#include "utils/thread/sync.hpp" +#include "utils/spin_lock.hpp" namespace communication { diff --git a/src/data_structures/ring_buffer.hpp b/src/data_structures/ring_buffer.hpp index 376a236fd..33e754f3a 100644 --- a/src/data_structures/ring_buffer.hpp +++ b/src/data_structures/ring_buffer.hpp @@ -9,7 +9,7 @@ #include "glog/logging.h" -#include "utils/thread/sync.hpp" +#include "utils/spin_lock.hpp" /** * A thread-safe ring buffer. Multi-producer, multi-consumer. Producers get diff --git a/src/query/interpreter.hpp b/src/query/interpreter.hpp index 5bf68ab66..fcf7178c1 100644 --- a/src/query/interpreter.hpp +++ b/src/query/interpreter.hpp @@ -10,7 +10,7 @@ #include "query/frontend/stripped.hpp" #include "query/interpret/frame.hpp" #include "query/plan/operator.hpp" -#include "utils/thread/sync.hpp" +#include "utils/spin_lock.hpp" #include "utils/timer.hpp" DECLARE_bool(query_cost_planner); diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index 1d0a3b9f8..7e9172280 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -30,7 +30,6 @@ #include "utils/exceptions.hpp" #include "utils/hashing/fnv.hpp" #include "utils/string.hpp" -#include "utils/thread/sync.hpp" // macro for the default implementation of LogicalOperator::Accept // that accepts the visitor and visits it's input_ operator diff --git a/src/transactions/distributed/engine_single_node.hpp b/src/transactions/distributed/engine_single_node.hpp index 718855f9f..68900d842 100644 --- a/src/transactions/distributed/engine_single_node.hpp +++ b/src/transactions/distributed/engine_single_node.hpp @@ -10,7 +10,7 @@ #include "transactions/commit_log.hpp" #include "transactions/distributed/engine.hpp" #include "transactions/transaction.hpp" -#include "utils/thread/sync.hpp" +#include "utils/spin_lock.hpp" namespace tx { diff --git a/src/transactions/lock_store.hpp b/src/transactions/lock_store.hpp index 4185571b4..d0cca72c3 100644 --- a/src/transactions/lock_store.hpp +++ b/src/transactions/lock_store.hpp @@ -8,7 +8,7 @@ #include "storage/common/locking/lock_status.hpp" #include "storage/common/locking/record_lock.hpp" #include "transactions/type.hpp" -#include "utils/thread/sync.hpp" +#include "utils/spin_lock.hpp" namespace tx { diff --git a/src/transactions/single_node/engine.hpp b/src/transactions/single_node/engine.hpp index 33bcc01d2..6ad2b6800 100644 --- a/src/transactions/single_node/engine.hpp +++ b/src/transactions/single_node/engine.hpp @@ -9,7 +9,7 @@ #include "durability/single_node/wal.hpp" #include "transactions/commit_log.hpp" #include "transactions/transaction.hpp" -#include "utils/thread/sync.hpp" +#include "utils/spin_lock.hpp" namespace tx { diff --git a/src/transactions/single_node_ha/engine.hpp b/src/transactions/single_node_ha/engine.hpp index 20d332836..5cf4c194f 100644 --- a/src/transactions/single_node_ha/engine.hpp +++ b/src/transactions/single_node_ha/engine.hpp @@ -9,7 +9,7 @@ #include "raft/raft_interface.hpp" #include "transactions/commit_log.hpp" #include "transactions/transaction.hpp" -#include "utils/thread/sync.hpp" +#include "utils/spin_lock.hpp" namespace tx { diff --git a/src/utils/spin_lock.hpp b/src/utils/spin_lock.hpp new file mode 100644 index 000000000..967aba27d --- /dev/null +++ b/src/utils/spin_lock.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include + +namespace utils { + +/// This class is a wrapper around the `pthread_spinlock_t`. It provides a +/// generic spin lock. The lock should be used in cases where you know that the +/// lock will be contended only for short periods of time. This lock doesn't +/// make any kernel calls (like sleep, or context switching) during its wait for +/// the lock to be acquired. This property is only useful when the lock will be +/// held for short periods of time and you don't want to introduce the extra +/// delays of a sleep or context switch. On the assembly level +/// `pthread_spinlock_t` is optimized to use less power, reduce branch +/// mispredictions, etc... The explanation can be seen here: +/// https://stackoverflow.com/questions/26583433/c11-implementation-of-spinlock-using-atomic/29195378#29195378 +/// https://software.intel.com/en-us/node/524249 +class SpinLock { + public: + SpinLock() { + // `pthread_spin_init` returns -1 only when there isn't enough memory to + // initialize the lock. That should never occur because the + // `pthread_spinlock_t` is an `int` and memory isn't allocated by this init. + // The message is probably here to suit all other platforms... + CHECK(pthread_spin_init(&lock_, PTHREAD_PROCESS_PRIVATE) == 0) + << "Couldn't construct utils::SpinLock!"; + } + + void lock() { + // `pthread_spin_lock` returns -1 only when there is a deadlock detected + // (errno EDEADLOCK). + CHECK(pthread_spin_lock(&lock_) == 0) << "Couldn't lock utils::SpinLock!"; + } + + bool try_lock() { + // `pthread_spin_trylock` returns -1 only when the lock is already locked + // (errno EBUSY). + return pthread_spin_trylock(&lock_) == 0; + } + + void unlock() { + // `pthread_spin_unlock` has no documented error codes that it could return, + // so any error is a fatal error. + CHECK(pthread_spin_unlock(&lock_) == 0) + << "Couldn't unlock utils::SpinLock!"; + } + + ~SpinLock() { + CHECK(pthread_spin_destroy(&lock_) == 0) + << "Couldn't destruct utils::SpinLock!"; + } + + private: + pthread_spinlock_t lock_; +}; +} // namespace utils diff --git a/src/utils/thread/sync.hpp b/src/utils/thread/sync.hpp index 5371f7b2f..03496bd32 100644 --- a/src/utils/thread/sync.hpp +++ b/src/utils/thread/sync.hpp @@ -5,12 +5,11 @@ #include #include -#include -#include #include #include #include "utils/exceptions.hpp" +#include "utils/spin_lock.hpp" namespace utils { @@ -47,59 +46,6 @@ class CasLock { std::atomic lock_flag; }; -/// Spinlock is used as a locking mechanism based on an atomic flag and waiting -/// loops. -/// -/// It uses the CpuRelax "asm pause" command to optimize wasted time while the -/// threads are waiting. -class SpinLock { - public: - void lock() { // Before was memory_order_acquire - while (lock_flag_.test_and_set()) { - CpuRelax(); - } - } - // Before was memory_order_release - void unlock() { lock_flag_.clear(); } - - bool try_lock() { return !lock_flag_.test_and_set(); } - - private: - // guaranteed by standard to be lock free! - mutable std::atomic_flag lock_flag_ = ATOMIC_FLAG_INIT; -}; - -template -class TimedSpinLock { - public: - TimedSpinLock(std::chrono::seconds expiration) : expiration_(expiration) {} - - void lock() { - using clock = std::chrono::high_resolution_clock; - - auto start = clock::now(); - - while (!lock_flag.test_and_set(std::memory_order_acquire)) { - // how long have we been locked? if we exceeded the expiration - // time, throw an exception and stop being blocked because this - // might be a deadlock! - - if (clock::now() - start > expiration_) - throw LockTimeoutException("This lock has expired"); - - usleep(microseconds); - } - } - - void unlock() { lock_flag.clear(std::memory_order_release); } - - private: - std::chrono::milliseconds expiration_; - - // guaranteed by standard to be lock free! - std::atomic_flag lock_flag = ATOMIC_FLAG_INIT; -}; - /// By passing the appropriate parameter to the `RWLock` constructor, it is /// possible to control the behavior of `RWLock` while shared lock is held. If /// the priority is set to `READ`, new shared (read) locks can be obtained even