Bug #59143 valgrind leak warning from innodb_plugin.innodb-index for mysql 5.1.52
Submitted: 24 Dec 2010 0:13 Modified: 21 Jan 2011 16:56
Reporter: Mark Callaghan Email Updates:
Status: Duplicate Impact on me:
None 
Category:MySQL Server: InnoDB Plugin storage engine Severity:S2 (Serious)
Version:5.1.52, 5.5, 5.6 OS:Any
Assigned to: Assigned Account CPU Architecture:Any
Tags: innodb, Leak, plugin, valgrind

[24 Dec 2010 0:13] Mark Callaghan
Description:
The test innodb_plugin.innodb-index has valgrind leak warnings. The test passes otherwise.

14 bytes in 1 blocks are indirectly lost in loss record 1 of 5
   at 0x4A05809: malloc (vg_replace_malloc.c:149)
   by 0x5935079: ???
   by 0x587755F: ???
   by 0x58F16F6: ???
   by 0x589EADA: ???
   by 0x6DFA95: mysql_alter_table(THD*, char*, char*, st_ha_create_information*, TABLE_LIST*, Alter_info*, unsigned, st_order*, bool) (sql_table.cc:7326)
   by 0x5CAB64: mysql_execute_command(THD*) (sql_parse.cc:2968)
   by 0x5CB66E: mysql_parse(THD*, char*, unsigned, char const**) (sql_parse.cc:6051)
   by 0x5CC37E: dispatch_command(enum_server_command, THD*, char*, unsigned) (sql_parse.cc:1260)
   by 0x5CD351: do_command(THD*) (sql_parse.cc:888)
   by 0x5BC7D4: handle_one_connection (sql_connect.cc:1136)
   by 0x328F8062F6: start_thread (in /lib64/libpthread-2.5.so)
   by 0x328ECD1E3C: clone (in /lib64/libc-2.5.so)

2,062 (568 direct, 1,494 indirect) bytes in 1 blocks are definitely lost in loss record 4 of 5
   at 0x4A05809: malloc (vg_replace_malloc.c:149)
   by 0x58BC2D8: ???
   by 0x58BAE61: ???
   by 0x58BAFD5: ???
   by 0x587781F: ???
   by 0x58F16F6: ???
   by 0x589EADA: ???
   by 0x6DFA95: mysql_alter_table(THD*, char*, char*, st_ha_create_information*, TABLE_LIST*, Alter_info*, unsigned, st_order*, bool) (sql_table.cc:7326)
   by 0x5CAB64: mysql_execute_command(THD*) (sql_parse.cc:2968)
   by 0x5CB66E: mysql_parse(THD*, char*, unsigned, char const**) (sql_parse.cc:6051)
   by 0x5CC37E: dispatch_command(enum_server_command, THD*, char*, unsigned) (sql_parse.cc:1260)
   by 0x5CD351: do_command(THD*) (sql_parse.cc:888)
   by 0x5BC7D4: handle_one_connection (sql_connect.cc:1136)
   by 0x328F8062F6: start_thread (in /lib64/libpthread-2.5.so)
   by 0x328ECD1E3C: clone (in /lib64/libc-2.5.so)

1,480 bytes in 2 blocks are indirectly lost in loss record 5 of 5
   at 0x4A05809: malloc (vg_replace_malloc.c:149)
   by 0x58BC2D8: ???
   by 0x58BAE61: ???
   by 0x5877421: ???
   by 0x58F16F6: ???
   by 0x589EADA: ???
   by 0x6DFA95: mysql_alter_table(THD*, char*, char*, st_ha_create_information*, TABLE_LIST*, Alter_info*, unsigned, st_order*, bool) (sql_table.cc:7326)
   by 0x5CAB64: mysql_execute_command(THD*) (sql_parse.cc:2968)
   by 0x5CB66E: mysql_parse(THD*, char*, unsigned, char const**) (sql_parse.cc:6051)
   by 0x5CC37E: dispatch_command(enum_server_command, THD*, char*, unsigned) (sql_parse.cc:1260)
   by 0x5CD351: do_command(THD*) (sql_parse.cc:888)
   by 0x5BC7D4: handle_one_connection (sql_connect.cc:1136)
   by 0x328F8062F6: start_thread (in /lib64/libpthread-2.5.so)
   by 0x328ECD1E3C: clone (in /lib64/libc-2.5.so)

LEAK SUMMARY:
   definitely lost: 568 bytes in 1 blocks.
   indirectly lost: 1,494 bytes in 3 blocks.
     possibly lost: 0 bytes in 0 blocks.
   still reachable: 0 bytes in 0 blocks.
        suppressed: 88 bytes in 2 blocks.

How to repeat:
The server uses centos 5.2, valgrind 3.2.1 and gcc 4.1.2
This is from unmodified mysql 5.1.52

./configure --enable-thread-safe-client --with-plugins=partition,csv,blackhole,myisam,heap,innodb_plugin,innobase --with-fast-mutexes --with-extra-charsets=all --with-debug C_EXTRA_FLAGS="-fno-omit-frame-pointer -fno-strict-aliasing -DHAVE_purify -DUNIV_DEBUG_VALGRIND -Wall" 

make

./mysql-test-run.pl innodb_plugin.innodb-index
[24 Dec 2010 0:39] Mark Callaghan
full stacks from 5.1.52 with the facebook patch

==30290== 14 bytes in 1 blocks are still reachable in loss record 1 of 3
==30290==    at 0x4A05809: malloc (vg_replace_malloc.c:149)
==30290==    by 0x8F2FC7: ut_malloc_low (ut0mem.c:106)
==30290==    by 0x8F3384: ut_malloc (ut0mem.c:244)
==30290==    by 0x940EAC: dict_mem_table_create (dict0mem.c:71)
==30290==    by 0x8B50F0: row_merge_create_temporary_table (row0merge.c:2249)
==30290==    by 0x859B48: ha_innobase::add_index(st_table*, st_key*, unsigned) (handler0alter.cc:741)
==30290==    by 0x782B5F: mysql_alter_table(THD*, char*, char*, st_ha_create_information*, TABLE_LIST*, Alter_info*, unsigned, st_order*, bool) (sql_table.cc:7377)
==30290==    by 0x613175: mysql_execute_command(THD*, unsigned long long*) (sql_parse.cc:3276)
==30290==    by 0x61A46D: mysql_parse(THD*, char*, unsigned, char const**, unsigned long long*) (sql_parse.cc:6470)
==30290==    by 0x61C769: dispatch_command(enum_server_command, THD*, char*, unsigned) (sql_parse.cc:1321)
==30290==    by 0x61DFC4: do_command(THD*) (sql_parse.cc:935)
==30290==    by 0x608BF4: handle_one_connection (sql_connect.cc:1184)
==30290==    by 0x31EF2062F6: start_thread (in /lib64/libpthread-2.5.so)
==30290==    by 0x31EE6D1E3C: clone (in /lib64/libc-2.5.so)
==30290== 
==30290== 
==30290== 2,552 bytes in 3 blocks are still reachable in loss record 3 of 3
==30290==    at 0x4A05809: malloc (vg_replace_malloc.c:149)
==30290==    by 0x87B4E4: mem_area_alloc (mem0pool.c:380)
==30290==    by 0x879EB5: mem_heap_create_block (mem0mem.c:333)
==30290==    by 0x878D5C: mem_heap_create_func (mem0mem.ic:443)
==30290==    by 0x940E40: dict_mem_table_create (dict0mem.c:64)
==30290==    by 0x8B50F0: row_merge_create_temporary_table (row0merge.c:2249)
==30290==    by 0x859B48: ha_innobase::add_index(st_table*, st_key*, unsigned) (handler0alter.cc:741)
==30290==    by 0x782B5F: mysql_alter_table(THD*, char*, char*, st_ha_create_information*, TABLE_LIST*, Alter_info*, unsigned, st_order*, bool) (sql_table.cc:7377)
==30290==    by 0x613175: mysql_execute_command(THD*, unsigned long long*) (sql_parse.cc:3276)
==30290==    by 0x61A46D: mysql_parse(THD*, char*, unsigned, char const**, unsigned long long*) (sql_parse.cc:6470)
==30290==    by 0x61C769: dispatch_command(enum_server_command, THD*, char*, unsigned) (sql_parse.cc:1321)
==30290==    by 0x61DFC4: do_command(THD*) (sql_parse.cc:935)
==30290==    by 0x608BF4: handle_one_connection (sql_connect.cc:1184)
==30290==    by 0x31EF2062F6: start_thread (in /lib64/libpthread-2.5.so)
==30290==    by 0x31EE6D1E3C: clone (in /lib64/libc-2.5.so)
[24 Dec 2010 1:11] Mark Callaghan
This is a reproduction case:

-- source include/have_innodb_plugin.inc

create table t1(a int not null, b int, c char(10) not null, d varchar(20)) engine = innodb;
insert into t1 values (5,5,'oo','oo'),(4,4,'tr','tr'),(3,4,'ad','ad'),(2,3,'ak','ak');
commit;

# Check how existing tables interfere with temporary tables.
CREATE TABLE `t1#1`(a INT PRIMARY KEY) ENGINE=InnoDB;

--error 156
alter table t1 add unique index (c), add index (d);

------

and I assume error handling in here is incomplete

row_merge_create_temporary_table(
/*=============================*/
        const char*             table_name,     /*!< in: new table name */
        const merge_index_def_t*index_def,      /*!< in: the index definition
                                                of the primary key */
        const dict_table_t*     table,          /*!< in: old table definition */
        trx_t*                  trx)            /*!< in/out: transaction
                                                (sets error_state) */
{
        ulint           i;
        dict_table_t*   new_table = NULL;
        ulint           n_cols = dict_table_get_n_user_cols(table);
        ulint           error;
        mem_heap_t*     heap = mem_heap_create(1000);

        ut_ad(table_name);
        ut_ad(index_def);
        ut_ad(table);
        ut_ad(mutex_own(&dict_sys->mutex));

        new_table = dict_mem_table_create(table_name, 0, n_cols, table->flags);

        for (i = 0; i < n_cols; i++) {
                const dict_col_t*       col;
                const char*             col_name;

                col = dict_table_get_nth_col(table, i);
                col_name = dict_table_get_col_name(table, i);

                dict_mem_table_add_col(new_table, heap, col_name, col->mtype,
                                       row_merge_col_prtype(col, col_name,
                                                            index_def),
                                       col->len);
        }

        error = row_create_table_for_mysql(new_table, trx);
        mem_heap_free(heap);

        if (error != DB_SUCCESS) {
                trx->error_state = error;
                new_table = NULL;
        }

        return(new_table);
}
[24 Dec 2010 1:12] Mark Callaghan
Is this missing a call to dict_mem_table_free?
[24 Dec 2010 7:14] MySQL Verification Team
mysql-trunk has same problem.

Attachment: bug59143_5.6.1_valgrind_memory_leaks.txt (text/plain), 8.03 KiB.

[24 Dec 2010 7:32] MySQL Verification Team
verified on todays mysql-5.5-innodb tree

[sbester@levovo mysql-5.5-innodb]$ bzr revno
3264
[21 Jan 2011 12:16] Vasil Dimov
This is a duplicate of Bug#56947 InnoDB leaks memory when failing to create a table

and has already been fixed in 5.1 in these two changesets:

          ------------------------------------------------------------
          revno: 3452.13.3
          revision-id: marko.makela@oracle.com-20101011081800-sby6kmb8n1mnryfq
          parent: marko.makela@oracle.com-20101011080147-72nory5su5bo9j9k
          committer: Marko M?kel? <marko.makela@oracle.com>
          timestamp: Mon 2010-10-11 11:18:00 +0300
          message:
            Bug #56947 InnoDB leaks memory when failing to create a table
            
            row_create_table_for_mysql(): When the table creation fails,
            free the dict_table_t object.
          ------------------------------------------------------------
          revno: 3452.13.2
          revision-id: marko.makela@oracle.com-20101011080147-72nory5su5bo9j9k
          parent: sunanda.menon@sun.com-20101007132514-yrhckyu0eltsytrt
          committer: Marko M?kel? <marko.makela@oracle.com>
          timestamp: Mon 2010-10-11 11:01:47 +0300
          message:
            Bug #56947 InnoDB leaks memory when failing to create a table
            
            No mysql-test case. Tested by creating a table, removing a *.frm file and
            attempting to create the table again. Code coverage tested by
            instrumentation. Tested with Valgrind.
          ------------------------------------------------------------
[21 Jan 2011 13:42] Mark Callaghan
Does 56947 need to be marked private?
[21 Jan 2011 14:13] Vasil Dimov
Not anymore
[21 Jan 2011 16:45] Mark Callaghan
I found those patches from the 5.1 launchpad branch. They are for storage/innobase not storage/innodb_plugin. From reading the bug text this bug was only in the plugin. Why was the built-in InnoDB changed?
[21 Jan 2011 16:52] Vasil Dimov
Mark, which patches? The bug exists/existed only in 5.1/storage/innodb_plugin.

Notice that in 5.5 there is just one InnoDB and it lives under 5.5/storage/innobase which is based on 5.1/storage/innodb_plugin so it also has this bug. Maybe you were looking at some 5.5 patches?
[21 Jan 2011 16:56] Mark Callaghan
I don't understand why this wasn't fixed by changing row_merge_create_temporary_table. That code in the 5.1 launchpad branch still doesn't call dict_mem_table_free on error. 

There were two commits listed. One changes built-in code, the other changes plug-in code. I will test the plug-in change.

        new_table = dict_mem_table_create(table_name, 0, n_cols, table->flags);

        for (i = 0; i < n_cols; i++) {
                const dict_col_t*       col;
                const char*             col_name;

                col = dict_table_get_nth_col(table, i);
                col_name = dict_table_get_col_name(table, i);

                dict_mem_table_add_col(new_table, heap, col_name, col->mtype,
                                       row_merge_col_prtype(col, col_name,
                                                            index_def),
                                       col->len);
        }

        error = row_create_table_for_mysql(new_table, trx);
        mem_heap_free(heap);

        if (error != DB_SUCCESS) {
                trx->error_state = error;
                new_table = NULL;
        }

        return(new_table);
[21 Jan 2011 16:59] Vasil Dimov
patch1

Attachment: 5.1-marko.makela@oracle.com-20101011080147-72nory5su5bo9j9k.diff (text/x-patch), 550 bytes.

[21 Jan 2011 17:00] Vasil Dimov
patch2

Attachment: 5.1-marko.makela@oracle.com-20101011081800-sby6kmb8n1mnryfq.diff (text/x-patch), 4.00 KiB.

[21 Jan 2011 17:03] Vasil Dimov
It was not fixed by changing row_merge_create_temporary_table() because row_create_table_for_mysql() is supposed to free new_table. But it did not do it always. If we have added the free to the caller then sometimes we would get double free bug.