From 69ab9165639ae5addc9319020abd6c656d8d841f Mon Sep 17 00:00:00 2001 From: Acts1631 Date: Thu, 27 Aug 2026 09:26:48 -0400 Subject: [PATCH] input/cache: synchronize lease notifications with lifetime The buffering thread may notify a lease while CacheInputStream is still being constructed or after its destruction has begun. This races virtual dispatch and can access the stream after it is freed. Keep the registered lease in a member with an explicit lifetime. Register it only after stream initialization, unregister it before stream teardown, and hold the item mutex until each callback returns. --- src/input/cache/Lease.hxx | 10 ++++++++-- src/input/cache/Stream.cxx | 23 +++++++++++++++-------- src/input/cache/Stream.hxx | 25 ++++++++++++++++++++++--- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/input/cache/Lease.hxx b/src/input/cache/Lease.hxx index 8f920fe058..be4b479d7b 100644 --- a/src/input/cache/Lease.hxx +++ b/src/input/cache/Lease.hxx @@ -36,8 +36,7 @@ public: } ~InputCacheLease() noexcept { - if (item != nullptr) - item->RemoveLease(*this); + Reset(); } InputCacheLease &operator=(InputCacheLease &&src) noexcept { @@ -68,6 +67,13 @@ public: return *item; } + void Reset() noexcept { + if (item != nullptr) { + item->RemoveLease(*this); + item = nullptr; + } + } + /** * Caller locks #InputCacheItem::mutex. */ diff --git a/src/input/cache/Stream.cxx b/src/input/cache/Stream.cxx index f55c47cb4b..b0d9615e71 100644 --- a/src/input/cache/Stream.cxx +++ b/src/input/cache/Stream.cxx @@ -7,12 +7,21 @@ CacheInputStream::CacheInputStream(InputCacheLease _lease, Mutex &_mutex) noexcept :InputStream(_lease->GetUri().c_str(), _mutex), - InputCacheLease(std::move(_lease)) + lease(*this) { - const auto &i = GetCacheItem(); + const auto &i = _lease.GetCacheItem(); size = i.size(); seekable = true; SetReady(); + + /* Register only after this object has been fully initialized. */ + lease.Set(std::move(_lease)); +} + +CacheInputStream::~CacheInputStream() noexcept +{ + /* Wait for a running notification before destructing this object. */ + lease.Reset(); } void @@ -20,7 +29,7 @@ CacheInputStream::Check() { const ScopeUnlock unlock(mutex); - auto &i = GetCacheItem(); + auto &i = lease.GetCacheItem(); const std::lock_guard protect{i.mutex}; i.Check(); @@ -44,7 +53,7 @@ CacheInputStream::IsAvailable() const noexcept const auto _offset = offset; const ScopeUnlock unlock(mutex); - auto &i = GetCacheItem(); + auto &i = lease.GetCacheItem(); const std::lock_guard protect{i.mutex}; return i.IsAvailable(_offset); @@ -55,7 +64,7 @@ CacheInputStream::Read(std::unique_lock &lock, std::span dest) { const auto _offset = offset; - auto &i = GetCacheItem(); + auto &i = lease.GetCacheItem(); size_t nbytes; @@ -76,9 +85,7 @@ CacheInputStream::Read(std::unique_lock &lock, void CacheInputStream::OnInputCacheAvailable(std::unique_lock &lock) noexcept { - assert(lock.mutex() == &GetCacheItem().mutex); - - const ScopeUnlock unlock{lock}; + assert(lock.mutex() == &lease.GetCacheItem().mutex); const std::lock_guard protect{mutex}; InvokeOnAvailable(); diff --git a/src/input/cache/Stream.hxx b/src/input/cache/Stream.hxx index ad413d3b6c..b930f02cc8 100644 --- a/src/input/cache/Stream.hxx +++ b/src/input/cache/Stream.hxx @@ -10,9 +10,29 @@ * An #InputStream implementation which reads data from an * #InputCacheItem. */ -class CacheInputStream final : public InputStream, InputCacheLease { +class CacheInputStream final : public InputStream { + class Lease final : public InputCacheLease { + CacheInputStream &stream; + + public: + explicit Lease(CacheInputStream &_stream) noexcept + :stream(_stream) {} + + void Set(InputCacheLease &&src) noexcept { + InputCacheLease::operator=(std::move(src)); + } + + private: + void OnInputCacheAvailable(std::unique_lock &lock) noexcept override { + stream.OnInputCacheAvailable(lock); + } + }; + + Lease lease; + public: CacheInputStream(InputCacheLease _lease, Mutex &_mutex) noexcept; + ~CacheInputStream() noexcept override; /* virtual methods from class InputStream */ void Check() override; @@ -28,6 +48,5 @@ public: std::span dest) override; private: - /* virtual methods from class InputCacheLease */ - void OnInputCacheAvailable(std::unique_lock &lock) noexcept override; + void OnInputCacheAvailable(std::unique_lock &lock) noexcept; };