Merge lp:~gl-az/percona-server/bug758788-5.5 into lp:percona-server/5.5

Proposed by George Ormond Lorch III
Status: Merged
Approved by: Laurynas Biveinis
Approved revision: no longer in the source branch.
Merged at revision: 624
Proposed branch: lp:~gl-az/percona-server/bug758788-5.5
Merge into: lp:percona-server/5.5
Diff against target: 288 lines (+156/-32)
8 files modified
mysql-test/suite/innodb/r/percona_bug758788.result (+9/-0)
mysql-test/suite/innodb/t/percona_bug758788-master.opt (+1/-0)
mysql-test/suite/innodb/t/percona_bug758788.test (+24/-0)
storage/innobase/dict/dict0dict.c (+95/-32)
storage/innobase/dict/dict0mem.c (+1/-0)
storage/innobase/include/lock0lock.h (+10/-0)
storage/innobase/include/lock0lock.ic (+14/-0)
storage/innobase/lock/lock0lock.c (+2/-0)
To merge this branch: bzr merge lp:~gl-az/percona-server/bug758788-5.5
Reviewer Review Type Date Requested Status
Laurynas Biveinis (community) Approve
Vlad Lesin (community) g2 Needs Fixing
Review via email: mp+205279@code.launchpad.net

Description of the change

Fix for bug 758788 - mysql process crashes after setting innodb_dict_size
Fix for bug 1250018 - innodb_dict_size_limit tries to do LRU eviction of an index that is in use

dict_table_LRU_trim was missing some critical checks for existing locks on tables and indexes. Cleaned this up a bit and changed the algo to be not so aggressive when trying to find and evict tables from the dict cache.

With some of the new functionality, added some more debug assertions that should trip if the table is being evicted when it shouldn't as well as watching for an unexpectedly deleted table in some places.

Created very simple debug test case for 758788.

This very likely doesn't address every possible edge crash as the InnoDB dict system was simply never designed to be a dynamic cache but a static set of instances.

jenkins http://jenkins.percona.com/view/PS%205.5/job/percona-server-5.5-param/943/

To post a comment you must log in.
Revision history for this message
Vlad Lesin (vlad-lesin) wrote :

Looks good for me, but the test case is not obvious. You did good explanation in bug #758788 comments about this test case, it would be more clear to have some resume in test comments.

review: Needs Fixing (g2)
Revision history for this message
Laurynas Biveinis (laurynas-biveinis) :
review: Needs Fixing
Revision history for this message
George Ormond Lorch III (gl-az) wrote :

Done...

Revision history for this message
Laurynas Biveinis (laurynas-biveinis) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'mysql-test/suite/innodb/r/percona_bug758788.result'
--- mysql-test/suite/innodb/r/percona_bug758788.result 1970-01-01 00:00:00 +0000
+++ mysql-test/suite/innodb/r/percona_bug758788.result 2014-02-12 18:07:20 +0000
@@ -0,0 +1,9 @@
1SET SESSION AUTOCOMMIT=0;
2CREATE TABLE t1 (c1 INT) ENGINE=INNODB;
3CREATE TABLE t2 (c1 INT) ENGINE=INNODB;
4INSERT INTO t1(c1) VALUES(1);
5FLUSH TABLES;
6INSERT INTO t2(c1) VALUES(1);
7COMMIT;
8DROP TABLE t2;
9DROP TABLE t1;
010
=== added file 'mysql-test/suite/innodb/t/percona_bug758788-master.opt'
--- mysql-test/suite/innodb/t/percona_bug758788-master.opt 1970-01-01 00:00:00 +0000
+++ mysql-test/suite/innodb/t/percona_bug758788-master.opt 2014-02-12 18:07:20 +0000
@@ -0,0 +1,1 @@
1--innodb_dict_size_limit=1
02
=== added file 'mysql-test/suite/innodb/t/percona_bug758788.test'
--- mysql-test/suite/innodb/t/percona_bug758788.test 1970-01-01 00:00:00 +0000
+++ mysql-test/suite/innodb/t/percona_bug758788.test 2014-02-12 18:07:20 +0000
@@ -0,0 +1,24 @@
1--source include/have_innodb.inc
2
3#
4# Bug #758788: mysql process crashes after setting innodb_dict_size
5#
6# With innodb_dict_size=1 in the .opt file, the 'FLUSH TABLES' command below
7# above allows the eviction of the dict_table for t1 in the dict cache even
8# though there is a lock held against it due to the INSERT and referenced
9# from the transaction lock list. When the COMMIT comes along and tries to
10# clean up the transaction lock list, the reference to t1 that was locked
11# during the first INSERT is now completely invalid and can cause various
12# assertions since it is now pointing to potentially reclaimed memory.
13#
14SET SESSION AUTOCOMMIT=0;
15
16CREATE TABLE t1 (c1 INT) ENGINE=INNODB;
17CREATE TABLE t2 (c1 INT) ENGINE=INNODB;
18INSERT INTO t1(c1) VALUES(1);
19FLUSH TABLES;
20INSERT INTO t2(c1) VALUES(1);
21COMMIT;
22
23DROP TABLE t2;
24DROP TABLE t1;
025
=== modified file 'storage/innobase/dict/dict0dict.c'
--- storage/innobase/dict/dict0dict.c 2013-12-05 08:07:26 +0000
+++ storage/innobase/dict/dict0dict.c 2014-02-12 18:07:20 +0000
@@ -65,6 +65,7 @@
65#include "srv0start.h" /* SRV_LOG_SPACE_FIRST_ID */65#include "srv0start.h" /* SRV_LOG_SPACE_FIRST_ID */
66#include "m_string.h"66#include "m_string.h"
67#include "my_sys.h"67#include "my_sys.h"
68#include "lock0lock.h"
6869
69#include <ctype.h>70#include <ctype.h>
7071
@@ -1331,6 +1332,7 @@
1331 ut_ad(table);1332 ut_ad(table);
1332 ut_ad(mutex_own(&(dict_sys->mutex)));1333 ut_ad(mutex_own(&(dict_sys->mutex)));
1333 ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);1334 ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
1335 ut_ad(lock_table_has_locks(table) == FALSE);
13341336
1335#if 01337#if 0
1336 fputs("Removing table ", stderr);1338 fputs("Removing table ", stderr);
@@ -1383,6 +1385,53 @@
1383 dict_mem_table_free(table);1385 dict_mem_table_free(table);
1384}1386}
13851387
1388/**********************************************************************//**
1389Test whether a table can be evicted from the LRU cache.
1390@return TRUE if table can be evicted. */
1391static
1392ibool
1393dict_table_can_be_evicted(
1394/*======================*/
1395 const dict_table_t* table) /*!< in: table to test */
1396{
1397 dict_index_t* index;
1398 dict_foreign_t* foreign;
1399 ibool has_locks;
1400
1401 ut_ad(mutex_own(&dict_sys->mutex));
1402
1403 /* bug 758788: A table may not have an active handle opened on it
1404 but may still have locks held on it across multiple statements
1405 within an individual transaction. So a table is not evictable if
1406 there are locks held on it */
1407 has_locks = lock_table_has_locks(table);
1408
1409 if (table->n_mysql_handles_opened || table->is_corrupt || has_locks)
1410 return(FALSE);
1411
1412 /* bug 758788: We are not allowed to free the in-memory index struct
1413 dict_index_t if there are any locks held on any of its indexes. */
1414 for (index = dict_table_get_first_index(table); index != NULL;
1415 index = dict_table_get_next_index(index)) {
1416
1417 rw_lock_t* lock = dict_index_get_lock(index);
1418
1419 if (rw_lock_is_locked(lock, RW_LOCK_SHARED)
1420 || rw_lock_is_locked(lock, RW_LOCK_EX)) {
1421 return(FALSE);
1422 }
1423 }
1424
1425 for (foreign = UT_LIST_GET_FIRST(table->foreign_list); foreign != NULL;
1426 foreign = UT_LIST_GET_NEXT(foreign_list, foreign)) {
1427
1428 if (foreign->referenced_table) {
1429 return(FALSE);
1430 }
1431 }
1432 return(TRUE);
1433}
1434
1386/**************************************************************************1435/**************************************************************************
1387Frees tables from the end of table_LRU if the dictionary cache occupies1436Frees tables from the end of table_LRU if the dictionary cache occupies
1388too much space. */1437too much space. */
@@ -1394,51 +1443,65 @@
1394{1443{
1395 dict_table_t* table;1444 dict_table_t* table;
1396 dict_table_t* prev_table;1445 dict_table_t* prev_table;
1397 dict_foreign_t* foreign;1446 ulint dict_size;
1398 ulint n_removed;1447 ulint max_depth;
1399 ulint n_have_parent;1448 ulint max_evict;
1400 ulint cached_foreign_tables;1449 ulint visited;
1450 ulint evicted;
14011451
1402#ifdef UNIV_SYNC_DEBUG1452#ifdef UNIV_SYNC_DEBUG
1403 ut_ad(mutex_own(&(dict_sys->mutex)));1453 ut_ad(mutex_own(&(dict_sys->mutex)));
1404#endif /* UNIV_SYNC_DEBUG */1454#endif /* UNIV_SYNC_DEBUG */
14051455
1406retry:1456 if (srv_dict_size_limit == 0)
1407 n_removed = n_have_parent = 0;1457 return;
1458
1459 /* Calculate this once here and then once on every eviction */
1460 dict_size = (dict_sys->table_hash->n_cells
1461 + dict_sys->table_id_hash->n_cells)
1462 * sizeof(hash_cell_t) + dict_sys->size;
1463
1464 /* Just some magic numbers to help keep us from holding the dict mutex
1465 for too long as well as preventing constant full scans of the list in
1466 a memory pressure situation. */
1467
1468 /* Don't go scanning into the front 50% of the list, chances are very
1469 good that there is nothing to be evicted in there and if there is,
1470 it should quickly get pushed into the back 50% */
1471 max_depth = UT_LIST_GET_LEN(dict_sys->table_LRU) / 2;
1472
1473 /* Don't try to evict any more than 10% of evictable tables at once. */
1474 max_evict = UT_LIST_GET_LEN(dict_sys->table_LRU) / 10;
1475
1476 visited = evicted = 0;
1477
1408 table = UT_LIST_GET_LAST(dict_sys->table_LRU);1478 table = UT_LIST_GET_LAST(dict_sys->table_LRU);
14091479
1410 while ( srv_dict_size_limit && table1480 while (table && dict_size > srv_dict_size_limit
1411 && ((dict_sys->table_hash->n_cells1481 && visited <= max_depth
1412 + dict_sys->table_id_hash->n_cells) * sizeof(hash_cell_t)1482 && srv_shutdown_state == SRV_SHUTDOWN_NONE) {
1413 + dict_sys->size) > srv_dict_size_limit ) {1483
1414 prev_table = UT_LIST_GET_PREV(table_LRU, table);1484 prev_table = UT_LIST_GET_PREV(table_LRU, table);
14151485
1416 if (table == self || table->n_mysql_handles_opened || table->is_corrupt)1486 if (table != self && dict_table_can_be_evicted(table)) {
1417 goto next_loop;
1418
1419 cached_foreign_tables = 0;
1420 foreign = UT_LIST_GET_FIRST(table->foreign_list);
1421 while (foreign != NULL) {
1422 if (foreign->referenced_table)
1423 cached_foreign_tables++;
1424 foreign = UT_LIST_GET_NEXT(foreign_list, foreign);
1425 }
1426
1427 if (cached_foreign_tables == 0) {
1428 dict_table_remove_from_cache(table);1487 dict_table_remove_from_cache(table);
1429 n_removed++;1488
1430 } else {1489 evicted++;
1431 n_have_parent++;1490
1491 if (evicted >= max_evict)
1492 break;
1493
1494 /* Only need to recalculate this when something
1495 has been evicted */
1496 dict_size = (dict_sys->table_hash->n_cells
1497 + dict_sys->table_id_hash->n_cells)
1498 * sizeof(hash_cell_t) + dict_sys->size;
1432 }1499 }
1433next_loop:1500
1501 visited++;
1502
1434 table = prev_table;1503 table = prev_table;
1435 }1504 }
1436
1437 if ( srv_dict_size_limit && n_have_parent && n_removed
1438 && ((dict_sys->table_hash->n_cells
1439 + dict_sys->table_id_hash->n_cells) * sizeof(hash_cell_t)
1440 + dict_sys->size) > srv_dict_size_limit )
1441 goto retry;
1442}1505}
14431506
1444/****************************************************************//**1507/****************************************************************//**
14451508
=== modified file 'storage/innobase/dict/dict0mem.c'
--- storage/innobase/dict/dict0mem.c 2013-08-14 03:52:04 +0000
+++ storage/innobase/dict/dict0mem.c 2014-02-12 18:07:20 +0000
@@ -115,6 +115,7 @@
115 ut_ad(table);115 ut_ad(table);
116 ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);116 ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
117 ut_d(table->cached = FALSE);117 ut_d(table->cached = FALSE);
118 ut_d(table->magic_n = 0);
118119
119#ifndef UNIV_HOTBACKUP120#ifndef UNIV_HOTBACKUP
120 mutex_free(&(table->autoinc_mutex));121 mutex_free(&(table->autoinc_mutex));
121122
=== modified file 'storage/innobase/include/lock0lock.h'
--- storage/innobase/include/lock0lock.h 2013-08-14 03:52:04 +0000
+++ storage/innobase/include/lock0lock.h 2014-02-12 18:07:20 +0000
@@ -459,6 +459,16 @@
459 by a read cursor */459 by a read cursor */
460 const read_view_t* view); /*!< in: consistent read view */460 const read_view_t* view); /*!< in: consistent read view */
461/*********************************************************************//**461/*********************************************************************//**
462Check if there are any locks (table or rec) against table.
463@return TRUE if locks exist */
464UNIV_INLINE
465ibool
466lock_table_has_locks(
467/*=================*/
468 const dict_table_t* table); /*!< in: check if there are any locks
469 held on records in this table or on the
470 table itself */
471/*********************************************************************//**
462Locks the specified database table in the mode given. If the lock cannot472Locks the specified database table in the mode given. If the lock cannot
463be granted immediately, the query thread is put to wait.473be granted immediately, the query thread is put to wait.
464@return DB_SUCCESS, DB_LOCK_WAIT, DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */474@return DB_SUCCESS, DB_LOCK_WAIT, DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */
465475
=== modified file 'storage/innobase/include/lock0lock.ic'
--- storage/innobase/include/lock0lock.ic 2013-06-10 20:29:41 +0000
+++ storage/innobase/include/lock0lock.ic 2014-02-12 18:07:20 +0000
@@ -119,3 +119,17 @@
119 FALSE)));119 FALSE)));
120 }120 }
121}121}
122
123/*******************************************************************//**
124Check if there are any locks (table or rec) against table.
125@return TRUE if table has either table or record locks. */
126UNIV_INLINE
127ibool
128lock_table_has_locks(
129/*=================*/
130 const dict_table_t* table) /*!< in: check if there are any locks
131 held on records in this table or on the
132 table itself */
133{
134 return(UT_LIST_GET_LEN(table->locks) > 0);
135}
122136
=== modified file 'storage/innobase/lock/lock0lock.c'
--- storage/innobase/lock/lock0lock.c 2013-10-17 07:44:31 +0000
+++ storage/innobase/lock/lock0lock.c 2014-02-12 18:07:20 +0000
@@ -3832,6 +3832,8 @@
3832 trx = lock->trx;3832 trx = lock->trx;
3833 table = lock->un_member.tab_lock.table;3833 table = lock->un_member.tab_lock.table;
38343834
3835 ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
3836
3835 /* Remove the table from the transaction's AUTOINC vector, if3837 /* Remove the table from the transaction's AUTOINC vector, if
3836 the lock that is being release is an AUTOINC lock. */3838 the lock that is being release is an AUTOINC lock. */
3837 if (lock_get_mode(lock) == LOCK_AUTO_INC) {3839 if (lock_get_mode(lock) == LOCK_AUTO_INC) {

Subscribers

People subscribed via source and target branches