]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Fix Controller.cc TheRoot assertion during shutdown (#1707)
authorAlex Rousskov <rousskov@measurement-factory.com>
Wed, 28 Feb 2024 19:27:15 +0000 (19:27 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Wed, 28 Feb 2024 19:27:20 +0000 (19:27 +0000)
    FATAL: assertion failed: Controller.cc:933: "TheRoot"

Store is an essential service, used by a lot of Squid code. As was
already established in 2022 commit 23b7963, this service should be
available during shutdown. That commit correctly removed explicit Store
service termination, but missed the fact that the reference-counting
TheRoot pointer (that provides access to the Store Controller singleton)
gets _automatically_ destroyed during C++ cleanup. This change removes
TheRoot reference counting, making that Controller singleton immortal.

Squid asserted when exiting with active entries in the shared memory
cache because TheRoot destruction leads to Controller destruction,
Controller destructor cleans up cache index, and that cleanup code may
result in calls to Store::Root() that dereferences destroyed TheRoot.

These assertions were seen when Squid was shutdown cleanly (e.g., using
SIGTERM) and when a kid process was exiting due to a fatal() error.

Making Store::Controller singleton immortal means that the class
destructor is never called. Fortunately, the destructor did nothing
particularly useful; Store flushing is performed by Controller::sync()
which is explicitly called during early stages of clean shutdown. The
now-unused destructor code was removed: Implementing this destructor in
a meaningful way (while avoiding accessing a being-destructed global
Store!) requires heroic efforts (which would be wasted since the
destructor is never actually called).

Also made Store Controller singleton available on the first use,
complying with Store's "essential, widely used service" status and
removing the need for an explicit Store::Init(void) call.

Also removed unit tests that required support for dynamic replacement of
Store Controller singleton. The minuscule value of the removed tests did
not justify the costs of supporting a replaceable Store Controller.

Also removed Store::FreeMemory(). Its implementation contradicted Store
status as an essential service. The function was only used by unit
tests, and its removal addresses the corresponding commit 23b7963 TODO.

src/main.cc
src/store/Controller.cc
src/store/Controller.h
src/tests/stub_libstore.cc
src/tests/testPackableStream.cc
src/tests/testRock.cc
src/tests/testStore.cc
src/tests/testStore.h
src/tests/testStoreController.cc
src/tests/testStoreHashIndex.cc
src/tests/testUfs.cc

index 040c7f1e7b5bcdadcc025b586ba445ce9e2fc8f4..51859775035534f53d2200ca01402a91544186e4 100644 (file)
@@ -1585,7 +1585,6 @@ SquidMain(int argc, char **argv)
         DiskIOModule::SetupAllModules();
 
         /* we may want the parsing process to set this up in the future */
-        Store::Init();
         Acl::Init();
         Auth::Init();      /* required for config parsing. NOP if !USE_AUTH */
         Ip::ProbeTransport(); // determine IPv4 or IPv6 capabilities before parsing.
index 5f5bad1f30eaee3db5f97cbd27e4a7f9897c1ef3..b9dac9bad6d1c1547cb350419b2c24f7ef7faedb 100644 (file)
@@ -41,17 +41,11 @@ Store::Controller::Controller() :
     assert(!store_table);
 }
 
+/// this destructor is never called because Controller singleton is immortal
 Store::Controller::~Controller()
 {
-    delete sharedMemStore;
-    delete transients;
-    delete disks;
-
-    if (store_table) {
-        hashFreeItems(store_table, destroyStoreEntry);
-        hashFreeMemory(store_table);
-        store_table = nullptr;
-    }
+    // assert at runtime because we cannot `= delete` an overridden destructor
+    assert(!"Controller is never destroyed");
 }
 
 void
@@ -927,26 +921,10 @@ Store::Controller::checkTransients(const StoreEntry &e) const
     assert(!transients || e.hasTransients());
 }
 
-namespace Store {
-static RefCount<Controller> TheRoot;
-}
-
 Store::Controller&
 Store::Root()
 {
-    assert(TheRoot);
-    return *TheRoot;
-}
-
-void
-Store::Init(Controller *root)
-{
-    TheRoot = root ? root : new Controller;
-}
-
-void
-Store::FreeMemory()
-{
-    TheRoot = nullptr;
+    static const auto root = new Controller();
+    return *root;
 }
 
index 7930dd6f66154fba7e4b10dc0b22c71514f88b77..aec96d06cdfcfff7b47285d89f5c04115afce2d1 100644 (file)
@@ -23,7 +23,6 @@ class Controller: public Storage
 {
 public:
     Controller();
-    ~Controller() override;
 
     /* Storage API */
     void create() override;
@@ -134,6 +133,8 @@ public:
     static int store_dirs_rebuilding;
 
 private:
+    ~Controller() override;
+
     bool memoryCacheHasSpaceFor(const int pagesRequired) const;
 
     void referenceBusy(StoreEntry &e);
@@ -165,12 +166,6 @@ private:
 /// safely access controller singleton
 extern Controller &Root();
 
-/// initialize the storage module; a custom root is used by unit tests only
-extern void Init(Controller *root = nullptr);
-
-/// undo Init()
-extern void FreeMemory();
-
 } // namespace Store
 
 #endif /* SQUID_SRC_STORE_CONTROLLER_H */
index 3d6dfaaccac3a0ef3d2c85e4b7f144c099236029..d18de336e87ae9cb66b0eae894e4926bbb75115d 100644 (file)
@@ -15,7 +15,7 @@
 namespace Store
 {
 Controller::Controller() {STUB_NOP}
-Controller::~Controller() {STUB_NOP}
+Controller::~Controller() STUB
 void Controller::create() STUB
 void Controller::init() STUB
 uint64_t Controller::maxSize() const STUB_RETVAL(0)
@@ -55,10 +55,7 @@ void Controller::memoryDisconnect(StoreEntry &) STUB
 StoreSearch *Controller::search() STUB_RETVAL(nullptr)
 bool Controller::SmpAware() STUB_RETVAL(false)
 int Controller::store_dirs_rebuilding = 0;
-Controller nil;
-Controller &Root() STUB_RETVAL(Store::nil)
-void Init(Controller *) STUB
-void FreeMemory() STUB
+Controller &Root() STUB_RETREF(Controller)
 }
 
 #include "store/Disk.h"
index d89adb98be9e1f310dbe01cf17ead4d8a9a6516d..7702a0a661d8ddd250c81f7432aa566fa73063ca 100644 (file)
@@ -33,9 +33,6 @@ CPPUNIT_TEST_SUITE_REGISTRATION( TestPackableStream );
 void
 TestPackableStream::testGetStream()
 {
-    /* Setup a store root so we can create a StoreEntry */
-    Store::Init();
-
     CapturingStoreEntry * anEntry = new CapturingStoreEntry();
     {
         anEntry->lock("test");
@@ -59,7 +56,6 @@ TestPackableStream::testGetStream()
         CPPUNIT_ASSERT_EQUAL(String("12345677.7 some text   !."), anEntry->_appended_text);
     }
     delete anEntry; // does the unlock()
-    Store::FreeMemory();
 }
 
 // This test uses main() from ./testStore.cc.
index 0eace9fd61a6243641b5d206433d5f7ef9edbe64..bb89e8c5296815687139ee99814ec52e90dbbce2 100644 (file)
@@ -85,8 +85,6 @@ TestRock::setUp()
     if (0 > system ("rm -rf " TESTDIR))
         throw std::runtime_error("Failed to clean test work directory");
 
-    Store::Init();
-
     store = new Rock::SwapDir();
 
     addSwapDir(store);
@@ -116,8 +114,6 @@ TestRock::tearDown()
 {
     CPPUNIT_NS::TestFixture::tearDown();
 
-    Store::FreeMemory();
-
     store = nullptr;
 
     free_cachedir(&Config.cacheSwap);
index 54e1c96ccddeda8fb1d63eadaa7bca812a1f86b9..419326949e2a35f80b9797271784b2106e13ba70 100644 (file)
 
 CPPUNIT_TEST_SUITE_REGISTRATION( TestStore );
 
-int
-StoreControllerStub::callback()
-{
-    return 1;
-}
-
-StoreEntry*
-StoreControllerStub::get(const cache_key*)
-{
-    return nullptr;
-}
-
-void
-StoreControllerStub::get(String, void (*)(StoreEntry*, void*), void*)
-{}
-
-void
-StoreControllerStub::init()
-{}
-
-uint64_t
-StoreControllerStub::maxSize() const
-{
-    return 3;
-}
-
-uint64_t
-StoreControllerStub::minSize() const
-{
-    return 1;
-}
-
-uint64_t
-StoreControllerStub::currentSize() const
-{
-    return 2;
-}
-
-uint64_t
-StoreControllerStub::currentCount() const
-{
-    return 2;
-}
-
-int64_t
-StoreControllerStub::maxObjectSize() const
-{
-    return 1;
-}
-
-void
-StoreControllerStub::getStats(StoreInfoStats &) const
-{
-}
-
-void
-StoreControllerStub::stat(StoreEntry &) const
-{
-    const_cast<StoreControllerStub *>(this)->statsCalled = true;
-}
-
-StoreSearch *
-StoreControllerStub::search()
-{
-    return nullptr;
-}
-
-void
-TestStore::testSetRoot()
-{
-    Store::Controller *aStore(new StoreControllerStub);
-    Store::Init(aStore);
-
-    CPPUNIT_ASSERT_EQUAL(&Store::Root(), aStore);
-    Store::FreeMemory();
-}
-
-void
-TestStore::testUnsetRoot()
-{
-    Store::Controller *aStore(new StoreControllerStub);
-    Store::Controller *aStore2(new StoreControllerStub);
-    Store::Init(aStore);
-    Store::FreeMemory();
-    Store::Init(aStore2);
-    CPPUNIT_ASSERT_EQUAL(&Store::Root(),aStore2);
-    Store::FreeMemory();
-}
-
-void
-TestStore::testStats()
-{
-    StoreControllerStub *aStore(new StoreControllerStub);
-    Store::Init(aStore);
-    CPPUNIT_ASSERT_EQUAL(false, aStore->statsCalled);
-    StoreEntry entry;
-    Store::Stats(&entry);
-    CPPUNIT_ASSERT_EQUAL(true, aStore->statsCalled);
-    Store::FreeMemory();
-}
-
-void
-TestStore::testMaxSize()
-{
-    Store::Controller *aStore(new StoreControllerStub);
-    Store::Init(aStore);
-    CPPUNIT_ASSERT_EQUAL(static_cast<uint64_t>(3), aStore->maxSize());
-    Store::FreeMemory();
-}
-
 namespace Store {
 
 /// check rawType that may be ignored
index accf6d9b74982c2b2611c8bf9addbf39eb499b5b..4c8dd3c9cfb7d31552ba7bb7016253db2d8a3046 100644 (file)
@@ -9,9 +9,6 @@
 #ifndef SQUID_SRC_TESTS_TESTSTORE_H
 #define SQUID_SRC_TESTS_TESTSTORE_H
 
-#include "Store.h"
-#include "store/Controlled.h"
-
 #include "compat/cppunit.h"
 
 /*
 class TestStore: public CPPUNIT_NS::TestFixture
 {
     CPPUNIT_TEST_SUITE( TestStore );
-    CPPUNIT_TEST( testSetRoot );
-    CPPUNIT_TEST( testUnsetRoot );
-    CPPUNIT_TEST( testStats );
-    CPPUNIT_TEST( testMaxSize );
     CPPUNIT_TEST( testSwapMetaTypeClassification );
     CPPUNIT_TEST_SUITE_END();
 
 public:
 
 protected:
-    void testSetRoot();
-    void testUnsetRoot();
-    void testStats();
-    void testMaxSize();
     void testSwapMetaTypeClassification();
 };
 
-/// allows testing of methods without having all the other components live
-class StoreControllerStub : public Store::Controller
-{
-
-public:
-    StoreControllerStub() : statsCalled (false) {}
-
-    bool statsCalled;
-
-    int callback() override;
-
-    virtual StoreEntry* get(const cache_key*);
-
-    virtual void get(String, void (*)(StoreEntry*, void*), void*);
-
-    void init() override;
-
-    void maintain() override {};
-
-    uint64_t maxSize() const override;
-
-    uint64_t minSize() const override;
-
-    uint64_t currentSize() const override;
-
-    uint64_t currentCount() const override;
-
-    int64_t maxObjectSize() const override;
-
-    void getStats(StoreInfoStats &) const override;
-
-    void stat(StoreEntry &) const override; /* output stats to the provided store entry */
-
-    virtual void reference(StoreEntry &) {} /* Reference this object */
-
-    virtual bool dereference(StoreEntry &) { return true; }
-
-    virtual StoreSearch *search();
-};
-
-typedef RefCount<StoreControllerStub> StoreControllerStubPointer;
-
 #endif /* SQUID_SRC_TESTS_TESTSTORE_H */
 
index 06e8f8fbb0b6eeadaaac7cbac25c6b6d6b9f6889..205dc59b8c052d468073dfce42a09673812aedf6 100644 (file)
@@ -46,7 +46,6 @@ addSwapDir(TestSwapDirPointer aStore)
 void
 TestStoreController::testStats()
 {
-    Store::Init();
     StoreEntry *logEntry = new StoreEntry;
     logEntry->createMemObject("dummy_storeId", nullptr, HttpRequestMethod());
     logEntry->store_status = STORE_PENDING;
@@ -60,7 +59,6 @@ TestStoreController::testStats()
     free_cachedir(&Config.cacheSwap);
     CPPUNIT_ASSERT_EQUAL(true, aStore->statsCalled);
     CPPUNIT_ASSERT_EQUAL(true, aStore2->statsCalled);
-    Store::FreeMemory();
 }
 
 static void
@@ -91,14 +89,12 @@ TestStoreController::testMaxSize()
     StoreEntry *logEntry = new StoreEntry;
     logEntry->createMemObject("dummy_storeId", nullptr, HttpRequestMethod());
     logEntry->store_status = STORE_PENDING;
-    Store::Init();
     TestSwapDirPointer aStore (new TestSwapDir);
     TestSwapDirPointer aStore2 (new TestSwapDir);
     addSwapDir(aStore);
     addSwapDir(aStore2);
     CPPUNIT_ASSERT_EQUAL(static_cast<uint64_t>(6), Store::Root().maxSize());
     free_cachedir(&Config.cacheSwap);
-    Store::FreeMemory();
 }
 
 static StoreEntry *
@@ -147,7 +143,6 @@ void
 TestStoreController::testSearch()
 {
     commonInit();
-    Store::Init();
     TestSwapDirPointer aStore (new TestSwapDir);
     TestSwapDirPointer aStore2 (new TestSwapDir);
     addSwapDir(aStore);
@@ -195,8 +190,6 @@ TestStoreController::testSearch()
     CPPUNIT_ASSERT_EQUAL(true, search->isDone());
     CPPUNIT_ASSERT_EQUAL(static_cast<StoreEntry *>(nullptr), search->currentItem());
     //CPPUNIT_ASSERT_EQUAL(false, search->next());
-
-    Store::FreeMemory();
 }
 
 // This test uses main() from ./testStore.cc.
index 72813338de39197d3e56572395e850c60956d2b5..232664e8c7aa2e89c024b8c18902c0076bdf2412 100644 (file)
@@ -49,7 +49,6 @@ TestStoreHashIndex::testStats()
     StoreEntry *logEntry = new StoreEntry;
     logEntry->createMemObject("dummy_storeId", nullptr, HttpRequestMethod());
     logEntry->store_status = STORE_PENDING;
-    Store::Init();
     TestSwapDirPointer aStore (new TestSwapDir);
     TestSwapDirPointer aStore2 (new TestSwapDir);
     addSwapDir(aStore);
@@ -60,7 +59,6 @@ TestStoreHashIndex::testStats()
     free_cachedir(&Config.cacheSwap);
     CPPUNIT_ASSERT_EQUAL(true, aStore->statsCalled);
     CPPUNIT_ASSERT_EQUAL(true, aStore2->statsCalled);
-    Store::FreeMemory();
 }
 
 void
@@ -69,14 +67,12 @@ TestStoreHashIndex::testMaxSize()
     StoreEntry *logEntry = new StoreEntry;
     logEntry->createMemObject("dummy_storeId", nullptr, HttpRequestMethod());
     logEntry->store_status = STORE_PENDING;
-    Store::Init();
     TestSwapDirPointer aStore (new TestSwapDir);
     TestSwapDirPointer aStore2 (new TestSwapDir);
     addSwapDir(aStore);
     addSwapDir(aStore2);
     CPPUNIT_ASSERT_EQUAL(static_cast<uint64_t>(6), Store::Root().maxSize());
     free_cachedir(&Config.cacheSwap);
-    Store::FreeMemory();
 }
 
 static StoreEntry *
@@ -147,7 +143,6 @@ void
 TestStoreHashIndex::testSearch()
 {
     commonInit();
-    Store::Init();
     TestSwapDirPointer aStore (new TestSwapDir);
     TestSwapDirPointer aStore2 (new TestSwapDir);
     addSwapDir(aStore);
@@ -195,8 +190,6 @@ TestStoreHashIndex::testSearch()
     CPPUNIT_ASSERT_EQUAL(true, search->isDone());
     CPPUNIT_ASSERT_EQUAL(static_cast<StoreEntry *>(nullptr), search->currentItem());
     //CPPUNIT_ASSERT_EQUAL(false, search->next());
-
-    Store::FreeMemory();
 }
 
 // This test uses main() from ./testStore.cc.
index cfdad7498f98e368334c6f7fba87daf2b8c6de37..5daec5e5b996678364d8582d4ac89b90c55e0b03 100644 (file)
@@ -110,8 +110,6 @@ TestUfs::testUfsSearch()
     if (0 > system ("rm -rf " TESTDIR))
         throw std::runtime_error("Failed to clean test work directory");
 
-    Store::Init();
-
     MySwapDirPointer aStore (new Fs::Ufs::UFSSwapDir("ufs", "Blocking"));
 
     aStore->IO = new Fs::Ufs::UFSStrategy(DiskIOModule::Find("Blocking")->createStrategy());
@@ -213,8 +211,6 @@ TestUfs::testUfsSearch()
     CPPUNIT_ASSERT_EQUAL(true, search->isDone());
     CPPUNIT_ASSERT_EQUAL(static_cast<StoreEntry *>(nullptr), search->currentItem());
 
-    Store::FreeMemory();
-
     free_cachedir(&Config.cacheSwap);
 
     // TODO: here we should test a dirty rebuild
@@ -236,12 +232,6 @@ TestUfs::testUfsDefaultEngine()
     if (0 > system ("rm -rf " TESTDIR))
         throw std::runtime_error("Failed to clean test work directory");
 
-    // This assertion may fail if previous test cases fail.
-    // Apparently, CPPUNIT_ASSERT* failure may prevent destructors of local
-    // objects such as "StorePointer aRoot" from being called.
-    CPPUNIT_ASSERT(!store_table); // or StoreHashIndex ctor will abort below
-
-    Store::Init();
     MySwapDirPointer aStore (new Fs::Ufs::UFSSwapDir("ufs", "Blocking"));
     addSwapDir(aStore);
     commonInit();
@@ -257,7 +247,6 @@ TestUfs::testUfsDefaultEngine()
     safe_free(config_line);
     CPPUNIT_ASSERT(aStore->IO->io != nullptr);
 
-    Store::FreeMemory();
     free_cachedir(&Config.cacheSwap);
     safe_free(Config.replPolicy->type);
     delete Config.replPolicy;