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

UBSAN in X is outside the range of representable values of type 'unsigned long' | page_cleaner_flush_pages_recommendation

Details

    Description

      --source include/have_innodb.inc 
       
      CREATE OR REPLACE TABLE t (a INT) ENGINE=INNODB;
      SELECT @@innodb_io_capacity;
      SELECt @@innodb_io_capacity_max;
      SET GLOBAL innodb_io_capacity=18446744073709551615;
      SET GLOBAL innodb_max_dirty_pages_pct=1;
      CREATE OR REPLACE TABLE t (a INT) ENGINE=INNODB;
      CREATE OR REPLACE TABLE t (a INT) ENGINE=INNODB;
      CREATE OR REPLACE TABLE t (a INT) ENGINE=INNODB;
      CREATE OR REPLACE TABLE t (a INT) ENGINE=INNODB;
      CREATE OR REPLACE TABLE t (a INT) ENGINE=INNODB;
      CREATE OR REPLACE TABLE t (a INT) ENGINE=INNODB;
      

      Leads to:

      CS 11.8.1 6f1161aa34cbb178b00fc24cbc46e2e0e2af767a (Optimized, UBASAN, Clang) Build 24/02/2025

      /test/11.8_opt_san/storage/innobase/buf/buf0flu.cc:2302:19: runtime error: 1.85291e+19 is outside the range of representable values of type 'unsigned long'
          #0 0x557d0b95b4b3 in page_cleaner_flush_pages_recommendation(unsigned long, unsigned long, double, unsigned long, double) /test/11.8_opt_san/storage/innobase/buf/buf0flu.cc:2302:19
          #1 0x557d0b95b4b3 in buf_flush_page_cleaner() /test/11.8_opt_san/storage/innobase/buf/buf0flu.cc:2619:18
          #2 0x14ffc48ecdb3 in execute_native_thread_routine /build/gcc-14-ig5ci0/gcc-14-14.2.0/build/x86_64-linux-gnu/libstdc++-v3/src/c++11/../../../../../src/libstdc++-v3/src/c++11/thread.cc:104:18
          #3 0x557d0930695c in asan_thread_start(void*) asan_interceptors.cpp.o
          #4 0x14ffc449caa3 in start_thread nptl/pthread_create.c:447:8
          #5 0x14ffc4529c3b in clone3 misc/../sysdeps/unix/sysv/linux/x86_64/clone3.S:78
       
      SUMMARY: UndefinedBehaviorSanitizer: float-cast-overflow /test/11.8_opt_san/storage/innobase/buf/buf0flu.cc:2302:19 
      

      Setup:

      Compiled with a recent version of Clang (I used Clang 18.1.3) with LLVM 18. Ubuntu instructions:
        # Note: It is strongly recommended to uninstall all old Clang & LLVM packages (ref  dpkg --list | grep -iE 'clang|llvm'  and use  apt purge  and  dpkg --purge  to remove the packages), before following these steps
           # Note: llvm-17-linker-tools installs /usr/lib/llvm-17/lib/LLVMgold.so, which is needed for compilation, and LLVMgold.so is no longer included in LLVM 18
           sudo apt install clang llvm-18 llvm-18-linker-tools llvm-18-runtime llvm-18-tools llvm-18-dev libstdc++-14-dev llvm-dev llvm-17-linker-tools
           sudo ln -s /usr/lib/llvm-17/lib/LLVMgold.so /usr/lib/llvm-18/lib/LLVMgold.so
      Compiled with: "-DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DCMAKE_C{,XX}_FLAGS='-march=native -mtune=native'" and:
          -DWITH_ASAN=ON -DWITH_ASAN_SCOPE=ON -DWITH_UBSAN=ON -DWSREP_LIB_WITH_ASAN=ON
      Set before execution:
          export UBSAN_OPTIONS=print_stacktrace=1:report_error_type=1   # And you may also want to supress UBSAN startup issues using 'suppressions=UBSAN.filter' in UBSAN_OPTIONS. For an example of UBSAN.filter, which includes current startup issues see: https://github.com/mariadb-corporation/mariadb-qa/blob/master/UBSAN.filter
      

      Bug confirmed present in:
      MariaDB: 11.4.6 (opt), 11.8.1 (opt), 12.0.0 (opt)

      Bug (or feature/syntax) confirmed not present in:
      MariaDB: 10.11.12 (dbg), 10.11.12 (opt), 12.0.0 (dbg)

      Attachments

        Issue Links

          Activity

            I don’t see why this would only affect 11.4 and later versions. The logic has not been changed since MariaDB Server 10.6.

            GDB won’t cooperate when a floating-point value is stored in a SIMD register, but I could imagine that the value of dirty_pct could be as follows in another debugging session where I repeated this:

            (gdb) p buf_pool.LRU.count
            $1 = 343
            (gdb) p buf_pool.free.count
            $2 = 7721
            (gdb) p buf_pool.flush_list.count
            $3 = 81
            (gdb) p 81*100.0/(343+7721)
            $4 = 1.0044642857142858
            

            In page_cleaner_flush_pages_recommendation() there is some logic that looks questionable:

            	const double max_pct = srv_max_buf_pool_modified_pct;
             
            	if (!prev_lsn || !pct_for_lsn) {
            		prev_time = curr_time;
            		prev_lsn = cur_lsn;
            		if (max_pct > 0.0) {
            			dirty_pct /= max_pct;
            		}
             
            		n_pages = ulint(dirty_pct * double(srv_io_capacity));
            

            We have max_pct=1, and dividing by 1 is a no-op. So, it looks like we could try to multiply ~0ULL by something larger than 1, and clearly the result would not fit in 64 bits. In the Description, we seem to have had dirty_pct=1.00446, which is close to what I observed in this debugging session.

            I think that we should not only fix the limits of dirty_pct, but also change the type of innodb_io_capacity and related parameters to be INT UNSIGNED (always 32 bits). With the default innodb_page_size=16k and innodb_doublewrite=ON, the maximum write rate that can be represented by a 32-bit unsigned integer would be 2^(32+14+1) = 2⁴⁷ bytes per second, or 128 TiB/s. It will take a while before we reach such write speeds. I believe that a 16-bit innodb_io_capacity would be insufficient already today: 2^(16+14+1) = 2 GiB/s is on the slow side even for consumer-grade NVMe SSDs.

            marko Marko Mäkelä added a comment - I don’t see why this would only affect 11.4 and later versions. The logic has not been changed since MariaDB Server 10.6. GDB won’t cooperate when a floating-point value is stored in a SIMD register, but I could imagine that the value of dirty_pct could be as follows in another debugging session where I repeated this: (gdb) p buf_pool.LRU.count $1 = 343 (gdb) p buf_pool.free.count $2 = 7721 (gdb) p buf_pool.flush_list.count $3 = 81 (gdb) p 81*100.0/(343+7721) $4 = 1.0044642857142858 In page_cleaner_flush_pages_recommendation() there is some logic that looks questionable: const double max_pct = srv_max_buf_pool_modified_pct;   if (!prev_lsn || !pct_for_lsn) { prev_time = curr_time; prev_lsn = cur_lsn; if (max_pct > 0.0) { dirty_pct /= max_pct; }   n_pages = ulint(dirty_pct * double (srv_io_capacity)); We have max_pct=1 , and dividing by 1 is a no-op. So, it looks like we could try to multiply ~0ULL by something larger than 1, and clearly the result would not fit in 64 bits. In the Description, we seem to have had dirty_pct=1.00446 , which is close to what I observed in this debugging session. I think that we should not only fix the limits of dirty_pct , but also change the type of innodb_io_capacity and related parameters to be INT UNSIGNED (always 32 bits). With the default innodb_page_size=16k and innodb_doublewrite=ON , the maximum write rate that can be represented by a 32-bit unsigned integer would be 2^(32+14+1) = 2⁴⁷ bytes per second, or 128 TiB/s. It will take a while before we reach such write speeds. I believe that a 16-bit innodb_io_capacity would be insufficient already today: 2^(16+14+1) = 2 GiB/s is on the slow side even for consumer-grade NVMe SSDs.

            I could repeat the issue with 10.11 using the test scenario described.

            2313│                 if (max_pct > 0.0) {
            2314│                         dirty_pct /= max_pct;
            2315│                 }
            2316│
            2317│                 n_pages = ulint(dirty_pct * double(srv_io_capacity));
            2318│                 if (n_pages < dirty_blocks) {
            2319│                         n_pages= std::min<ulint>(srv_io_capacity, dirty_blocks);
            2320│                 }
            

            (gdb) p srv_io_capacity
            $10 = 18446744073709551615
            (gdb) p dirty_pct
            $11 = 1.0168650793650793
            (gdb) p dirty_pct * srv_io_capacity
            $13 = 1.875784987653997e+19
            

            After the assignment

            (gdb) p n_pages
            $12 = 0
            

            Now, it should be fine to go beyond innodb_io_capacity sometimes during calculation and specifically MDEV-24369 has introduced a logic for aggressive flushing when dirty page percentage in buffer pool
            exceeds innodb_max_dirty_pages_pct. Based on this, we could set our target in multiples of innodb_io_capacity once we go beyond the dirty page threshold.

            1. I agree with marko that we should prevent setting io_capacity to unrealistic values and define a practical limit to it. Pull-3857 introduces limits for innodb_io_capacity_max and innodb_io_capacity to the maximum of 4 byte unsigned integer i.e. 4294967295 (2^32-1). For 16k page size this limit translates to 64 TiB/sec write IO speed which looks sufficient.

            IMHO, the above patch should be sufficient to address the current issue. Hi ramesh, may I request you to test the patch and confirm that it addresses the issue adequately ?

            This would also require some documentation change to update the max limit for 2 innodb configurations. It is not expected to affect any user as the limits are beyond any practical value.

            https://mariadb.com/kb/en/innodb-system-variables/#innodb_io_capacity
            Range: 100 to 4294967295 (2^32-1)
            https://mariadb.com/kb/en/innodb-system-variables/#innodb_io_capacity_max
            Range: 100 to 4294967295 (2^32-1)
            

            2. marko While it is possible that we could improve page_cleaner_flush_pages_recommendation w.r.t. how aggressively the flush target is set, it should only be done based on good amount of performance / IO testing. Changing anything here would impact the current flushing behaviour. Since MDEV-24369 is there from 10.5.9, it is probably best to attempt such changes as improvement work /project in latest to avoid impact on GA versions. It looks out of scope for the current bug-fix.

            debarun Debarun Banerjee added a comment - I could repeat the issue with 10.11 using the test scenario described. 2313│ if (max_pct > 0.0) { 2314│ dirty_pct /= max_pct; 2315│ } 2316│ 2317│ n_pages = ulint(dirty_pct * double(srv_io_capacity)); 2318│ if (n_pages < dirty_blocks) { 2319│ n_pages= std::min<ulint>(srv_io_capacity, dirty_blocks); 2320│ } (gdb) p srv_io_capacity $10 = 18446744073709551615 (gdb) p dirty_pct $11 = 1.0168650793650793 (gdb) p dirty_pct * srv_io_capacity $13 = 1.875784987653997e+19 After the assignment (gdb) p n_pages $12 = 0 Now, it should be fine to go beyond innodb_io_capacity sometimes during calculation and specifically MDEV-24369 has introduced a logic for aggressive flushing when dirty page percentage in buffer pool exceeds innodb_max_dirty_pages_pct. Based on this, we could set our target in multiples of innodb_io_capacity once we go beyond the dirty page threshold. 1. I agree with marko that we should prevent setting io_capacity to unrealistic values and define a practical limit to it. Pull-3857 introduces limits for innodb_io_capacity_max and innodb_io_capacity to the maximum of 4 byte unsigned integer i.e. 4294967295 (2^32-1). For 16k page size this limit translates to 64 TiB/sec write IO speed which looks sufficient. IMHO, the above patch should be sufficient to address the current issue. Hi ramesh , may I request you to test the patch and confirm that it addresses the issue adequately ? This would also require some documentation change to update the max limit for 2 innodb configurations. It is not expected to affect any user as the limits are beyond any practical value. https://mariadb.com/kb/en/innodb-system-variables/#innodb_io_capacity Range: 100 to 4294967295 (2^32-1) https://mariadb.com/kb/en/innodb-system-variables/#innodb_io_capacity_max Range: 100 to 4294967295 (2^32-1) 2. marko While it is possible that we could improve page_cleaner_flush_pages_recommendation w.r.t. how aggressively the flush target is set, it should only be done based on good amount of performance / IO testing. Changing anything here would impact the current flushing behaviour. Since MDEV-24369 is there from 10.5.9, it is probably best to attempt such changes as improvement work /project in latest to avoid impact on GA versions. It looks out of scope for the current bug-fix.

            debarun Test case passes without any issues in the bug fix branch, also initiated new test run to verify the patch. No issues have been found in the test run so far. I will let you know if the test run hit any issues related to this.

            ramesh Ramesh Sivaraman added a comment - debarun Test case passes without any issues in the bug fix branch, also initiated new test run to verify the patch. No issues have been found in the test run so far. I will let you know if the test run hit any issues related to this.

            The proposed fix addresses clearly the problem on 64-bit systems. On 32-bit targets, which we still support, we would seem to need something conceptually similar to the C++26 std::saturate_cast.

            marko Marko Mäkelä added a comment - The proposed fix addresses clearly the problem on 64-bit systems. On 32-bit targets, which we still support, we would seem to need something conceptually similar to the C++26 std::saturate_cast .

            This MDEV requires documentation change as it restricts the maximum value of two Innodb configuration variables. It is not expected to affect any user as the limits are beyond any practical value.

            https://mariadb.com/kb/en/innodb-system-variables/#innodb_io_capacity

            Range: 100 to 4294967295 (2^32-1)
            

            https://mariadb.com/kb/en/innodb-system-variables/#innodb_io_capacity_max

            Range: 100 to 4294967295 (2^32-1)
            

            debarun Debarun Banerjee added a comment - This MDEV requires documentation change as it restricts the maximum value of two Innodb configuration variables. It is not expected to affect any user as the limits are beyond any practical value. https://mariadb.com/kb/en/innodb-system-variables/#innodb_io_capacity Range: 100 to 4294967295 (2^32-1) https://mariadb.com/kb/en/innodb-system-variables/#innodb_io_capacity_max Range: 100 to 4294967295 (2^32-1)

            People

              debarun Debarun Banerjee
              ramesh Ramesh Sivaraman
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Git Integration

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