Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-41036

buf_page_peek_if_young() does not properly compute and wrap page age in young calculation

    XMLWordPrintable

Details

    Description

      buf_page_peek_if_young() does not properly compute and wrap the age of the page, which causes the function to determine some pages being "young" when in fact (considering the 31-bits accuracy of the page clock limit absolute correctness) are old.

      The code of the function has this reported has FIXME:

      /** Determine if a block is still close enough to the MRU end of the LRU list
      meaning that it is not in danger of getting evicted and also implying
      that it has been accessed recently.
      The page must be either buffer-fixed, or its page hash must be locked.
      @param[in]	bpage		buffer pool page
      @return whether bpage is close to MRU end of LRU */
      inline bool buf_page_peek_if_young(const buf_page_t *bpage)
      {
      	/* FIXME: bpage->freed_page_clock is 31 bits */
      	return((buf_pool.freed_page_clock & ((1UL << 31) - 1))
      	       < (bpage->freed_page_clock
      		  + (buf_pool.curr_size()
      		     * (BUF_LRU_OLD_RATIO_DIV - buf_pool.LRU_old_ratio)
      		     / (BUF_LRU_OLD_RATIO_DIV * 4))));
      }
      

      I wrote a test program to simulate error conditions for the current version and a plausible fix:

      #include <array>
      #include <cstdio>
      #include <cstdint>
       
      /*
       * inline bool buf_page_peek_if_young(const buf_page_t *bpage)
       * {
       *      return((buf_pool.freed_page_clock & ((1UL << 31) - 1))
       *             < (bpage->freed_page_clock
       *                + (buf_pool.curr_size()
       * (BUF_LRU_OLD_RATIO_DIV - buf_pool.LRU_old_ratio)
       *                   / (BUF_LRU_OLD_RATIO_DIV * 4))));
       * }
       */
       
      constexpr uint32_t CLOCK_MASK= ((1UL << 31) - 1);
      static_assert(CLOCK_MASK == 0x7FFFFFFF);
       
      inline bool f_current_young(const uint32_t bp_fpc, const uint32_t stamped, const size_t window)
      {
        const uint32_t now= bp_fpc & CLOCK_MASK;
        static_assert(sizeof((stamped + window)) == sizeof(size_t)); /* promotion */
        return now < (stamped + window);
      }
       
      inline bool f_new_young(const uint32_t bp_fpc, const uint32_t stamped, const size_t window)
      {
        const uint32_t now= bp_fpc & CLOCK_MASK;
        const uint32_t age= (now - stamped) & CLOCK_MASK;
        return age < window;
      }
       
      int main()
      {
        constexpr size_t window= 10000;
        const std::array<uint32_t, 7> stampeds= {0, 1, window - 1, window, window + 1, CLOCK_MASK / 2, CLOCK_MASK - 1};
       
        for (const uint32_t stamped : stampeds)
        {
          printf("stamped=%u (0x%08X)\n", stamped, stamped);
          bool disagree= false;
          uint64_t disagree_count= 0;
       
          for (uint32_t bp_fpc= 0; ; ++bp_fpc)
          {
            const bool current_young= f_current_young(bp_fpc, stamped, window);
            const bool new_young= f_new_young(bp_fpc, stamped, window);
            if (current_young != new_young)
            {
              ++disagree_count;
              if (!disagree)
              {
                disagree= true;
                printf("Disagree started at bp_fpc=%u (0x%08X), stamped=%u (0x%08X), window=%zu: current_young=%d != new_young=%d\n",
                       bp_fpc, bp_fpc, stamped, stamped, window, current_young, new_young);
              }
            }
            else if (current_young == new_young && disagree)
            {
              disagree= false;
              printf("Disagree stopped at bp_fpc=%u (0x%08X), stamped=%u (0x%08X), window=%zu: current_young=%d == new_young=%d\n",
                     bp_fpc, bp_fpc, stamped, stamped, window, current_young, new_young);
            }
       
            if (bp_fpc == 0xFFFFFFFF)
            {
              if (disagree)
              {
                printf("Disagreed up to the end\n");
              }
              break;
            }
          }
          printf("stamped=%u (0x%08X), disagree_count=%lu\n\n", stamped, stamped, disagree_count);
        }
       
        return 0;
      }
      

      The output is (considering the window size at 10000):

      stamped=0 (0x00000000)
      stamped=0 (0x00000000), disagree_count=0
       
      stamped=1 (0x00000001)
      Disagree started at bp_fpc=0 (0x00000000), stamped=1 (0x00000001), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=1 (0x00000001), stamped=1 (0x00000001), window=10000: current_young=1 == new_young=1
      Disagree started at bp_fpc=2147483648 (0x80000000), stamped=1 (0x00000001), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=2147483649 (0x80000001), stamped=1 (0x00000001), window=10000: current_young=1 == new_young=1
      stamped=1 (0x00000001), disagree_count=2
       
      stamped=9999 (0x0000270F)
      Disagree started at bp_fpc=0 (0x00000000), stamped=9999 (0x0000270F), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=9999 (0x0000270F), stamped=9999 (0x0000270F), window=10000: current_young=1 == new_young=1
      Disagree started at bp_fpc=2147483648 (0x80000000), stamped=9999 (0x0000270F), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=2147493647 (0x8000270F), stamped=9999 (0x0000270F), window=10000: current_young=1 == new_young=1
      stamped=9999 (0x0000270F), disagree_count=19998
       
      stamped=10000 (0x00002710)
      Disagree started at bp_fpc=0 (0x00000000), stamped=10000 (0x00002710), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=10000 (0x00002710), stamped=10000 (0x00002710), window=10000: current_young=1 == new_young=1
      Disagree started at bp_fpc=2147483648 (0x80000000), stamped=10000 (0x00002710), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=2147493648 (0x80002710), stamped=10000 (0x00002710), window=10000: current_young=1 == new_young=1
      stamped=10000 (0x00002710), disagree_count=20000
       
      stamped=10001 (0x00002711)
      Disagree started at bp_fpc=0 (0x00000000), stamped=10001 (0x00002711), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=10001 (0x00002711), stamped=10001 (0x00002711), window=10000: current_young=1 == new_young=1
      Disagree started at bp_fpc=2147483648 (0x80000000), stamped=10001 (0x00002711), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=2147493649 (0x80002711), stamped=10001 (0x00002711), window=10000: current_young=1 == new_young=1
      stamped=10001 (0x00002711), disagree_count=20002
       
      stamped=1073741823 (0x3FFFFFFF)
      Disagree started at bp_fpc=0 (0x00000000), stamped=1073741823 (0x3FFFFFFF), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=1073741823 (0x3FFFFFFF), stamped=1073741823 (0x3FFFFFFF), window=10000: current_young=1 == new_young=1
      Disagree started at bp_fpc=2147483648 (0x80000000), stamped=1073741823 (0x3FFFFFFF), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=3221225471 (0xBFFFFFFF), stamped=1073741823 (0x3FFFFFFF), window=10000: current_young=1 == new_young=1
      stamped=1073741823 (0x3FFFFFFF), disagree_count=2147483646
       
      stamped=2147483646 (0x7FFFFFFE)
      Disagree started at bp_fpc=9998 (0x0000270E), stamped=2147483646 (0x7FFFFFFE), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=2147483646 (0x7FFFFFFE), stamped=2147483646 (0x7FFFFFFE), window=10000: current_young=1 == new_young=1
      Disagree started at bp_fpc=2147493646 (0x8000270E), stamped=2147483646 (0x7FFFFFFE), window=10000: current_young=1 != new_young=0
      Disagree stopped at bp_fpc=4294967294 (0xFFFFFFFE), stamped=2147483646 (0x7FFFFFFE), window=10000: current_young=1 == new_young=1
      stamped=2147483646 (0x7FFFFFFE), disagree_count=4294947296
      

      We can see that the error is always current function telling "young" while the new one does not.
      Sample error condition (discovered while investigating MDEV-40050, comment here):

      stamped = 2**31 - 10
      now (stamped + 2010, wrapped, masked to 31 bits) = 2000
      window = 1000
      true age = 2010 -> truly old
       
      would make the current code tell:
       
      2000 < (2**31 - 10) + 1000 => 2000 < some big number => true => young (wrong!)
       
      fixed code:
       
      (2000 - (2**31 - 10)) & CLOCK_MASK < 1000 => (2147485658 & CLOCK_MASK) < 1000 => 2010 < 1000 => false => old (correct)
      

      Mistakenly marking pages as "young" can make hot pages slip into the old portion of the list.

      Attachments

        Issue Links

          Activity

            People

              marko Marko Mäkelä
              alessandro.vetere Alessandro Vetere
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

                Created:
                Updated:

                Time Tracking

                  Estimated:
                  Original Estimate - 1d
                  1d
                  Remaining:
                  Time Spent - 1h Remaining Estimate - 7h
                  7h
                  Logged:
                  Time Spent - 1h Remaining Estimate - 7h
                  1h

                  Git Integration

                    Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.