Implement new spin lock

Reviewers: teon.banek, buda

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1786
This commit is contained in:
Matej Ferencevic 2019-01-07 09:18:52 +01:00
parent a4cce253c0
commit 3209788cd4
11 changed files with 67 additions and 64 deletions

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

58
src/utils/spin_lock.hpp Normal file
View File

@ -0,0 +1,58 @@
#pragma once
#include <pthread.h>
#include <glog/logging.h>
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

View File

@ -5,12 +5,11 @@
#include <unistd.h>
#include <atomic>
#include <cerrno>
#include <chrono>
#include <cstdint>
#include <mutex>
#include "utils/exceptions.hpp"
#include "utils/spin_lock.hpp"
namespace utils {
@ -47,59 +46,6 @@ class CasLock {
std::atomic<bool> 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 <size_t microseconds = 250>
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