mirror of
https://github.com/transmission/transmission.git
synced 2025-12-24 20:35:36 +00:00
refactor: thread-safe lru-cache and tr_open_files (#6230)
This commit is contained in:
@@ -6,44 +6,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "libtransmission/observable.h"
|
||||
|
||||
// A fixed-size cache that erases least-recently-used items to make room for new ones.
|
||||
template<typename Key, typename Val, std::size_t N>
|
||||
class tr_lru_cache
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] constexpr Val* get(Key const& key) noexcept
|
||||
[[nodiscard]] TR_CONSTEXPR23 std::optional<std::pair<Val&, libtransmission::ObserverTag>> get(Key const& key) noexcept
|
||||
{
|
||||
if (auto const found = find(key); found != nullptr)
|
||||
auto const lock = std::lock_guard{ mutex_ };
|
||||
if (auto* const found = find(key); found != nullptr)
|
||||
{
|
||||
found->sequence_ = next_sequence_++;
|
||||
return &found->val_;
|
||||
return lock_and_get_entry(*found);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool contains(Key const& key) const noexcept
|
||||
[[nodiscard]] TR_CONSTEXPR23 bool contains(Key const& key) const noexcept
|
||||
{
|
||||
return !!find(key);
|
||||
return find(key) != nullptr;
|
||||
}
|
||||
|
||||
Val& add(Key&& key)
|
||||
std::pair<Val&, libtransmission::ObserverTag> add(Key&& key)
|
||||
{
|
||||
auto& entry = getFreeSlot();
|
||||
auto const lock = std::lock_guard{ mutex_ };
|
||||
|
||||
auto& entry = get_free_slot();
|
||||
entry.key_ = std::move(key);
|
||||
entry.sequence_ = next_sequence_++;
|
||||
|
||||
key = {};
|
||||
return entry.val_;
|
||||
return lock_and_get_entry(entry);
|
||||
}
|
||||
|
||||
void erase(Key const& key)
|
||||
{
|
||||
auto const lock = std::lock_guard{ mutex_ };
|
||||
if (auto* const found = find(key); found != nullptr)
|
||||
{
|
||||
this->erase(*found);
|
||||
@@ -52,6 +61,7 @@ public:
|
||||
|
||||
void erase_if(std::function<bool(Key const&, Val const&)> test)
|
||||
{
|
||||
auto const lock = std::lock_guard{ mutex_ };
|
||||
for (auto& entry : entries_)
|
||||
{
|
||||
if (entry.sequence_ != InvalidSeq && test(entry.key_, entry.val_))
|
||||
@@ -63,44 +73,50 @@ public:
|
||||
|
||||
void clear()
|
||||
{
|
||||
auto const lock = std::lock_guard{ mutex_ };
|
||||
for (auto& entry : entries_)
|
||||
{
|
||||
erase(entry);
|
||||
}
|
||||
}
|
||||
|
||||
using PreEraseCallback = std::function<void(Key const&, Val&)>;
|
||||
|
||||
void setPreErase(PreEraseCallback&& func)
|
||||
{
|
||||
pre_erase_cb_ = std::move(func);
|
||||
}
|
||||
|
||||
private:
|
||||
PreEraseCallback pre_erase_cb_ = [](Key const&, Val&) {
|
||||
};
|
||||
|
||||
struct Entry
|
||||
{
|
||||
Key key_ = {};
|
||||
Val val_ = {};
|
||||
size_t in_use_ = 0U;
|
||||
uint64_t sequence_ = InvalidSeq;
|
||||
};
|
||||
|
||||
std::pair<Val&, libtransmission::ObserverTag> lock_and_get_entry(Entry& entry)
|
||||
{
|
||||
auto const lock = std::lock_guard{ mutex_ };
|
||||
++entry.in_use_;
|
||||
return std::make_pair(
|
||||
std::ref(entry.val_),
|
||||
libtransmission::ObserverTag{ [this, &entry]()
|
||||
{
|
||||
auto lock2 = std::unique_lock{ mutex_ };
|
||||
--entry.in_use_;
|
||||
TR_ASSERT(entry.in_use_ >= 0U);
|
||||
lock2.unlock();
|
||||
cv_.notify_one();
|
||||
} });
|
||||
}
|
||||
|
||||
void erase(Entry& entry)
|
||||
{
|
||||
if (entry.sequence_ != InvalidSeq)
|
||||
{
|
||||
pre_erase_cb_(entry.key_, entry.val_);
|
||||
}
|
||||
auto const lock = std::lock_guard{ mutex_ };
|
||||
|
||||
entry.key_ = {};
|
||||
entry.val_ = {};
|
||||
entry.sequence_ = InvalidSeq;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Entry* find(Key const& key) noexcept
|
||||
[[nodiscard]] TR_CONSTEXPR23 Entry* find(Key const& key) noexcept
|
||||
{
|
||||
auto const lock = std::lock_guard{ mutex_ };
|
||||
for (auto& entry : entries_)
|
||||
{
|
||||
if (entry.sequence_ != InvalidSeq && entry.key_ == key)
|
||||
@@ -112,8 +128,9 @@ private:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Entry const* find(Key const& key) const noexcept
|
||||
[[nodiscard]] TR_CONSTEXPR23 Entry const* find(Key const& key) const noexcept
|
||||
{
|
||||
auto const lock = std::lock_guard{ mutex_ };
|
||||
for (auto const& entry : entries_)
|
||||
{
|
||||
if (entry.sequence_ != InvalidSeq && entry.key_ == key)
|
||||
@@ -125,17 +142,29 @@ private:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Entry& getFreeSlot()
|
||||
Entry& get_free_slot()
|
||||
{
|
||||
auto lock = std::unique_lock{ mutex_ };
|
||||
cv_.wait(
|
||||
lock,
|
||||
[this]() {
|
||||
return std::any_of(
|
||||
std::begin(entries_),
|
||||
std::end(entries_),
|
||||
[](auto const& entry) { return entry.in_use_ == 0U; });
|
||||
});
|
||||
auto const iter = std::min_element(
|
||||
std::begin(entries_),
|
||||
std::end(entries_),
|
||||
[](auto const& a, auto const& b) { return a.sequence_ < b.sequence_; });
|
||||
this->erase(*iter);
|
||||
[](auto const& a, auto const& b) { return b.in_use_ > 0U || a.sequence_ < b.sequence_; });
|
||||
erase(*iter);
|
||||
return *iter;
|
||||
}
|
||||
|
||||
std::array<Entry, N> entries_;
|
||||
uint64_t next_sequence_ = 1;
|
||||
static uint64_t constexpr InvalidSeq = 0;
|
||||
uint64_t next_sequence_ = 1U;
|
||||
static uint64_t constexpr InvalidSeq = 0U;
|
||||
|
||||
mutable std::recursive_mutex mutex_;
|
||||
std::condition_variable_any cv_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user