cachelib/allocator/nvmcache/InFlightPuts.h (98 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed 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 <folly/Hash.h>
#include <folly/Range.h>
#include <folly/lang/Align.h>
#include <folly/logging/xlog.h>
#include <mutex>
#include <unordered_map>
#include <utility>
namespace facebook {
namespace cachelib {
// Utility to track inflight puts in nvmcache through a token. Tokens can be
// invalidated and can be used to execute some function if not invalidated. The
// user guarantees that the lifetime of the token is within the lifetime of the
// string piece with which they obtain the token.
class alignas(folly::hardware_destructive_interference_size) InFlightPuts {
using LockGuard = std::lock_guard<std::mutex>;
using UniqueLock = std::unique_lock<std::mutex>;
public:
class PutToken;
// inserts an in-flight put into the map if none exists and acquires a
// token. Caller can check if the token is valid to determine if they can
// use it to complete the operation.
PutToken tryAcquireToken(folly::StringPiece key) {
UniqueLock l(mutex_, std::try_to_lock);
if (!l.owns_lock()) {
return PutToken{};
}
auto ret = keys_.emplace(key, true);
// record for same key being inflight written to nvmcache should be rare.
// In that case, fail the latter one.
if (ret.second) {
return PutToken{key, *this};
}
return PutToken{};
}
// marks the token as invalidated. This will ensure that we dont execute any
// function on this token and simply remove the token when the token gets
// destroyed.
void invalidateToken(folly::StringPiece key) {
LockGuard l(mutex_);
auto it = keys_.find(key);
if (it != keys_.end()) {
it->second = false;
}
}
// Represents an insertion into the inflight map. this token can be used to
// execute some action if the token was not invalidated in the mean time.
class PutToken {
public:
PutToken() noexcept {}
~PutToken() {
if (puts_) {
puts_->removeToken(key_);
}
}
// disallow copying
PutToken(const PutToken&) = delete;
PutToken& operator=(const PutToken&) = delete;
// moving is okay
PutToken(PutToken&& other) noexcept : key_(other.key_), puts_(other.puts_) {
other.reset();
}
PutToken& operator=(PutToken&& other) noexcept {
if (this != &other) {
this->~PutToken();
new (this) PutToken(std::move(other));
}
return *this;
}
// returns true if the token was valid one upon construction. Does not
// reflect if the token was invalidated after construction.
bool isValid() const noexcept { return puts_ != nullptr; }
// executes the fn if the token is valid and the there has been no
// invalidation. destroys the token state accordingly.
template <typename F>
bool executeIfValid(F&& fn) {
if (isValid() &&
puts_->executeIfValid(key_, std::forward<decltype(fn)>(fn))) {
// successfully executed, reset the token.
reset();
return true;
}
return false;
}
private:
void reset() noexcept {
puts_ = nullptr;
key_.clear();
XDCHECK(!isValid());
}
friend InFlightPuts;
PutToken(folly::StringPiece key, InFlightPuts& puts)
: key_(key), puts_(&puts) {}
// key corresponding to the token
folly::StringPiece key_{};
// map holding the state
InFlightPuts* puts_{nullptr};
};
private:
// execute only if the token is present and was not invalidated.
// @param key the item key
// @param fn function to execute
//
// @return true if the function was executed and token was destroyed
// appropriately
// @throw if fn throws, token is preserved.
template <typename F>
bool executeIfValid(folly::StringPiece key, F&& fn) {
LockGuard l(mutex_);
auto it = keys_.find(key);
const bool valid = it != keys_.end() && it->second;
if (valid) {
fn();
keys_.erase(it);
return true;
}
return false;
}
// erases the record from inflight map.
void removeToken(folly::StringPiece key) {
LockGuard l(mutex_);
auto res = keys_.erase(key);
XDCHECK_EQ(res, 1u);
}
// map storing the presence of a token and its validity
std::unordered_map<folly::StringPiece, bool, folly::Hash> keys_;
// mutex protecting the map.
std::mutex mutex_;
};
} // namespace cachelib
} // namespace facebook