reader_concurrency_semaphore: make inactive_read_handle a weak reference

Having the handle keep an owning reference to the inactive read lead to
awkward situations, where the inactive read is destroyed during eviction
in certain situations only (querier cache) and not in other cases.
Although the users didn't notice anything from this, it lead to very
brittle code inside the reader concurrency semaphore. Among others, the
inactive read destructor has to be open coded in evict() which already
lead to mistakes.
This patch goes back to the weak pointer paradigm used a while ago,
which is a much more natural fit for this. Inactive reads are still kept
in an intrusive list in the semaphore but the handle now keeps a weak
pointer to them. When destroyed the handler will destroy the inactive
read if it is still alive. When evicting the inactive read, it will
set the pointer in the handle to null.
This commit is contained in:
Botond Dénes
2021-03-12 18:04:41 +02:00
parent cbc83b8b1b
commit 581edc4e4e
2 changed files with 44 additions and 15 deletions

View File

@@ -340,6 +340,18 @@ void reader_concurrency_semaphore::expiry_handler::operator()(entry& e) noexcept
}
reader_concurrency_semaphore::inactive_read::~inactive_read() {
detach();
}
void reader_concurrency_semaphore::inactive_read::detach() noexcept {
if (handle) {
handle->_irp = nullptr;
handle = nullptr;
}
}
void reader_concurrency_semaphore::inactive_read_handle::abandon() noexcept {
delete std::exchange(_irp, nullptr);
}
void reader_concurrency_semaphore::signal(const resources& r) noexcept {
@@ -385,7 +397,7 @@ reader_concurrency_semaphore::inactive_read_handle reader_concurrency_semaphore:
auto& ir = *irp;
_inactive_reads.push_back(ir);
++_stats.inactive_reads;
return inactive_read_handle(*this, std::move(irp));
return inactive_read_handle(*this, *irp.release());
} catch (...) {
// It is okay to swallow the exception since
// we're allowed to drop the reader upon registration
@@ -427,8 +439,7 @@ flat_mutation_reader_opt reader_concurrency_semaphore::unregister_inactive_read(
}
--_stats.inactive_reads;
auto irp = std::move(irh._irp);
irp->unlink();
std::unique_ptr<inactive_read> irp(irh._irp);
return std::move(irp->reader);
}
@@ -441,13 +452,11 @@ bool reader_concurrency_semaphore::try_evict_one_inactive_read(evict_reason reas
}
void reader_concurrency_semaphore::evict(inactive_read& ir, evict_reason reason) noexcept {
auto reader = std::move(ir.reader);
ir.unlink();
ir.detach();
std::unique_ptr<inactive_read> irp(&ir);
try {
if (auto notify_handler = std::move(ir.notify_handler)) {
notify_handler(reason);
// The notify_handler may destroy the inactive_read.
// Do not use it after this point!
if (ir.notify_handler) {
ir.notify_handler(reason);
}
} catch (...) {
rcslog.error("[semaphore {}] evict(): notify handler failed for inactive read evicted due to {}: {}", _name, reason, std::current_exception());

View File

@@ -81,6 +81,8 @@ public:
struct permit_list;
class inactive_read_handle;
private:
struct entry {
promise<reader_permit::resource_units> pr;
@@ -102,12 +104,13 @@ private:
flat_mutation_reader reader;
eviction_notify_handler notify_handler;
timer<lowres_clock> ttl_timer;
inactive_read_handle* handle = nullptr;
explicit inactive_read(flat_mutation_reader reader_) noexcept
: reader(std::move(reader_))
{ }
inactive_read(inactive_read&&) = default;
~inactive_read();
void detach() noexcept;
};
using inactive_reads_type = bi::list<inactive_read, bi::constant_time_size<false>>;
@@ -115,24 +118,41 @@ private:
public:
class inactive_read_handle {
reader_concurrency_semaphore* _sem = nullptr;
std::unique_ptr<inactive_read> _irp;
inactive_read* _irp = nullptr;
friend class reader_concurrency_semaphore;
explicit inactive_read_handle(reader_concurrency_semaphore& sem, std::unique_ptr<inactive_read> irp) noexcept
: _sem(&sem), _irp(std::move(irp)) {
private:
void abandon() noexcept;
explicit inactive_read_handle(reader_concurrency_semaphore& sem, inactive_read& ir) noexcept
: _sem(&sem), _irp(&ir) {
_irp->handle = this;
}
public:
inactive_read_handle() = default;
inactive_read_handle(inactive_read_handle&& o) noexcept
: _sem(std::exchange(o._sem, nullptr))
, _irp(std::move(o._irp)) {
, _irp(std::exchange(o._irp, nullptr)) {
if (_irp) {
_irp->handle = this;
}
}
inactive_read_handle& operator=(inactive_read_handle&& o) noexcept {
if (this == &o) {
return *this;
}
abandon();
_sem = std::exchange(o._sem, nullptr);
_irp = std::move(o._irp);
_irp = std::exchange(o._irp, nullptr);
if (_irp) {
_irp->handle = this;
}
return *this;
}
~inactive_read_handle() {
abandon();
}
explicit operator bool() const noexcept {
return bool(_irp);
}