diff --git a/ql/patterns/observable.cpp b/ql/patterns/observable.cpp index e5a2b4e35a0..ba2c7892061 100644 --- a/ql/patterns/observable.cpp +++ b/ql/patterns/observable.cpp @@ -33,8 +33,12 @@ namespace QuantLib { if (!deferredObservers_.empty()) { bool successful = true; std::string errMsg; + runningDeferredUpdates_ = true; + + for (const auto& [deferredObserver, isValid] : deferredObservers_) { + if (!isValid) + continue; - for (auto* deferredObserver : deferredObservers_) { try { deferredObserver->update(); } catch (std::exception& e) { @@ -46,10 +50,12 @@ namespace QuantLib { } deferredObservers_.clear(); + runningDeferredUpdates_ = false; QL_ENSURE(successful, "could not notify one or more observers: " << errMsg); } + } diff --git a/ql/patterns/observable.hpp b/ql/patterns/observable.hpp index 71212a5e687..7eb126173e4 100644 --- a/ql/patterns/observable.hpp +++ b/ql/patterns/observable.hpp @@ -34,6 +34,7 @@ FOR A PARTICULAR PURPOSE. See the license for more details. #include #include #include +#include #if !defined(QL_USE_STD_SHARED_PTR) && BOOST_VERSION < 107400 @@ -96,11 +97,12 @@ namespace QuantLib { bool updatesEnabled() const { return updatesEnabled_; } bool updatesDeferred() const { return updatesDeferred_; } + bool runningDeferredUpdates() const { return runningDeferredUpdates_; } private: ObservableSettings() = default; - typedef std::set set_type; + typedef std::map set_type; typedef set_type::iterator iterator; void registerDeferredObservers(const Observable::set_type& observers); @@ -109,6 +111,7 @@ namespace QuantLib { set_type deferredObservers_; bool updatesEnabled_ = true, updatesDeferred_ = false; + bool runningDeferredUpdates_ = false; }; //! Object that gets notified when a given observable changes @@ -160,12 +163,20 @@ namespace QuantLib { inline void ObservableSettings::registerDeferredObservers(const Observable::set_type& observers) { if (updatesDeferred()) { - deferredObservers_.insert(observers.begin(), observers.end()); + for (Observer* obs : observers) + deferredObservers_.emplace(obs, true); } } inline void ObservableSettings::unregisterDeferredObserver(Observer* o) { - deferredObservers_.erase(o); + if (updatesDeferred()) + deferredObservers_.erase(o); + else + { + auto it = deferredObservers_.find(o); + if (it != deferredObservers_.end()) + it->second = false; + } } inline Observable::Observable(const Observable&) { @@ -195,7 +206,8 @@ namespace QuantLib { } inline Size Observable::unregisterObserver(Observer* o) { - if (ObservableSettings::instance().updatesDeferred()) + if (ObservableSettings::instance().updatesDeferred() || + ObservableSettings::instance().runningDeferredUpdates()) ObservableSettings::instance().unregisterDeferredObserver(o); return observers_.erase(o); diff --git a/test-suite/observable.cpp b/test-suite/observable.cpp index 0254c71dbbb..8981e71567a 100644 --- a/test-suite/observable.cpp +++ b/test-suite/observable.cpp @@ -20,14 +20,19 @@ #include "toplevelfixture.hpp" #include "utilities.hpp" #include +#include #include #include #include +#include +#include #include #include #include #include +#include #include +#include #include #include @@ -395,6 +400,20 @@ BOOST_AUTO_TEST_CASE(testAddAndDeleteObserverDuringNotifyObservers) { } } +BOOST_AUTO_TEST_CASE(testDeferredObserverLifetime) +{ + Date today(24, Dec, 2025); + Settings::instance().evaluationDate() = today; + Handle quote(ext::make_shared(0.02)); + auto zciHelper = ext::make_shared( + quote, 3 * Months, Date(29, Dec, 2026), TARGET(), ModifiedFollowing, + ActualActual(ActualActual::ISDA), ext::make_shared(), CPI::Flat); + + ObservableSettings::instance().disableUpdates(true); + Settings::instance().evaluationDate() = Date(29, Dec, 2025); + ObservableSettings::instance().enableUpdates(); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()