]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Fewer "busy shutdown" crashes; simpler shutdown cleanup (#1128)
authorAlex Rousskov <rousskov@measurement-factory.com>
Thu, 1 Sep 2022 02:34:26 +0000 (02:34 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Fri, 2 Sep 2022 08:30:23 +0000 (08:30 +0000)
    Preparing for shutdown after 186724 requests
    Waiting 1 seconds for active connections to finish
    Closing HTTP(S) port [::]:3128
    FATAL: assertion failed: Transients.cc:192:
        "oldE->mem_obj && oldE->mem_obj->xitTable.index == index"

This change removes two steps at the end of the manual shutdown sequence
(FreeAllFs() and Store::FreeMemory()) and associated deadly Store hacks,
adjusting a few Store checks as needed. Also, StoreFileSystem hierarchy
declarations were slightly updated when half of its virtual methods were
removed. Key changes are detailed below.

### Do not disable Store [by dropping Store::Root()] during shutdown

Disabling Store (i.e. calling Store::FreeMemory()) resulted in "do not
use Root() during shutdown" assertion-avoiding hacks that bypassed code
maintaining key invariants. Those hacks were probably accommodating
Store-using destruction sequences triggered by SquidShutdown() or later
code, _but_ the hacks also ran during the graceful shutdown period!
Violating those invariants in MemObject destructor led to the quoted
assertions when CollapsedForwarding::HandleNotification() ran during
graceful shutdown.

We could add more state to narrow down hacks scope, or we could disable
IPC notifications that triggered the known deadly shutdown sequence, but

* We should not disable assertions during shutdown because many are
  about essential (e.g., preventing data corruption) invariants and
  because it is not practical to disable all assertions that should be
  disabled (and keep disabling the new ones correctly). The idea leads
  to unsafe code that is also more difficult to write and maintain.

* We should not disable kid-to-kid notifications because some are
  required for finishing transactions (that can still be successfully
  finished) during graceful shutdown. Increasing successful completion
  chances is the whole idea behind that graceful shutdown feature!

Instead, Store should provide (essential) services during shutdown.

TODO: Update those unit test cases that use Store::FreeMemory() but
should not. That requires careful analysis because the next test cases
may rely on the previous case removing its Store object/statistics/etc.
The same dedicated project might (help) address commit 27685ef TODO
about unwanted Store unit test order dependencies.

### Do not disable StoreFileSystem service during shutdown

Similar to the Store service, manually killing FS service is a bad idea.
The underlying code was also essentially unused and buggy:

* Nobody called RegisterAllFsWithCacheManager(). A caller would assert.
* Nobody called Fs::Clean(). A call could crash Squid because deleted
  objects could still be in use by others (they are not refcounted).
* SetupAllFs() was called but did essentially nothing.
* FreeAllFs() was called but had only harmful effects: It leaked memory
  and removed usable FS registrations, increasing shutdown dangers.

See also: Commit 3a85184 documents why we should avoid explicit "delete
essentialService" calls (and why some crashes may still occur if we do).

14 files changed:
src/MemObject.cc
src/MemStore.cc
src/StoreFileSystem.cc
src/StoreFileSystem.h
src/fs/Module.cc
src/fs/Module.h
src/fs/rock/RockStoreFileSystem.cc
src/fs/rock/RockStoreFileSystem.h
src/fs/ufs/StoreFSufs.h
src/main.cc
src/store.cc
src/store_client.cc
src/store_swapout.cc
src/tests/testRock.cc

index 93ecf3f7cc60d44a8cb19d4a8850724b2208ac72..a8e5aef1ae0a33a15d4c26671f5b651bb3705277 100644 (file)
@@ -110,11 +110,9 @@ MemObject::~MemObject()
     checkUrlChecksum();
 #endif
 
-    if (!shutting_down) { // Store::Root() is FATALly missing during shutdown
-        assert(xitTable.index < 0);
-        assert(memCache.index < 0);
-        assert(swapout.sio == nullptr);
-    }
+    assert(xitTable.index < 0);
+    assert(memCache.index < 0);
+    assert(swapout.sio == nullptr);
 
     data_hdr.freeContent();
 }
index 5f2ae2904c76cf16469d8f4d2fa3710ed56eae65..9dbd3289889d1d84b3115cf224376b0652c2d67e 100644 (file)
@@ -587,9 +587,8 @@ MemStore::shouldCache(StoreEntry &e) const
         return false;
     }
 
-    // Store::Root() in the next check below is FATALly missing during shutdown
     if (shutting_down) {
-        debugs(20, 5, "yield to shutdown: " << e);
+        debugs(20, 5, "avoid heavy optional work during shutdown: " << e);
         return false;
     }
 
index fb58e96e7d99b7863a9fc3f8c749bcb1b4fada8b..066cdcf62c4dd597d9e5bbb90a815a2d25314997 100644 (file)
 
 std::vector<StoreFileSystem*> *StoreFileSystem::_FileSystems = nullptr;
 
-void
-StoreFileSystem::RegisterAllFsWithCacheManager(void)
-{
-    for (iterator i = GetFileSystems().begin(); i != GetFileSystems().end(); ++i)
-        (*i)->registerWithCacheManager();
-}
-
-void
-StoreFileSystem::SetupAllFs()
-{
-    for (iterator i = GetFileSystems().begin(); i != GetFileSystems().end(); ++i)
-        /* Call the FS to set up capabilities and initialize the FS driver */
-        (*i)->setup();
-}
-
 void
 StoreFileSystem::FsAdd(StoreFileSystem &instance)
 {
@@ -56,20 +41,6 @@ StoreFileSystem::GetFileSystems()
     return *_FileSystems;
 }
 
-/*
- * called when a graceful shutdown is to occur
- * of each fs module.
- */
-void
-StoreFileSystem::FreeAllFs()
-{
-    while (!GetFileSystems().empty()) {
-        StoreFileSystem *fs = GetFileSystems().back();
-        GetFileSystems().pop_back();
-        fs->done();
-    }
-}
-
 StoreFileSystem *
 StoreFileSystem::FindByType(const char *type)
 {
@@ -80,8 +51,3 @@ StoreFileSystem::FindByType(const char *type)
     return nullptr;
 }
 
-/* no filesystem is required to export statistics */
-void
-StoreFileSystem::registerWithCacheManager(void)
-{}
-
index 92738ce431dea5b0734fa7bb37fd6821a94f560f..b558a18070aef2c4d8bc82bc7fa7f725c314f82f 100644 (file)
@@ -9,6 +9,7 @@
 #ifndef SQUID_STOREFILESYSTEM_H
 #define SQUID_STOREFILESYSTEM_H
 
+#include "base/TypeTraits.h"
 #include "store/forward.h"
 #include <vector>
 
  * The core API for storage modules this class provides all the hooks
  * squid uses to interact with a filesystem IO module.
  */
-class StoreFileSystem
+class StoreFileSystem: public Interface
 {
 
 public:
-    static void SetupAllFs();
     static void FsAdd(StoreFileSystem &);
-    static void FreeAllFs();
     static StoreFileSystem *FindByType(const char *type);
     static std::vector<StoreFileSystem*> const &FileSystems();
     typedef std::vector<StoreFileSystem*>::iterator iterator;
     typedef std::vector<StoreFileSystem*>::const_iterator const_iterator;
-    StoreFileSystem() : initialised(false) {}
 
-    virtual ~StoreFileSystem() {}
+    StoreFileSystem() = default;
+    StoreFileSystem(StoreFileSystem &&) = delete; // no copying/moving of any kind
 
     virtual char const *type () const = 0;
     virtual SwapDir *createSwapDir() = 0;
-    virtual void done() = 0;
-    virtual void setup() = 0;
-    // Not implemented
-    StoreFileSystem(StoreFileSystem const &);
-    StoreFileSystem &operator=(StoreFileSystem const&);
-
-protected:
-    bool initialised;
-    virtual void registerWithCacheManager(void);
 
 private:
     static std::vector<StoreFileSystem*> &GetFileSystems();
     static std::vector<StoreFileSystem*> *_FileSystems;
-    static void RegisterAllFsWithCacheManager(void);
 };
 
 // TODO: Kill this typedef!
index 1c0ec9a0febb201ec8e79d63a1368d89bd334582..b8319e473b25e9d7c9693ca3c3be3a10062978e0 100644 (file)
@@ -51,23 +51,3 @@ void Fs::Init()
 
 }
 
-void Fs::Clean()
-{
-#if HAVE_FS_UFS
-    delete UfsInstance;
-#endif
-
-#if HAVE_FS_AUFS
-    delete AufsInstance;
-#endif
-
-#if HAVE_FS_DISKD
-    delete DiskdInstance;
-#endif
-
-#if HAVE_FS_ROCK
-    delete RockInstance;
-#endif
-
-}
-
index dcb51dd17916ff6b62d9250c83d73d428bd774f9..473e12cd27612a9a2ce88afe8fa9301ea55711df 100644 (file)
@@ -13,7 +13,6 @@ namespace Fs
 {
 
 void Init();
-void Clean();
 
 } // namespace Fs
 
index e3b4bd5762e88ed117e275cd2378bf12739ba929..d8c67c0e76bb998943d5d379fa6672f7af542493 100644 (file)
@@ -33,26 +33,3 @@ Rock::StoreFileSystem::createSwapDir()
     return new SwapDir();
 }
 
-void
-Rock::StoreFileSystem::done()
-{
-}
-
-void
-Rock::StoreFileSystem::registerWithCacheManager()
-{
-    assert(false); // XXX: implement
-}
-
-void
-Rock::StoreFileSystem::setup()
-{
-    debugs(92,2, "Will use Rock FS");
-}
-
-void
-Rock::StoreFileSystem::Stats(StoreEntry *)
-{
-    assert(false); // XXX: implement
-}
-
index 61492891bf10dbdeeec5f38b9d8e4aac892b7fe5..a7cbae6ca384944ccc44b285810c9f3b822a7f28 100644 (file)
@@ -25,11 +25,9 @@ public:
     StoreFileSystem();
     virtual ~StoreFileSystem();
 
-    virtual char const *type() const;
-    virtual SwapDir *createSwapDir();
-    virtual void done();
-    virtual void registerWithCacheManager();
-    virtual void setup();
+    /* StoreFileSystem API */
+    virtual char const *type() const override;
+    virtual SwapDir *createSwapDir() override;
 
 private:
     //static Stats Stats_;
index 5434ac0eb419e98f24de6c85b42621b0254c48f9..7c453da674fb841501a13632525b1876814ba9ce 100644 (file)
@@ -38,13 +38,9 @@ public:
     StoreFSufs(char const *DefaultModuleType, char const *label);
     virtual ~StoreFSufs() {}
 
-    virtual char const *type() const;
-    virtual SwapDir *createSwapDir();
-    virtual void done();
-    virtual void setup();
-    /** Not implemented */
-    StoreFSufs (StoreFSufs const &);
-    StoreFSufs &operator=(StoreFSufs const &);
+    /* StoreFileSystem API */
+    virtual const char *type() const override;
+    virtual SwapDir *createSwapDir() override;
 
 protected:
     DiskIOModule *IO;
@@ -73,21 +69,6 @@ StoreFSufs<C>::createSwapDir()
     return result;
 }
 
-template <class C>
-void
-StoreFSufs<C>::done()
-{
-    initialised = false;
-}
-
-template <class C>
-void
-StoreFSufs<C>::setup()
-{
-    assert(!initialised);
-    initialised = true;
-}
-
 } /* namespace Ufs */
 } /* namespace Fs */
 
index 64184e6547d3a0df37fbd83488d5d329bf76c44b..74fe2a08965c7dc444dadefeec863371c607b53b 100644 (file)
@@ -1559,15 +1559,11 @@ SquidMain(int argc, char **argv)
 
         storeFsInit();      /* required for config parsing */
 
-        /* TODO: call the FS::Clean() in shutdown to do Fs cleanups */
         Fs::Init();
 
         /* May not be needed for parsing, have not audited for such */
         DiskIOModule::SetupAllModules();
 
-        /* Shouldn't be needed for config parsing, but have not audited for such */
-        StoreFileSystem::SetupAllFs();
-
         /* we may want the parsing process to set this up in the future */
         Store::Init();
         Acl::Init();
@@ -2112,9 +2108,7 @@ SquidShutdown()
     storeLogClose();
     accessLogClose();
     Store::Root().sync();       /* Flush log close */
-    StoreFileSystem::FreeAllFs();
     DiskIOModule::FreeAllModules();
-    Store::FreeMemory();
 
     fdDumpOpen();
 
index debb92655a69717b6efd7e5e936a336b1ad6f130..b6e808279fc5864c3f18b58720f8716d1dc1b2ff 100644 (file)
@@ -360,10 +360,9 @@ StoreEntry::destroyMemObject()
 {
     debugs(20, 3, mem_obj << " in " << *this);
 
-    // Store::Root() is FATALly missing during shutdown
-    if (hasTransients() && !shutting_down)
+    if (hasTransients())
         Store::Root().transientsDisconnect(*this);
-    if (hasMemStore() && !shutting_down)
+    if (hasMemStore())
         Store::Root().memoryDisconnect(*this);
 
     if (auto memObj = mem_obj) {
@@ -380,8 +379,7 @@ destroyStoreEntry(void *data)
     StoreEntry *e = static_cast<StoreEntry *>(static_cast<hash_link *>(data));
     assert(e != nullptr);
 
-    // Store::Root() is FATALly missing during shutdown
-    if (e->hasDisk() && !shutting_down)
+    if (e->hasDisk())
         e->disk().disconnect(*e);
 
     e->destroyMemObject();
@@ -1095,8 +1093,7 @@ StoreEntry::abort()
 void
 storeGetMemSpace(int size)
 {
-    if (!shutting_down) // Store::Root() is FATALly missing during shutdown
-        Store::Root().freeMemorySpace(size);
+    Store::Root().freeMemorySpace(size);
 }
 
 /* thunk through to Store::Root().maintain(). Note that this would be better still
@@ -1254,6 +1251,9 @@ StoreEntry::memoryCachable()
     if (!checkCachable())
         return 0;
 
+    if (shutting_down)
+        return 0; // avoid heavy optional work during shutdown
+
     if (mem_obj == nullptr)
         return 0;
 
@@ -1753,8 +1753,7 @@ StoreEntry::storeWritingCheckpoint()
     }
 
     debugs(20, 7, "done with writing " << *this);
-    if (!shutting_down) // Store::Root() is FATALly missing during shutdown
-        Store::Root().noteStoppedSharedWriting(*this);
+    Store::Root().noteStoppedSharedWriting(*this);
 }
 
 void
index 725da0b91131ea8c0558d8d59bcecb1817f620a4..6428492d82d4937b237c3441a5f147cd52c05def 100644 (file)
@@ -813,7 +813,7 @@ CheckQuickAbortIsReasonable(StoreEntry * entry)
         return false;
     }
 
-    if (!shutting_down && Store::Root().transientReaders(*entry)) {
+    if (Store::Root().transientReaders(*entry)) {
         debugs(90, 3, "quick-abort? NO still have one or more transient readers");
         return false;
     }
@@ -828,6 +828,11 @@ CheckQuickAbortIsReasonable(StoreEntry * entry)
         return false;
     }
 
+    if (shutting_down) {
+        debugs(90, 3, "quick-abort? YES avoid heavy optional work during shutdown");
+        return true;
+    }
+
     MemObject * const mem = entry->mem_obj;
     assert(mem);
     debugs(90, 3, "mem=" << mem);
index 9d4de299b9aeabd3f7a256dc8a8e9672dae2c215..84c3ea6f9e9522e4088c61beb7ea5106ed5fe666 100644 (file)
@@ -160,10 +160,6 @@ doPages(StoreEntry *anEntry)
 void
 StoreEntry::swapOut()
 {
-    // Store::Root() in many swapout checks is FATALly missing during shutdown
-    if (shutting_down)
-        return;
-
     if (!mem_obj)
         return;
 
@@ -363,6 +359,12 @@ StoreEntry::mayStartSwapOut()
         return false;
     }
 
+    if (shutting_down) {
+        debugs(20, 3, "avoid heavy optional work during shutdown");
+        swapOutDecision(MemObject::SwapOut::swImpossible);
+        return false;
+    }
+
     // if there is a usable disk entry already, do not start over
     if (hasDisk() || Store::Root().hasReadableDiskEntry(*this)) {
         debugs(20, 3, "already did"); // we or somebody else created that entry
index 3fff1e930d63d2012608937ea6e73f105fde34af..5780fa6011db8a52c628834527e159273ae90bc3 100644 (file)
@@ -124,8 +124,6 @@ testRock::commonInit()
     if (inited)
         return;
 
-    StoreFileSystem::SetupAllFs();
-
     Config.Store.avgObjectSize = 1024;
     Config.Store.objectsPerBucket = 20;
     Config.Store.maxObjectSize = 2048;