Bug #96893 TempTable SE out of memory by SHOW VARIABLES
Submitted: 17 Sep 2019 3:40 Modified: 29 Oct 2019 18:19
Reporter: Fungo Wang (OCA) Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server: Information schema Severity:S3 (Non-critical)
Version:8.0.17 OS:Any
Assigned to: Jusufadis Bakamovic CPU Architecture:Any
Tags: 1041, out of memory, temptable

[17 Sep 2019 3:40] Fungo Wang
Description:
show variables failed with Out of memory error:

mysql> show variables;
ERROR 1041 (HY000): Out of memory; check if mysqld or some other process uses all available memory; if not, you may have to use 'ulimit' to allow mysqld to use more memory or you can add more swap space

How to repeat:
1. Change the temptable memory limit to the minimal possible value, so we can easily repro by hand. (You can use large value, and repro with high concurrent "show variables"). And also disable using mmap if temptable limitation is reached.
SET GLOBAL temptable_max_ram = 2097152;
SET GLOBAL temptable_use_mmap = OFF;

2. Open 2 session manually, and run "show variables" in each of them. In my debug building, the 1st succeeded, the 2nd failed

session 1 > show variables;

session 2 >show variables;
ERROR 1041 (HY000): Out of memory; check if mysqld or some other process uses all available memory; if not, you may have to use 'ulimit' to allow mysqld to use more memory or you can add more swap space

Suggested fix:
In temptable::Handler::create()

```
122  Result ret = Result::OUT_OF_MEM;
123
124  try {
125    // clang-format off
126    DBUG_EXECUTE_IF(
127        "temptable_create_return_full",
128        ret = Result::RECORD_FILE_FULL;
129        throw std::bad_alloc();
130    );
131    // clang-format on
132
133    const auto insert_result = tls_tables.emplace(
134        std::piecewise_construct, std::make_tuple(table_name),
135        std::forward_as_tuple(mysql_table, all_columns_are_fixed_size));
136
137    ret = insert_result.second ? Result::OK : Result::TABLE_EXIST;
138  } catch (...) {
139    /* ret is already set above. */
140  }
```

The default ret code is set to `Result::OUT_OF_MEM`, and if exception happened for tls_tables.emplace, Result::OUT_OF_MEM will be returned as result.

In the upper level create_tmp_table_with_fallback()

```
2152  int error =
2153      table->file->create(share->table_name.str, table, &create_info, nullptr);
2154  if (error == HA_ERR_RECORD_FILE_FULL &&
2155      table->s->db_type() == temptable_hton) {
2156    table->file =
2157        get_new_handler(table->s, false, &table->s->mem_root, innodb_hton);
2158    error = table->file->create(share->table_name.str, table, &create_info,
2159                                nullptr);
2160  }
2161
2162  if (error) {
2163    table->file->print_error(error, MYF(0)); /* purecov: inspected */
2164    table->db_stat = 0;
2165    DBUG_RETURN(true);
2166  } else {
2167    if (table->s->db_type() != temptable_hton) {
2168      table->in_use->inc_status_created_tmp_disk_tables();
2169    }
2170    DBUG_RETURN(false);
2171  }

```

if the return error is HA_ERR_RECORD_FILE_FULL, the on disk innodb tmp table will be tried. But HA_ERR_OUT_OF_MEM is returned from temptable::Handler::create().
Actually, HA_ERR_RECORD_FILE_FULL is never returned from temptable::Handler::create(), so the fallback logic will never be touched.

For temptable::Table::m_columns, the TempTable allocator is used to provide memory, and capped by temptable_use_mmap, as bellow stack:

```
#0  temptable::Allocator<temptable::Column>::mem_fetch (this=0x7f16e8023988, bytes=1048576) at /home/fungo/Projects/mysql-server-8/storage/temptable/include/temptable/allocator.h:663
#1  0x0000000006790a89 in temptable::Allocator<temptable::Column>::block_create (this=0x7f16e8023988, first_alloc_size=32) at /home/fungo/Projects/mysql-server-8/storage/temptable/include/temptable/allocator.h:902
#2  0x000000000678e4b7 in temptable::Allocator<temptable::Column>::allocate (this=0x7f16e8023988, n_elements=2) at /home/fungo/Projects/mysql-server-8/storage/temptable/include/temptable/allocator.h:530
#3  0x000000000678cd29 in std::allocator_traits<temptable::Allocator<temptable::Column> >::allocate (__a=..., __n=2) at /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/alloc_traits.h:301
#4  0x000000000678b480 in std::_Vector_base<temptable::Column, temptable::Allocator<temptable::Column> >::_M_allocate (this=0x7f16e8023988, __n=2) at /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_vector.h:172
#5  0x0000000006789922 in std::vector<temptable::Column, temptable::Allocator<temptable::Column> >::_M_allocate_and_copy<std::move_iterator<temptable::Column*> > (this=0x7f16e8023988, __n=2, __first=..., __last=...)
    at /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_vector.h:1260
#6  0x0000000006787d89 in std::vector<temptable::Column, temptable::Allocator<temptable::Column> >::reserve (this=0x7f16e8023988, __n=2) at /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/vector.tcc:73
#7  0x0000000006784792 in temptable::Table::Table (this=0x7f16e80238e0, mysql_table=0x7f16e800a0f8, all_columns_are_fixed_size=false) at /home/fungo/Projects/mysql-server-8/storage/temptable/src/table.cc:82
#8  0x000000000677e92b in std::pair<std::string const, temptable::Table>::pair<char const*, 0ul, TABLE*&, bool&, 0ul, 1ul> (this=0x7f16e80238d8, __tuple1=..., __tuple2=...) at /opt/rh/devtoolset-7/root/usr/include/c++/7/tuple:1652
#9  0x000000000677e571 in std::pair<std::string const, temptable::Table>::pair<char const*, TABLE*&, bool&> (this=0x7f16e80238d8, __first=..., __second=...) at /opt/rh/devtoolset-7/root/usr/include/c++/7/tuple:1641
#10 0x000000000677e0e7 in __gnu_cxx::new_allocator<std::pair<std::string const, temptable::Table> >::construct<std::pair<std::string const, temptable::Table>, std::piecewise_construct_t const&, std::tuple<char const*>, std::tuple<TABLE*&, bool&> >(std::pair<std::string const, temptable::Table>*, std::piecewise_construct_t const&, std::tuple<char const*>&&, std::tuple<TABLE*&, bool&>&&) (this=0x7f179c072def, __p=0x7f16e80238d8, __args#0=..., __args#1=...,
...
    __args#2=...) at /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/hashtable.h:1641
#14 0x000000000677b73c in std::_Hashtable<std::string, std::pair<std::string const, temptable::Table>, std::allocator<std::pair<std::string const, temptable::Table> >, std::__detail::_Select1st, std::equal_to<std::string>, std::hash<std::string>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::emplace<std::piecewise_construct_t const&, std::tuple<char const*>, std::tuple<TABLE*&, bool&> >(std::piecewise_construct_t const&, std::tuple<char const*>&&, std::tuple<TABLE*&, bool&>&&) (this=0x7f179c0766b0, __args#0=..., __args#1=..., __args#2=...)
    at /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/hashtable.h:736
#15 0x000000000677aa7a in std::unordered_map<std::string, temptable::Table, std::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<std::string const, temptable::Table> > >::emplace<std::piecewise_construct_t const&, std::tuple<char const*>, std::tuple<TABLE*&, bool&> >(std::piecewise_construct_t const&, std::tuple<char const*>&&, std::tuple<TABLE*&, bool&>&&) (this=0x7f179c0766b0, __args#0=..., __args#1=..., __args#2=...)
    at /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/unordered_map.h:387
#16 0x000000000676e32b in temptable::Handler::create (this=0x7f16e801ed98, table_name=0x7f16e8021ae8 "/flash1/xiangluo.wb/Projects/mysql-server-8/mysql-test/var/tmp/mysqld.1/#sqlf517_b_2", mysql_table=0x7f16e800a0f8)
    at /home/fungo/Projects/mysql-server-8/storage/temptable/src/handler.cc:135
#17 0x0000000003000f6b in create_tmp_table_with_fallback (table=0x7f16e800a0f8) at /home/fungo/Projects/mysql-server-8/sql/sql_tmp_table.cc:2153
#18 0x0000000003001b07 in instantiate_tmp_table (thd=0x7f16e8000c00, table=0x7f16e800a0f8) at /home/fungo/Projects/mysql-server-8/sql/sql_tmp_table.cc:2231
#19 0x0000000003a0330a in TABLE_LIST::create_materialized_table (this=0x7f16e802ec48, thd=0x7f16e8000c00) at /home/fungo/Projects/mysql-server-8/sql/sql_derived.cc:768
```

How to fix:

Change the default ret code of temptable::Handler::create() to Result::RECORD_FILE_FULL

```
diff --git a/storage/temptable/src/handler.cc b/storage/temptable/src/handler.cc
index e5898e0de69..9fa9e1fc675 100644
--- a/storage/temptable/src/handler.cc
+++ b/storage/temptable/src/handler.cc
@@ -119,7 +119,7 @@ int Handler::create(const char *table_name, TABLE *mysql_table,
     }
   }

-  Result ret = Result::OUT_OF_MEM;
+  Result ret = Result::RECORD_FILE_FULL;

   try {
     // clang-format off
```
[17 Sep 2019 8:10] MySQL Verification Team
Hello Fungo Wang,

Thank you for the report.
Verified as described on 8.0.17 build.

regards,
Umesh
[29 Oct 2019 18:19] Daniel Price
Posted by developer:
 
Fixed as of the upcoming 8.0.19 release, and here's the changelog entry:

When the temptable_max_ram limit was reached, the TempTable storage
engine incorrectly reported an out-of-memory error instead of falling back
to disk-based storage.