blob: 985d5efc8ce32caa59fe2035b9f8b5a19a73412b [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
#pragma once
#include <rocksdb/db.h>
#include <functional>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <vector>
class LockManager {
public:
explicit LockManager(unsigned hash_power)
: hash_power_(hash_power), hash_mask_((1U << hash_power) - 1), mutex_pool_(Size()) {}
~LockManager() = default;
LockManager(const LockManager &) = delete;
LockManager &operator=(const LockManager &) = delete;
unsigned Size() const { return (1U << hash_power_); }
void Lock(std::string_view key) { mutex_pool_[hash(key)].lock(); }
void UnLock(std::string_view key) { mutex_pool_[hash(key)].unlock(); }
void Lock(rocksdb::Slice key) { Lock(key.ToStringView()); }
void UnLock(rocksdb::Slice key) { UnLock(key.ToStringView()); }
template <typename Key>
std::mutex *Get(const Key &key) {
return &mutex_pool_[hash(key)];
}
template <typename Keys>
std::vector<std::mutex *> MultiGet(const Keys &keys) {
std::set<unsigned, std::greater<unsigned>> to_acquire_indexes;
// We are using the `set` to avoid retrieving the mutex, as well as guarantee to retrieve
// the order of locks.
//
// For example, we need lock the key `A` and `B` and they have the same lock hash
// index, it will be deadlock if lock the same mutex twice. Besides, we also need
// to order the mutex before acquiring locks since different threads may acquire
// same keys with different order.
for (const auto &key : keys) {
to_acquire_indexes.insert(hash(key));
}
std::vector<std::mutex *> locks;
locks.reserve(to_acquire_indexes.size());
for (auto index : to_acquire_indexes) {
locks.emplace_back(&mutex_pool_[index]);
}
return locks;
}
private:
unsigned hash_power_;
unsigned hash_mask_;
std::vector<std::mutex> mutex_pool_;
unsigned hash(std::string_view key) const { return std::hash<std::string_view>{}(key)&hash_mask_; }
};
class LockGuard {
public:
template <typename KeyType>
explicit LockGuard(LockManager *lock_mgr, const KeyType &key) : lock_(lock_mgr->Get(key)) {
lock_->lock();
}
~LockGuard() {
if (lock_) lock_->unlock();
}
LockGuard(const LockGuard &) = delete;
LockGuard &operator=(const LockGuard &) = delete;
LockGuard(LockGuard &&guard) noexcept : lock_(guard.lock_) { guard.lock_ = nullptr; }
LockGuard &operator=(LockGuard &&other) noexcept {
if (&other != this) {
std::destroy_at(this);
new (this) LockGuard(std::move(other));
}
return *this;
}
private:
std::mutex *lock_{nullptr};
};
class MultiLockGuard {
public:
template <typename Keys>
explicit MultiLockGuard(LockManager *lock_mgr, const Keys &keys) : locks_(lock_mgr->MultiGet(keys)) {
for (const auto &iter : locks_) {
iter->lock();
}
}
~MultiLockGuard() {
// Lock with order `A B C` and unlock should be `C B A`
for (auto iter = locks_.rbegin(); iter != locks_.rend(); ++iter) {
(*iter)->unlock();
}
}
MultiLockGuard(const MultiLockGuard &) = delete;
MultiLockGuard &operator=(const MultiLockGuard &) = delete;
MultiLockGuard(MultiLockGuard &&guard) : locks_(std::move(guard.locks_)) { guard.locks_.clear(); }
MultiLockGuard &operator=(MultiLockGuard &&other) noexcept {
if (this != &other) {
std::destroy_at(this);
new (this) MultiLockGuard(std::move(other));
}
return *this;
}
private:
std::vector<std::mutex *> locks_;
};