refactor: thread-safe `lru-cache` and `tr_open_files` (#6230)
This commit is contained in:
parent
a575be778f
commit
40fe56f33e
|
@ -34,7 +34,7 @@ namespace
|
|||
|
||||
bool readEntireBuf(tr_sys_file_t fd, uint64_t file_offset, uint8_t* buf, uint64_t buflen, tr_error* error)
|
||||
{
|
||||
while (buflen > 0)
|
||||
while (buflen > 0U)
|
||||
{
|
||||
auto n_read = uint64_t{};
|
||||
|
||||
|
@ -53,7 +53,7 @@ bool readEntireBuf(tr_sys_file_t fd, uint64_t file_offset, uint8_t* buf, uint64_
|
|||
|
||||
bool writeEntireBuf(tr_sys_file_t fd, uint64_t file_offset, uint8_t const* buf, uint64_t buflen, tr_error* error)
|
||||
{
|
||||
while (buflen > 0)
|
||||
while (buflen > 0U)
|
||||
{
|
||||
auto n_written = uint64_t{};
|
||||
|
||||
|
@ -112,10 +112,10 @@ void readOrWriteBytes(
|
|||
|
||||
bool const do_write = io_mode == IoMode::Write;
|
||||
auto const file_size = tor->file_size(file_index);
|
||||
TR_ASSERT(file_size == 0 || file_offset < file_size);
|
||||
TR_ASSERT(file_size == 0U || file_offset < file_size);
|
||||
TR_ASSERT(file_offset + buflen <= file_size);
|
||||
|
||||
if (file_size == 0)
|
||||
if (file_size == 0U)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -128,9 +128,9 @@ void readOrWriteBytes(
|
|||
|
||||
// --- Find the fd
|
||||
|
||||
auto fd = session->openFiles().get(tor->id(), file_index, do_write);
|
||||
auto fd_top = session->openFiles().get(tor->id(), file_index, do_write);
|
||||
auto filename = tr_pathbuf{};
|
||||
if (!fd && !getFilename(filename, tor, file_index, io_mode))
|
||||
if (!fd_top && !getFilename(filename, tor, file_index, io_mode))
|
||||
{
|
||||
auto const err = ENOENT;
|
||||
error->set(
|
||||
|
@ -143,20 +143,20 @@ void readOrWriteBytes(
|
|||
return;
|
||||
}
|
||||
|
||||
if (!fd) // not in the cache, so open or create it now
|
||||
if (!fd_top) // not in the cache, so open or create it now
|
||||
{
|
||||
// open (and maybe create) the file
|
||||
auto const prealloc = (!do_write || !tor->file_is_wanted(file_index)) ? TR_PREALLOCATE_NONE :
|
||||
tor->session->preallocationMode();
|
||||
fd = session->openFiles().get(tor->id(), file_index, do_write, filename, prealloc, file_size);
|
||||
if (fd && do_write)
|
||||
fd_top = session->openFiles().get(tor->id(), file_index, do_write, filename, prealloc, file_size);
|
||||
if (fd_top && do_write)
|
||||
{
|
||||
// make a note that we just created a file
|
||||
tor->session->add_file_created();
|
||||
}
|
||||
}
|
||||
|
||||
if (!fd) // couldn't create/open it either
|
||||
if (!fd_top) // couldn't create/open it either
|
||||
{
|
||||
auto const errnum = errno;
|
||||
error->set(
|
||||
|
@ -170,10 +170,11 @@ void readOrWriteBytes(
|
|||
return;
|
||||
}
|
||||
|
||||
auto const& [fd, tag] = *fd_top;
|
||||
switch (io_mode)
|
||||
{
|
||||
case IoMode::Read:
|
||||
if (!readEntireBuf(*fd, file_offset, buf, buflen, error) && *error)
|
||||
if (!readEntireBuf(fd, file_offset, buf, buflen, error) && *error)
|
||||
{
|
||||
tr_logAddErrorTor(
|
||||
tor,
|
||||
|
@ -187,7 +188,7 @@ void readOrWriteBytes(
|
|||
break;
|
||||
|
||||
case IoMode::Write:
|
||||
if (!writeEntireBuf(*fd, file_offset, buf, buflen, error) && *error)
|
||||
if (!writeEntireBuf(fd, file_offset, buf, buflen, error) && *error)
|
||||
{
|
||||
tr_logAddErrorTor(
|
||||
tor,
|
||||
|
@ -201,7 +202,7 @@ void readOrWriteBytes(
|
|||
break;
|
||||
|
||||
case IoMode::Prefetch:
|
||||
tr_sys_file_advise(*fd, file_offset, buflen, TR_SYS_FILE_ADVICE_WILL_NEED);
|
||||
tr_sys_file_advise(fd, file_offset, buflen, TR_SYS_FILE_ADVICE_WILL_NEED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +217,7 @@ int readOrWritePiece(tr_torrent* tor, IoMode io_mode, tr_block_info::Location lo
|
|||
|
||||
auto [file_index, file_offset] = tor->file_offset(loc, false);
|
||||
|
||||
while (buflen != 0)
|
||||
while (buflen != 0U)
|
||||
{
|
||||
uint64_t const bytes_this_pass = std::min(uint64_t{ buflen }, uint64_t{ tor->file_size(file_index) - file_offset });
|
||||
|
||||
|
@ -240,7 +241,7 @@ int readOrWritePiece(tr_torrent* tor, IoMode io_mode, tr_block_info::Location lo
|
|||
buflen -= bytes_this_pass;
|
||||
|
||||
++file_index;
|
||||
file_offset = 0;
|
||||
file_offset = 0U;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -275,7 +276,7 @@ std::optional<tr_sha1_digest_t> recalculateHash(tr_torrent* tor, tr_piece_index_
|
|||
{
|
||||
begin += (begin_byte - block_loc.byte);
|
||||
}
|
||||
if (block + 1 == end_block) // `block` may end after `piece` does
|
||||
if (block + 1U == end_block) // `block` may end after `piece` does
|
||||
{
|
||||
end -= (block_loc.byte + block_len - end_byte);
|
||||
}
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
@ -17,11 +17,14 @@
|
|||
#include "libtransmission/error.h"
|
||||
#include "libtransmission/file.h"
|
||||
#include "libtransmission/log.h"
|
||||
#include "libtransmission/observable.h"
|
||||
#include "libtransmission/open-files.h"
|
||||
#include "libtransmission/tr-assert.h"
|
||||
#include "libtransmission/tr-strbuf.h"
|
||||
#include "libtransmission/utils.h" // _()
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
@ -32,7 +35,7 @@ namespace
|
|||
|
||||
bool preallocate_file_sparse(tr_sys_file_t fd, uint64_t length, tr_error* error)
|
||||
{
|
||||
if (length == 0)
|
||||
if (length == 0U)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -48,12 +51,12 @@ bool preallocate_file_sparse(tr_sys_file_t fd, uint64_t length, tr_error* error)
|
|||
|
||||
if (!TR_ERROR_IS_ENOSPC(local_error.code()))
|
||||
{
|
||||
char const zero = '\0';
|
||||
static char constexpr Zero = '\0';
|
||||
|
||||
local_error = {};
|
||||
|
||||
/* fallback: the old-style seek-and-write */
|
||||
if (tr_sys_file_write_at(fd, &zero, 1, length - 1, nullptr, &local_error) &&
|
||||
if (tr_sys_file_write_at(fd, &Zero, 1, length - 1, nullptr, &local_error) &&
|
||||
tr_sys_file_truncate(fd, length, &local_error))
|
||||
{
|
||||
return true;
|
||||
|
@ -72,7 +75,7 @@ bool preallocate_file_sparse(tr_sys_file_t fd, uint64_t length, tr_error* error)
|
|||
|
||||
bool preallocate_file_full(tr_sys_file_t fd, uint64_t length, tr_error* error)
|
||||
{
|
||||
if (length == 0)
|
||||
if (length == 0U)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -122,22 +125,26 @@ bool preallocate_file_full(tr_sys_file_t fd, uint64_t length, tr_error* error)
|
|||
|
||||
// ---
|
||||
|
||||
std::optional<tr_sys_file_t> tr_open_files::get(tr_torrent_id_t tor_id, tr_file_index_t file_num, bool writable)
|
||||
std::optional<std::pair<tr_sys_file_t, libtransmission::ObserverTag>> tr_open_files::get(
|
||||
tr_torrent_id_t tor_id,
|
||||
tr_file_index_t file_num,
|
||||
bool writable)
|
||||
{
|
||||
if (auto* const found = pool_.get(make_key(tor_id, file_num)); found != nullptr)
|
||||
if (auto found = pool_.get(make_key(tor_id, file_num)); found)
|
||||
{
|
||||
if (writable && !found->writable_)
|
||||
auto& [entry, tag] = *found;
|
||||
if (writable && !entry.writable_)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return found->fd_;
|
||||
return std::pair{ entry.fd_, std::move(tag) };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<tr_sys_file_t> tr_open_files::get(
|
||||
std::optional<std::pair<tr_sys_file_t, libtransmission::ObserverTag>> tr_open_files::get(
|
||||
tr_torrent_id_t tor_id,
|
||||
tr_file_index_t file_num,
|
||||
bool writable,
|
||||
|
@ -147,11 +154,12 @@ std::optional<tr_sys_file_t> tr_open_files::get(
|
|||
{
|
||||
// is there already an entry
|
||||
auto key = make_key(tor_id, file_num);
|
||||
if (auto* const found = pool_.get(key); found != nullptr)
|
||||
if (auto found = pool_.get(key); found)
|
||||
{
|
||||
if (!writable || found->writable_)
|
||||
auto& [entry, tag] = *found;
|
||||
if (!writable || entry.writable_)
|
||||
{
|
||||
return found->fd_;
|
||||
return std::pair{ entry.fd_, std::move(tag) };
|
||||
}
|
||||
|
||||
pool_.erase(key); // close so we can re-open as writable
|
||||
|
@ -199,20 +207,20 @@ std::optional<tr_sys_file_t> tr_open_files::get(
|
|||
if (writable && !already_existed && allocation != TR_PREALLOCATE_NONE)
|
||||
{
|
||||
bool success = false;
|
||||
char const* type = nullptr;
|
||||
auto type = std::string_view{};
|
||||
|
||||
if (allocation == TR_PREALLOCATE_FULL)
|
||||
{
|
||||
success = preallocate_file_full(fd, file_size, &error);
|
||||
type = "full";
|
||||
type = "full"sv;
|
||||
}
|
||||
else if (allocation == TR_PREALLOCATE_SPARSE)
|
||||
{
|
||||
success = preallocate_file_sparse(fd, file_size, &error);
|
||||
type = "sparse";
|
||||
type = "sparse"sv;
|
||||
}
|
||||
|
||||
TR_ASSERT(type != nullptr);
|
||||
TR_ASSERT(!std::empty(type));
|
||||
|
||||
if (!success)
|
||||
{
|
||||
|
@ -245,11 +253,11 @@ std::optional<tr_sys_file_t> tr_open_files::get(
|
|||
}
|
||||
|
||||
// cache it
|
||||
auto& entry = pool_.add(std::move(key));
|
||||
auto [entry, tag] = pool_.add(std::move(key));
|
||||
entry.fd_ = fd;
|
||||
entry.writable_ = writable;
|
||||
|
||||
return fd;
|
||||
return std::pair{ fd, std::move(tag) };
|
||||
}
|
||||
|
||||
void tr_open_files::close_all()
|
||||
|
|
|
@ -24,9 +24,12 @@
|
|||
class tr_open_files
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] std::optional<tr_sys_file_t> get(tr_torrent_id_t tor_id, tr_file_index_t file_num, bool writable);
|
||||
[[nodiscard]] std::optional<std::pair<tr_sys_file_t, libtransmission::ObserverTag>> get(
|
||||
tr_torrent_id_t tor_id,
|
||||
tr_file_index_t file_num,
|
||||
bool writable);
|
||||
|
||||
[[nodiscard]] std::optional<tr_sys_file_t> get(
|
||||
[[nodiscard]] std::optional<std::pair<tr_sys_file_t, libtransmission::ObserverTag>> get(
|
||||
tr_torrent_id_t tor_id,
|
||||
tr_file_index_t file_num,
|
||||
bool writable,
|
||||
|
@ -67,6 +70,6 @@ private:
|
|||
bool writable_ = false;
|
||||
};
|
||||
|
||||
static constexpr size_t MaxOpenFiles = 32;
|
||||
static constexpr size_t MaxOpenFiles = 32U;
|
||||
tr_lru_cache<Key, Val, MaxOpenFiles> pool_;
|
||||
};
|
||||
|
|
|
@ -28,8 +28,8 @@ using OpenFilesTest = libtransmission::test::SessionTest;
|
|||
|
||||
TEST_F(OpenFilesTest, getCachedFailsIfNotCached)
|
||||
{
|
||||
auto const fd = session_->openFiles().get(0, 0, false);
|
||||
EXPECT_FALSE(fd);
|
||||
auto const fd_top = session_->openFiles().get(0, 0, false);
|
||||
EXPECT_FALSE(fd_top);
|
||||
}
|
||||
|
||||
TEST_F(OpenFilesTest, getOpensIfNotCached)
|
||||
|
@ -42,15 +42,16 @@ TEST_F(OpenFilesTest, getOpensIfNotCached)
|
|||
EXPECT_FALSE(session_->openFiles().get(0, 0, false));
|
||||
|
||||
// confirm that we can cache the file
|
||||
auto fd = session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
||||
EXPECT_TRUE(fd.has_value());
|
||||
assert(fd.has_value());
|
||||
EXPECT_NE(TR_BAD_SYS_FILE, *fd);
|
||||
auto fd_top = session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
||||
EXPECT_TRUE(fd_top.has_value());
|
||||
assert(fd_top.has_value());
|
||||
auto& [fd, tag] = *fd_top;
|
||||
EXPECT_NE(TR_BAD_SYS_FILE, fd);
|
||||
|
||||
// test the file contents to confirm that fd points to the right file
|
||||
auto buf = std::array<char, std::size(Contents) + 1>{};
|
||||
auto bytes_read = uint64_t{};
|
||||
EXPECT_TRUE(tr_sys_file_read_at(*fd, std::data(buf), std::size(Contents), 0, &bytes_read));
|
||||
EXPECT_TRUE(tr_sys_file_read_at(fd, std::data(buf), std::size(Contents), 0, &bytes_read));
|
||||
auto const contents = std::string_view{ std::data(buf), static_cast<size_t>(bytes_read) };
|
||||
EXPECT_EQ(Contents, contents);
|
||||
}
|
||||
|
@ -73,13 +74,13 @@ TEST_F(OpenFilesTest, getCachedReturnsTheSameFd)
|
|||
createFileWithContents(filename, Contents);
|
||||
|
||||
EXPECT_FALSE(session_->openFiles().get(0, 0, false));
|
||||
auto const fd1 = session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
||||
auto const fd2 = session_->openFiles().get(0, 0, false);
|
||||
EXPECT_TRUE(fd1.has_value());
|
||||
EXPECT_TRUE(fd2.has_value());
|
||||
assert(fd1.has_value());
|
||||
assert(fd2.has_value());
|
||||
EXPECT_EQ(*fd1, *fd2);
|
||||
auto const fd1_top = session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
||||
auto const fd2_top = session_->openFiles().get(0, 0, false);
|
||||
EXPECT_TRUE(fd1_top.has_value());
|
||||
EXPECT_TRUE(fd2_top.has_value());
|
||||
assert(fd1_top.has_value());
|
||||
assert(fd2_top.has_value());
|
||||
EXPECT_EQ(fd1_top->first, fd2_top->first);
|
||||
}
|
||||
|
||||
TEST_F(OpenFilesTest, getCachedFailsIfWrongPermissions)
|
||||
|
@ -104,13 +105,13 @@ TEST_F(OpenFilesTest, opensInReadOnlyUnlessWritableIsRequested)
|
|||
createFileWithContents(filename, Contents);
|
||||
|
||||
// cache a file read-only mode
|
||||
auto fd = session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
||||
EXPECT_TRUE(fd.has_value());
|
||||
assert(fd.has_value());
|
||||
auto fd_top = session_->openFiles().get(0, 0, false, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
||||
EXPECT_TRUE(fd_top.has_value());
|
||||
assert(fd_top.has_value());
|
||||
|
||||
// confirm that writing to it fails
|
||||
auto error = tr_error{};
|
||||
EXPECT_FALSE(tr_sys_file_write(*fd, std::data(Contents), std::size(Contents), nullptr, &error));
|
||||
EXPECT_FALSE(tr_sys_file_write(fd_top->first, std::data(Contents), std::size(Contents), nullptr, &error));
|
||||
EXPECT_TRUE(error);
|
||||
}
|
||||
|
||||
|
@ -120,14 +121,14 @@ TEST_F(OpenFilesTest, createsMissingFileIfWriteRequested)
|
|||
auto filename = tr_pathbuf{ sandboxDir(), "/test-file.txt" };
|
||||
EXPECT_FALSE(tr_sys_path_exists(filename));
|
||||
|
||||
auto fd = session_->openFiles().get(0, 0, false);
|
||||
EXPECT_FALSE(fd);
|
||||
auto fd_top = session_->openFiles().get(0, 0, false);
|
||||
EXPECT_FALSE(fd_top);
|
||||
EXPECT_FALSE(tr_sys_path_exists(filename));
|
||||
|
||||
fd = session_->openFiles().get(0, 0, true, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
||||
EXPECT_TRUE(fd.has_value());
|
||||
assert(fd.has_value());
|
||||
EXPECT_NE(TR_BAD_SYS_FILE, *fd);
|
||||
fd_top = session_->openFiles().get(0, 0, true, filename, TR_PREALLOCATE_FULL, std::size(Contents));
|
||||
EXPECT_TRUE(fd_top.has_value());
|
||||
assert(fd_top.has_value());
|
||||
EXPECT_NE(TR_BAD_SYS_FILE, fd_top->first);
|
||||
EXPECT_TRUE(tr_sys_path_exists(filename));
|
||||
}
|
||||
|
||||
|
@ -196,7 +197,7 @@ TEST_F(OpenFilesTest, closesLeastRecentlyUsedFile)
|
|||
for (int i = 0; i < LargerThanCacheLimit; ++i)
|
||||
{
|
||||
auto filename = tr_pathbuf{ sandboxDir(), fmt::format("/file-{:d}.txt"sv, i) };
|
||||
results[i] = !!session_->openFiles().get(TorId, i, false);
|
||||
results[i] = static_cast<bool>(session_->openFiles().get(TorId, i, false));
|
||||
}
|
||||
sorted = results;
|
||||
std::sort(std::begin(sorted), std::end(sorted));
|
||||
|
|
Loading…
Reference in New Issue