Commit ab761db8 authored by Mattias Jonsson's avatar Mattias Jonsson

Bug#12696518: MEMORY LEAKS IN HA_PARTITION (VALGRIND TESTS ON TRUNK)

(also 5.5+ solution for bug#11766879/bug#60106)

The valgrind warning was due to an unused 'new handler_add_index(...)'
which was never freed.

The error handling did not work (fails as in bug#11766879) and
the implementation was not as transparant as it could, therefore I
made it a bit simpler and more transparant to the underlying handlers.

This way it follows the api better and the error handling works and
is also now tested.

Also added a debug test to verify the error handling.

Improved according to Jon Olavs review:
Added class ha_partition_add_index.
Also added base class Sql_alloc to handler_add_index.
Update 3.
parent 39175b92
#
# Bug#11766879/Bug#60106: DIFF BETWEEN # OF INDEXES IN MYSQL VS INNODB,
# PARTITONING, ON INDEX CREATE
# Bug#12696518: MEMORY LEAKS IN HA_PARTITION (VALGRIND TESTS ON TRUNK)
#
CREATE TABLE t1 (
id bigint NOT NULL AUTO_INCREMENT,
time date,
id2 bigint not null,
PRIMARY KEY (id,time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE(TO_DAYS(time))
(PARTITION p10 VALUES LESS THAN (734708) ENGINE = InnoDB,
PARTITION p20 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */;
INSERT INTO t1 (time,id2) VALUES ('2011-07-24',1);
INSERT INTO t1 (time,id2) VALUES ('2011-07-25',1);
INSERT INTO t1 (time,id2) VALUES ('2011-07-25',1);
CREATE UNIQUE INDEX uk_time_id2 on t1(time,id2);
ERROR 23000: Duplicate entry '2011-07-25-1' for key 'uk_time_id2'
SELECT COUNT(*) FROM t1;
COUNT(*)
3
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`time` date NOT NULL DEFAULT '0000-00-00',
`id2` bigint(20) NOT NULL,
PRIMARY KEY (`id`,`time`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE (TO_DAYS(time))
(PARTITION p10 VALUES LESS THAN (734708) ENGINE = InnoDB,
PARTITION p20 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
DROP TABLE t1;
call mtr.add_suppression("nnoDB: Error: table `test`.`t1` .* Partition.* InnoDB internal"); call mtr.add_suppression("nnoDB: Error: table `test`.`t1` .* Partition.* InnoDB internal");
# #
# Bug#55091: Server crashes on ADD PARTITION after a failed attempt # Bug#55091: Server crashes on ADD PARTITION after a failed attempt
......
DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t1;
#
# Bug#12696518/Bug#11766879/60106:DIFF BETWEEN # OF INDEXES IN MYSQL
# VS INNODB, PARTITONING, ON INDEX CREATE
#
CREATE TABLE t1
(a INT PRIMARY KEY,
b VARCHAR(64))
ENGINE = InnoDB
PARTITION BY HASH (a) PARTITIONS 3;
INSERT INTO t1 VALUES (0, 'first row'), (1, 'second row'), (2, 'Third row');
INSERT INTO t1 VALUES (3, 'row id 3'), (4, '4 row'), (5, 'row5');
INSERT INTO t1 VALUES (6, 'X 6 row'), (7, 'Seventh row'), (8, 'Last row');
ALTER TABLE t1 ADD INDEX new_b_index (b);
ALTER TABLE t1 DROP INDEX new_b_index;
SET SESSION debug= "+d,ha_partition_fail_final_add_index";
ALTER TABLE t1 ADD INDEX (b);
ERROR HY000: Table has no partition for value 0
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`a` int(11) NOT NULL,
`b` varchar(64) DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50100 PARTITION BY HASH (a)
PARTITIONS 3 */
SELECT * FROM t1;
a b
0 first row
1 second row
2 Third row
3 row id 3
4 4 row
5 row5
6 X 6 row
7 Seventh row
8 Last row
FLUSH TABLES;
CREATE INDEX new_index ON t1 (b);
ERROR HY000: Table has no partition for value 0
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`a` int(11) NOT NULL,
`b` varchar(64) DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50100 PARTITION BY HASH (a)
PARTITIONS 3 */
SELECT * FROM t1;
a b
0 first row
1 second row
2 Third row
3 row id 3
4 4 row
5 row5
6 X 6 row
7 Seventh row
8 Last row
SET SESSION debug= "-d,ha_partition_fail_final_add_index";
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`a` int(11) NOT NULL,
`b` varchar(64) DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50100 PARTITION BY HASH (a)
PARTITIONS 3 */
DROP TABLE t1;
call mtr.add_suppression("InnoDB: Warning: allocated tablespace .*, old maximum was"); call mtr.add_suppression("InnoDB: Warning: allocated tablespace .*, old maximum was");
call mtr.add_suppression("InnoDB: Error: table .* does not exist in the InnoDB internal"); call mtr.add_suppression("InnoDB: Error: table .* does not exist in the InnoDB internal");
call mtr.add_suppression("InnoDB: Warning: MySQL is trying to drop table "); call mtr.add_suppression("InnoDB: Warning: MySQL is trying to drop table ");
......
...@@ -12,6 +12,41 @@ DROP TABLE IF EXISTS t1; ...@@ -12,6 +12,41 @@ DROP TABLE IF EXISTS t1;
--let $DATADIR= `SELECT @@datadir;` --let $DATADIR= `SELECT @@datadir;`
--echo #
--echo # Bug#12696518/Bug#11766879/60106:DIFF BETWEEN # OF INDEXES IN MYSQL
--echo # VS INNODB, PARTITONING, ON INDEX CREATE
--echo #
CREATE TABLE t1
(a INT PRIMARY KEY,
b VARCHAR(64))
ENGINE = InnoDB
PARTITION BY HASH (a) PARTITIONS 3;
INSERT INTO t1 VALUES (0, 'first row'), (1, 'second row'), (2, 'Third row');
INSERT INTO t1 VALUES (3, 'row id 3'), (4, '4 row'), (5, 'row5');
INSERT INTO t1 VALUES (6, 'X 6 row'), (7, 'Seventh row'), (8, 'Last row');
ALTER TABLE t1 ADD INDEX new_b_index (b);
ALTER TABLE t1 DROP INDEX new_b_index;
SET SESSION debug= "+d,ha_partition_fail_final_add_index";
--error ER_NO_PARTITION_FOR_GIVEN_VALUE
ALTER TABLE t1 ADD INDEX (b);
SHOW CREATE TABLE t1;
--sorted_result
SELECT * FROM t1;
FLUSH TABLES;
--error ER_NO_PARTITION_FOR_GIVEN_VALUE
CREATE INDEX new_index ON t1 (b);
SHOW CREATE TABLE t1;
--sorted_result
SELECT * FROM t1;
SET SESSION debug= "-d,ha_partition_fail_final_add_index";
SHOW CREATE TABLE t1;
DROP TABLE t1;
# Checking with #innodb what this is... # Checking with #innodb what this is...
call mtr.add_suppression("InnoDB: Warning: allocated tablespace .*, old maximum was"); call mtr.add_suppression("InnoDB: Warning: allocated tablespace .*, old maximum was");
# If there is a crash or failure between the ddl_log is written and the # If there is a crash or failure between the ddl_log is written and the
......
...@@ -3,6 +3,33 @@ ...@@ -3,6 +3,33 @@
let $MYSQLD_DATADIR= `SELECT @@datadir`; let $MYSQLD_DATADIR= `SELECT @@datadir`;
--echo #
--echo # Bug#11766879/Bug#60106: DIFF BETWEEN # OF INDEXES IN MYSQL VS INNODB,
--echo # PARTITONING, ON INDEX CREATE
--echo # Bug#12696518: MEMORY LEAKS IN HA_PARTITION (VALGRIND TESTS ON TRUNK)
--echo #
CREATE TABLE t1 (
id bigint NOT NULL AUTO_INCREMENT,
time date,
id2 bigint not null,
PRIMARY KEY (id,time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE(TO_DAYS(time))
(PARTITION p10 VALUES LESS THAN (734708) ENGINE = InnoDB,
PARTITION p20 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */;
INSERT INTO t1 (time,id2) VALUES ('2011-07-24',1);
INSERT INTO t1 (time,id2) VALUES ('2011-07-25',1);
INSERT INTO t1 (time,id2) VALUES ('2011-07-25',1);
--error ER_DUP_ENTRY
CREATE UNIQUE INDEX uk_time_id2 on t1(time,id2);
SELECT COUNT(*) FROM t1;
SHOW CREATE TABLE t1;
DROP TABLE t1;
call mtr.add_suppression("nnoDB: Error: table `test`.`t1` .* Partition.* InnoDB internal"); call mtr.add_suppression("nnoDB: Error: table `test`.`t1` .* Partition.* InnoDB internal");
--echo # --echo #
--echo # Bug#55091: Server crashes on ADD PARTITION after a failed attempt --echo # Bug#55091: Server crashes on ADD PARTITION after a failed attempt
......
...@@ -6662,50 +6662,82 @@ bool ha_partition::check_if_incompatible_data(HA_CREATE_INFO *create_info, ...@@ -6662,50 +6662,82 @@ bool ha_partition::check_if_incompatible_data(HA_CREATE_INFO *create_info,
} }
/**
Helper class for [final_]add_index, see handler.h
*/
class ha_partition_add_index : public handler_add_index
{
public:
handler_add_index **add_array;
ha_partition_add_index(TABLE* table_arg, KEY* key_info_arg,
uint num_of_keys_arg)
: handler_add_index(table_arg, key_info_arg, num_of_keys_arg)
{}
~ha_partition_add_index() {}
};
/** /**
Support of in-place add/drop index Support of in-place add/drop index
@param table_arg Table to add index to
@param key_info Struct over the new keys to add
@param num_of_keys Number of keys to add
@param[out] add Data to be submitted with final_add_index
@return Operation status
@retval 0 Success
@retval != 0 Failure (error code returned, and all operations rollbacked)
*/ */
int ha_partition::add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys, int ha_partition::add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys,
handler_add_index **add) handler_add_index **add)
{ {
handler **file; uint i;
int ret= 0; int ret= 0;
THD *thd= ha_thd();
ha_partition_add_index *part_add_index;
DBUG_ENTER("ha_partition::add_index"); DBUG_ENTER("ha_partition::add_index");
*add= new handler_add_index(table, key_info, num_of_keys);
/* /*
There has already been a check in fix_partition_func in mysql_alter_table There has already been a check in fix_partition_func in mysql_alter_table
before this call, which checks for unique/primary key violations of the before this call, which checks for unique/primary key violations of the
partitioning function. So no need for extra check here. partitioning function. So no need for extra check here.
*/ */
for (file= m_file; *file; file++)
/*
This will be freed at the end of the statement.
And destroyed at final_add_index. (Sql_alloc does not free in delete).
*/
part_add_index= new (thd->mem_root)
ha_partition_add_index(table_arg, key_info, num_of_keys);
if (!part_add_index)
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
part_add_index->add_array= (handler_add_index **)
thd->alloc(sizeof(void *) * m_tot_parts);
if (!part_add_index->add_array)
{ {
handler_add_index *add_index; delete part_add_index;
if ((ret= (*file)->add_index(table_arg, key_info, num_of_keys, &add_index))) DBUG_RETURN(HA_ERR_OUT_OF_MEM);
goto err; }
if ((ret= (*file)->final_add_index(add_index, true)))
for (i= 0; i < m_tot_parts; i++)
{
if ((ret= m_file[i]->add_index(table_arg, key_info, num_of_keys,
&part_add_index->add_array[i])))
goto err; goto err;
} }
*add= part_add_index;
DBUG_RETURN(ret); DBUG_RETURN(ret);
err: err:
if (file > m_file) /* Rollback all prepared partitions. i - 1 .. 0 */
while (i)
{ {
uint *key_numbers= (uint*) ha_thd()->alloc(sizeof(uint) * num_of_keys); i--;
uint old_num_of_keys= table_arg->s->keys; (void) m_file[i]->final_add_index(part_add_index->add_array[i], false);
uint i;
/* The newly created keys have the last id's */
for (i= 0; i < num_of_keys; i++)
key_numbers[i]= i + old_num_of_keys;
if (!table_arg->key_info)
table_arg->key_info= key_info;
while (--file >= m_file)
{
(void) (*file)->prepare_drop_index(table_arg, key_numbers, num_of_keys);
(void) (*file)->final_drop_index(table_arg);
}
if (table_arg->key_info == key_info)
table_arg->key_info= NULL;
} }
delete part_add_index;
DBUG_RETURN(ret); DBUG_RETURN(ret);
} }
...@@ -6713,37 +6745,119 @@ err: ...@@ -6713,37 +6745,119 @@ err:
/** /**
Second phase of in-place add index. Second phase of in-place add index.
@param add Info from add_index
@param commit Should we commit or rollback the add_index operation
@return Operation status
@retval 0 Success
@retval != 0 Failure (error code returned)
@note If commit is false, index changes are rolled back by dropping the @note If commit is false, index changes are rolled back by dropping the
added indexes. If commit is true, nothing is done as the indexes added indexes. If commit is true, nothing is done as the indexes
were already made active in ::add_index() were already made active in ::add_index()
*/ */
int ha_partition::final_add_index(handler_add_index *add, bool commit) int ha_partition::final_add_index(handler_add_index *add, bool commit)
{ {
ha_partition_add_index *part_add_index;
uint i;
int ret= 0;
DBUG_ENTER("ha_partition::final_add_index"); DBUG_ENTER("ha_partition::final_add_index");
// Rollback by dropping indexes.
if (!commit) if (!add)
{ {
TABLE *table_arg= add->table; DBUG_ASSERT(!commit);
uint num_of_keys= add->num_of_keys; DBUG_RETURN(0);
handler **file; }
uint *key_numbers= (uint*) ha_thd()->alloc(sizeof(uint) * num_of_keys); part_add_index= static_cast<class ha_partition_add_index*>(add);
uint old_num_of_keys= table_arg->s->keys;
uint i; for (i= 0; i < m_tot_parts; i++)
/* The newly created keys have the last id's */ {
for (i= 0; i < num_of_keys; i++) if ((ret= m_file[i]->final_add_index(part_add_index->add_array[i], commit)))
key_numbers[i]= i + old_num_of_keys; goto err;
if (!table_arg->key_info) DBUG_EXECUTE_IF("ha_partition_fail_final_add_index", {
table_arg->key_info= add->key_info; /* Simulate a failure by rollback the second partition */
for (file= m_file; *file; file++) if (m_tot_parts > 1)
{
i++;
m_file[i]->final_add_index(part_add_index->add_array[i], false);
/* Set an error that is specific to ha_partition. */
ret= HA_ERR_NO_PARTITION_FOUND;
goto err;
}
});
}
delete part_add_index;
DBUG_RETURN(ret);
err:
uint j;
uint *key_numbers= NULL;
KEY *old_key_info= NULL;
uint num_of_keys= 0;
int error;
/* How could this happen? Needed to create a covering test case :) */
DBUG_ASSERT(ret == HA_ERR_NO_PARTITION_FOUND);
if (i > 0)
{
num_of_keys= part_add_index->num_of_keys;
key_numbers= (uint*) ha_thd()->alloc(sizeof(uint) * num_of_keys);
if (!key_numbers)
{
sql_print_error("Failed with error handling of adding index:\n"
"committing index failed, and when trying to revert "
"already committed partitions we failed allocating\n"
"memory for the index for table '%s'",
table_share->table_name.str);
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
}
old_key_info= table->key_info;
/*
Use the newly added key_info as table->key_info to remove them.
Note that this requires the subhandlers to use name lookup of the
index. They must use given table->key_info[key_number], they cannot
use their local view of the keys, since table->key_info only include
the indexes to be removed here.
*/
for (j= 0; j < num_of_keys; j++)
key_numbers[j]= j;
table->key_info= part_add_index->key_info;
}
for (j= 0; j < m_tot_parts; j++)
{
if (j < i)
{ {
(void) (*file)->prepare_drop_index(table_arg, key_numbers, num_of_keys); /* Remove the newly added index */
(void) (*file)->final_drop_index(table_arg); error= m_file[j]->prepare_drop_index(table, key_numbers, num_of_keys);
if (error || m_file[j]->final_drop_index(table))
{
sql_print_error("Failed with error handling of adding index:\n"
"committing index failed, and when trying to revert "
"already committed partitions we failed removing\n"
"the index for table '%s' partition nr %d",
table_share->table_name.str, j);
}
}
else if (j > i)
{
/* Rollback non finished partitions */
if (m_file[j]->final_add_index(part_add_index->add_array[j], false))
{
/* How could this happen? */
sql_print_error("Failed with error handling of adding index:\n"
"Rollback of add_index failed for table\n"
"'%s' partition nr %d",
table_share->table_name.str, j);
}
} }
if (table_arg->key_info == add->key_info)
table_arg->key_info= NULL;
} }
DBUG_RETURN(0); if (i > 0)
table->key_info= old_key_info;
delete part_add_index;
DBUG_RETURN(ret);
} }
int ha_partition::prepare_drop_index(TABLE *table_arg, uint *key_num, int ha_partition::prepare_drop_index(TABLE *table_arg, uint *key_num,
......
...@@ -1163,10 +1163,12 @@ uint calculate_key_len(TABLE *, uint, const uchar *, key_part_map); ...@@ -1163,10 +1163,12 @@ uint calculate_key_len(TABLE *, uint, const uchar *, key_part_map);
/** /**
Index creation context. Index creation context.
Created by handler::add_index() and freed by handler::final_add_index(). Created by handler::add_index() and destroyed by handler::final_add_index().
And finally freed at the end of the statement.
(Sql_alloc does not free in delete).
*/ */
class handler_add_index class handler_add_index : public Sql_alloc
{ {
public: public:
/* Table where the indexes are added */ /* Table where the indexes are added */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment