Hello,

    The attached trunk patch fixes bug 3819 in our tests.

Please note that the underlying problem may lead to other assertions,
including "NumberOfUFSDirs" in UFSSwapDir and "fd >= 0" in file_close().

Since trunk r9181.3.1, reconfiguration is done in at least two steps:
First, mainReconfigureStart() closes/cleans/disables all modules
supporting hot reconfiguration and then, at the end of the event loop
iteration, mainReconfigureFinish() opens/configures/enables them again.
UFS code hits assertions if it has to log entries or rebuild swap.state
between those two steps.  The patch disables or delays those activities
during reconfiguration.

The tiny assertion opportunity window hides these bugs from most proxies
that are not doing enough disk I/O or are not being reconfigured
frequently enough.


HTH,

Alex.

Bug 3819: "fd >= 0" assertion in file_write() during reconfiguration

Other possible assertions include "NumberOfUFSDirs" in UFSSwapDir and
"fd >= 0" in file_close().

Since trunk r9181.3.1, reconfiguration is done in at least two steps:
First, mainReconfigureStart() closes/cleans/disables all modules
supporting hot reconfiguration and then, at the end of the event loop
iteration, mainReconfigureFinish() opens/configures/enables them again.
UFS code hits assertions if it has to log entries or rebuild swap.state
between those two steps. The tiny assertion opportunity window hides
these bugs from most proxies that are not doing enough disk I/O or are
not being reconfigured frequently enough.

Asynchronous UFS cache_dirs such as diskd were the most exposed, but
even blocking UFS caching code could probably hit [rebuild] assertions.

The swap.state rebuilding (always initiated at startup) probably did not
work as intended if reconfiguration happened during the rebuild time
because reconfiguration closed the swap.state file being rebuilt. We now
protect that swap.state file and delay rebuilding progress until
reconfiguration is over.

TODO: To squash more similar bugs, we need to move hot reconfiguration
support into the modules so that each module can reconfigure instantly.

=== modified file 'src/fs/ufs/RebuildState.cc'
--- src/fs/ufs/RebuildState.cc	2016-04-03 23:41:58 +0000
+++ src/fs/ufs/RebuildState.cc	2016-09-16 01:17:04 +0000
@@ -74,43 +74,45 @@ Fs::Ufs::RebuildState::RebuildState(RefC
 
     if (!clean)
         flags.need_to_validate = true;
 
     debugs(47, DBG_IMPORTANT, "Rebuilding storage in " << sd->path << " (" <<
            (clean ? "clean log" : (LogParser ? "dirty log" : "no log")) << ")");
 }
 
 Fs::Ufs::RebuildState::~RebuildState()
 {
     sd->closeTmpSwapLog();
 
     if (LogParser)
         delete LogParser;
 }
 
 void
 Fs::Ufs::RebuildState::RebuildStep(void *data)
 {
     RebuildState *rb = (RebuildState *)data;
-    rb->rebuildStep();
+    if (!reconfiguring)
+        rb->rebuildStep();
 
-    if (!rb->isDone())
+    // delay storeRebuildComplete() when reconfiguring to protect storeCleanup()
+    if (!rb->isDone() || reconfiguring)
         eventAdd("storeRebuild", RebuildStep, rb, 0.01, 1);
     else {
         -- StoreController::store_dirs_rebuilding;
         storeRebuildComplete(&rb->counts);
         delete rb;
     }
 }
 
 /// load entries from swap.state or files until we run out of entries or time
 void
 Fs::Ufs::RebuildState::rebuildStep()
 {
     currentEntry(NULL);
 
     // Balance our desire to maximize the number of entries processed at once
     // (and, hence, minimize overheads and total rebuild time) with a
     // requirement to also process Coordinator events, disk I/Os, etc.
     const int maxSpentMsec = 50; // keep small: most RAM I/Os are under 1ms
     const timeval loopStart = current_time;
 

=== modified file 'src/fs/ufs/UFSSwapDir.cc'
--- src/fs/ufs/UFSSwapDir.cc	2016-04-03 23:41:58 +0000
+++ src/fs/ufs/UFSSwapDir.cc	2016-09-16 01:05:19 +0000
@@ -300,41 +300,42 @@ Fs::Ufs::UFSSwapDir::init()
 void
 Fs::Ufs::UFSSwapDir::create()
 {
     debugs(47, 3, "Creating swap space in " << path);
     createDirectory(path, 0);
     createSwapSubDirs();
 }
 
 Fs::Ufs::UFSSwapDir::UFSSwapDir(char const *aType, const char *anIOType) :
     SwapDir(aType),
     IO(NULL),
     fsdata(NULL),
     map(new FileMap()),
     suggest(0),
     l1(16),
     l2(256),
     swaplog_fd(-1),
     currentIOOptions(new ConfigOptionVector()),
     ioType(xstrdup(anIOType)),
     cur_size(0),
-    n_disk_objects(0)
+    n_disk_objects(0),
+    rebuilding_(false)
 {
     /* modulename is only set to disk modules that are built, by configure,
      * so the Find call should never return NULL here.
      */
     IO = new Fs::Ufs::UFSStrategy(DiskIOModule::Find(anIOType)->createStrategy());
 }
 
 Fs::Ufs::UFSSwapDir::~UFSSwapDir()
 {
     if (swaplog_fd > -1) {
         file_close(swaplog_fd);
         swaplog_fd = -1;
     }
     xfree(ioType);
     delete map;
     delete IO;
     delete currentIOOptions;
 }
 
 void
@@ -708,78 +709,81 @@ Fs::Ufs::UFSSwapDir::logFile(char const
 
         if (strncmp(lpath, Config.Log.swap, MAXPATHLEN - 64) == 0) {
             strcat(lpath, ".");
             snprintf(digit, 32, "%02d", index);
             strncat(lpath, digit, 3);
         }
     } else {
         xstrncpy(lpath, path, MAXPATHLEN - 64);
         strcat(lpath, "/swap.state");
     }
 
     if (ext)
         strncat(lpath, ext, 16);
 
     return lpath;
 }
 
 void
 Fs::Ufs::UFSSwapDir::openLog()
 {
+    assert(NumberOfUFSDirs || !UFSDirToGlobalDirMapping);
+    ++NumberOfUFSDirs;
+    assert(NumberOfUFSDirs <= Config.cacheSwap.n_configured);
+
+    if (rebuilding_) { // we did not close the temporary log used for rebuilding
+        assert(swaplog_fd >= 0);
+        return;
+    }
+
     char *logPath;
     logPath = logFile();
     swaplog_fd = file_open(logPath, O_WRONLY | O_CREAT | O_BINARY);
 
     if (swaplog_fd < 0) {
         int xerrno = errno;
         debugs(50, DBG_IMPORTANT, "ERROR opening swap log " << logPath << ": " << xstrerr(xerrno));
         fatal("UFSSwapDir::openLog: Failed to open swap log.");
     }
 
     debugs(50, 3, HERE << "Cache Dir #" << index << " log opened on FD " << swaplog_fd);
-
-    if (0 == NumberOfUFSDirs)
-        assert(NULL == UFSDirToGlobalDirMapping);
-
-    ++NumberOfUFSDirs;
-
-    assert(NumberOfUFSDirs <= Config.cacheSwap.n_configured);
 }
 
 void
 Fs::Ufs::UFSSwapDir::closeLog()
 {
     if (swaplog_fd < 0) /* not open */
         return;
 
+    --NumberOfUFSDirs;
+    assert(NumberOfUFSDirs >= 0);
+    if (!NumberOfUFSDirs)
+        safe_free(UFSDirToGlobalDirMapping);
+
+    if (rebuilding_) // we cannot close the temporary log used for rebuilding
+        return;
+
     file_close(swaplog_fd);
 
     debugs(47, 3, "Cache Dir #" << index << " log closed on FD " << swaplog_fd);
 
     swaplog_fd = -1;
-
-    --NumberOfUFSDirs;
-
-    assert(NumberOfUFSDirs >= 0);
-
-    if (0 == NumberOfUFSDirs)
-        safe_free(UFSDirToGlobalDirMapping);
 }
 
 bool
 Fs::Ufs::UFSSwapDir::validL1(int anInt) const
 {
     return anInt < l1;
 }
 
 bool
 Fs::Ufs::UFSSwapDir::validL2(int anInt) const
 {
     return anInt < l2;
 }
 
 StoreEntry *
 Fs::Ufs::UFSSwapDir::addDiskRestore(const cache_key * key,
                                     sfileno file_number,
                                     uint64_t swap_file_sz,
                                     time_t expires,
                                     time_t timestamp,
@@ -825,100 +829,106 @@ Fs::Ufs::UFSSwapDir::undoAddDiskRestore(
     debugs(47, 5, HERE << *e);
     replacementRemove(e); // checks swap_dirn so do it before we invalidate it
     // Do not unlink the file as it might be used by a subsequent entry.
     mapBitReset(e->swap_filen);
     e->swap_filen = -1;
     e->swap_dirn = -1;
     cur_size -= fs.blksize * sizeInBlocks(e->swap_file_sz);
     --n_disk_objects;
 }
 
 void
 Fs::Ufs::UFSSwapDir::rebuild()
 {
     ++StoreController::store_dirs_rebuilding;
     eventAdd("storeRebuild", Fs::Ufs::RebuildState::RebuildStep, new Fs::Ufs::RebuildState(this), 0.0, 1);
 }
 
 void
 Fs::Ufs::UFSSwapDir::closeTmpSwapLog()
 {
+    assert(rebuilding_);
+    rebuilding_ = false;
+
     char *swaplog_path = xstrdup(logFile(NULL)); // where the swaplog should be
     char *tmp_path = xstrdup(logFile(".new")); // the temporary file we have generated
     int fd;
     file_close(swaplog_fd);
 
     if (xrename(tmp_path, swaplog_path) < 0) {
         fatalf("Failed to rename log file %s to %s", tmp_path, swaplog_path);
     }
 
     fd = file_open(swaplog_path, O_WRONLY | O_CREAT | O_BINARY);
 
     if (fd < 0) {
         int xerrno = errno;
         debugs(50, DBG_IMPORTANT, "ERROR: " << swaplog_path << ": " << xstrerr(xerrno));
         fatalf("Failed to open swap log %s", swaplog_path);
     }
 
     xfree(swaplog_path);
     xfree(tmp_path);
     swaplog_fd = fd;
     debugs(47, 3, "Cache Dir #" << index << " log opened on FD " << fd);
 }
 
 FILE *
 Fs::Ufs::UFSSwapDir::openTmpSwapLog(int *clean_flag, int *zero_flag)
 {
+    assert(!rebuilding_);
+
     char *swaplog_path = xstrdup(logFile(NULL));
     char *clean_path = xstrdup(logFile(".last-clean"));
     char *new_path = xstrdup(logFile(".new"));
 
     struct stat log_sb;
 
     struct stat clean_sb;
     FILE *fp;
     int fd;
 
     if (::stat(swaplog_path, &log_sb) < 0) {
         debugs(47, DBG_IMPORTANT, "Cache Dir #" << index << ": No log file");
         safe_free(swaplog_path);
         safe_free(clean_path);
         safe_free(new_path);
         return NULL;
     }
 
     *zero_flag = log_sb.st_size == 0 ? 1 : 0;
     /* close the existing write-only FD */
 
     if (swaplog_fd >= 0)
         file_close(swaplog_fd);
 
     /* open a write-only FD for the new log */
     fd = file_open(new_path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
 
     if (fd < 0) {
         int xerrno = errno;
         debugs(50, DBG_IMPORTANT, "ERROR: while opening swap log" << new_path << ": " << xstrerr(xerrno));
         fatalf("Failed to open swap log %s", new_path);
     }
 
     swaplog_fd = fd;
+    rebuilding_ = true;
 
     {
         const StoreSwapLogHeader header;
         MemBuf buf;
         buf.init(header.record_size, header.record_size);
         buf.append(reinterpret_cast<const char*>(&header), sizeof(header));
         // Pad to keep in sync with UFSSwapDir::writeCleanStart().
         memset(buf.space(), 0, header.gapSize());
         buf.appended(header.gapSize());
         file_write(swaplog_fd, -1, buf.content(), buf.contentSize(),
                    NULL, NULL, buf.freeFunc());
     }
 
     /* open a read-only stream of the old log */
     fp = fopen(swaplog_path, "rb");
 
     if (!fp) {
         int xerrno = errno;
         debugs(50, DBG_CRITICAL, "ERROR: while opening " << swaplog_path << ": " << xstrerr(xerrno));
         fatalf("Failed to open swap log for reading %s", swaplog_path);
@@ -1043,52 +1053,51 @@ Fs::Ufs::UFSSwapDir::writeCleanDone()
     else
         file_close(file_open(state->cln, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY));
 
     /* close */
     safe_free(state->cur);
 
     safe_free(state->newLog);
 
     safe_free(state->cln);
 
     if (state->fd >= 0)
         file_close(state->fd);
 
     state->fd = -1;
 
     delete state;
 
     cleanLog = NULL;
 }
 
-void
-Fs::Ufs::UFSSwapDir::CleanEvent(void *)
+/// safely cleans a few unused files if possible
+int
+Fs::Ufs::UFSSwapDir::HandleCleanEvent()
 {
     static int swap_index = 0;
     int i;
     int j = 0;
     int n = 0;
-    /*
-     * Assert that there are UFS cache_dirs configured, otherwise
-     * we should never be called.
-     */
-    assert(NumberOfUFSDirs);
+
+    if (!NumberOfUFSDirs)
+        return 0; // probably in the middle of reconfiguration
 
     if (NULL == UFSDirToGlobalDirMapping) {
         SwapDir *sd;
         /*
          * Initialize the little array that translates UFS cache_dir
          * number into the Config.cacheSwap.swapDirs array index.
          */
         UFSDirToGlobalDirMapping = (int *)xcalloc(NumberOfUFSDirs, sizeof(*UFSDirToGlobalDirMapping));
 
         for (i = 0, n = 0; i < Config.cacheSwap.n_configured; ++i) {
             /* This is bogus, the controller should just clean each instance once */
             sd = dynamic_cast <SwapDir *>(INDEXSD(i));
 
             if (!UFSSwapDir::IsUFSDir(sd))
                 continue;
 
             UFSSwapDir *usd = dynamic_cast<UFSSwapDir *>(sd);
 
             assert (usd);
 
@@ -1098,40 +1107,47 @@ Fs::Ufs::UFSSwapDir::CleanEvent(void *)
             j += (usd->l1 * usd->l2);
         }
 
         assert(n == NumberOfUFSDirs);
         /*
          * Start the commonUfsDirClean() swap_index with a random
          * value.  j equals the total number of UFS level 2
          * swap directories
          */
         std::mt19937 mt(static_cast<uint32_t>(getCurrentTime() & 0xFFFFFFFF));
         xuniform_int_distribution<> dist(0, j);
         swap_index = dist(mt);
     }
 
     /* if the rebuild is finished, start cleaning directories. */
     if (0 == StoreController::store_dirs_rebuilding) {
         n = DirClean(swap_index);
         ++swap_index;
     }
 
+    return n;
+}
+
+void
+Fs::Ufs::UFSSwapDir::CleanEvent(void *)
+{
+    const int n = HandleCleanEvent();
     eventAdd("storeDirClean", CleanEvent, NULL,
              15.0 * exp(-0.25 * n), 1);
 }
 
 bool
 Fs::Ufs::UFSSwapDir::IsUFSDir(SwapDir * sd)
 {
     UFSSwapDir *mySD = dynamic_cast<UFSSwapDir *>(sd);
     return (mySD != 0) ;
 }
 
 /*
  * XXX: this is broken - it assumes all cache dirs use the same
  * l1 and l2 scheme. -RBC 20021215. Partial fix is in place -
  * if not UFSSwapDir return 0;
  */
 bool
 Fs::Ufs::UFSSwapDir::FilenoBelongsHere(int fn, int F0, int F1, int F2)
 {
     int D1, D2;
@@ -1267,40 +1283,45 @@ Fs::Ufs::UFSSwapDir::callback()
 {
     return IO->callback();
 }
 
 void
 Fs::Ufs::UFSSwapDir::sync()
 {
     IO->sync();
 }
 
 void
 Fs::Ufs::UFSSwapDir::swappedOut(const StoreEntry &e)
 {
     cur_size += fs.blksize * sizeInBlocks(e.swap_file_sz);
     ++n_disk_objects;
 }
 
 void
 Fs::Ufs::UFSSwapDir::logEntry(const StoreEntry & e, int op) const
 {
+    if (swaplog_fd < 0) {
+        debugs(36, 5, "cannot log " << e << " in the middle of reconfiguration");
+        return;
+    }
+
     StoreSwapLogData *s = new StoreSwapLogData;
     s->op = (char) op;
     s->swap_filen = e.swap_filen;
     s->timestamp = e.timestamp;
     s->lastref = e.lastref;
     s->expires = e.expires;
     s->lastmod = e.lastmod;
     s->swap_file_sz = e.swap_file_sz;
     s->refcount = e.refcount;
     s->flags = e.flags;
     memcpy(s->key, e.key, SQUID_MD5_DIGEST_LENGTH);
     s->finalize();
     file_write(swaplog_fd,
                -1,
                s,
                sizeof(StoreSwapLogData),
                NULL,
                NULL,
                FreeObject);
 }

=== modified file 'src/fs/ufs/UFSSwapDir.h'
--- src/fs/ufs/UFSSwapDir.h	2016-07-23 13:36:56 +0000
+++ src/fs/ufs/UFSSwapDir.h	2016-09-16 01:05:19 +0000
@@ -109,45 +109,47 @@ public:
     bool validL2(int) const;
     bool validL1(int) const;
 
     /** Add and remove the given StoreEntry from the replacement policy in use */
     void replacementAdd(StoreEntry *e);
     void replacementRemove(StoreEntry *e);
 
 protected:
     FileMap *map;
     int suggest;
     int l1;
     int l2;
 
 private:
     void parseSizeL1L2();
     static int NumberOfUFSDirs;
     static int * UFSDirToGlobalDirMapping;
     bool pathIsDirectory(const char *path)const;
     int swaplog_fd;
     static EVH CleanEvent;
+    static int HandleCleanEvent();
     /** Verify that the the CacheDir exists
      *
      * If this returns < 0, then Squid exits, complains about swap
      * directories not existing, and instructs the admin to run 'squid -z'
      * Called by UFSSwapDir::init
      */
     bool verifyCacheDirs();
     void rebuild();
     int createDirectory(const char *path, int);
     void createSwapSubDirs();
     void dumpEntry(StoreEntry &) const;
     char *logFile(char const *ext = NULL)const;
     void changeIO(DiskIOModule *);
     bool optionIOParse(char const *option, const char *value, int reconfiguring);
     void optionIODump(StoreEntry * e) const;
     mutable ConfigOptionVector *currentIOOptions;
     char const *ioType;
     uint64_t cur_size; ///< currently used space in the storage area
     uint64_t n_disk_objects; ///< total number of objects stored
+    bool rebuilding_; ///< whether RebuildState is writing the new swap.state
 };
 
 } //namespace Ufs
 } //namespace Fs
 #endif /* SQUID_FS_UFS_UFSSWAPDIR_H */
 

=== modified file 'src/store/Disks.cc'
--- src/store/Disks.cc	2016-07-23 13:36:56 +0000
+++ src/store/Disks.cc	2016-09-16 01:05:19 +0000
@@ -565,41 +565,41 @@ storeDirCloseSwapLogs()
  *  Writes a "clean" swap log file from in-memory metadata.
  *  This is a rewrite of the original function to troll each
  *  StoreDir and write the logs, and flush at the end of
  *  the run. Thanks goes to Eric Stern, since this solution
  *  came out of his COSS code.
  */
 int
 storeDirWriteCleanLogs(int reopen)
 {
     const StoreEntry *e = NULL;
     int n = 0;
 
     struct timeval start;
     double dt;
     RefCount<SwapDir> sd;
     int dirn;
     int notdone = 1;
 
     // Check for store_dirs_rebuilding because fatal() often calls us in early
     // initialization phases, before store log is initialized and ready. Also,
-    // some stores probably do not support log cleanup during Store rebuilding.
+    // some stores do not support log cleanup during Store rebuilding.
     if (StoreController::store_dirs_rebuilding) {
         debugs(20, DBG_IMPORTANT, "Not currently OK to rewrite swap log.");
         debugs(20, DBG_IMPORTANT, "storeDirWriteCleanLogs: Operation aborted.");
         return 0;
     }
 
     debugs(20, DBG_IMPORTANT, "storeDirWriteCleanLogs: Starting...");
     getCurrentTime();
     start = current_time;
 
     for (dirn = 0; dirn < Config.cacheSwap.n_configured; ++dirn) {
         sd = dynamic_cast<SwapDir *>(INDEXSD(dirn));
 
         if (sd->writeCleanStart() < 0) {
             debugs(20, DBG_IMPORTANT, "log.clean.start() failed for dir #" << sd->index);
             continue;
         }
     }
 
     /*

_______________________________________________
squid-dev mailing list
squid-dev@lists.squid-cache.org
http://lists.squid-cache.org/listinfo/squid-dev

Reply via email to