Skip to content

Commit f7d6fa6

Browse files
author
konstantin@mysql.com
committed
A fix for Bug#7209 "Client error with "Access Denied" on updates
when high concurrency": remove HASH::current_record and make it an external search parameter, so that it can not be the cause of a race condition under high concurrent load. The bug was in a race condition in table_hash_search, when column_priv_hash.current_record was overwritten simultaneously by multiple threads, causing the search for a suitable grant record to fail. No test case as the bug is repeatable only under concurrent load.
1 parent ec753ef commit f7d6fa6

File tree

7 files changed

+79
-50
lines changed

7 files changed

+79
-50
lines changed

include/hash.h

+10-4
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,17 @@ typedef void (*hash_free_key)(void *);
3333

3434
typedef struct st_hash {
3535
uint key_offset,key_length; /* Length of key if const length */
36-
uint records,blength,current_record;
36+
uint records, blength;
3737
uint flags;
3838
DYNAMIC_ARRAY array; /* Place for hash_keys */
3939
hash_get_key get_key;
4040
void (*free)(void *);
4141
CHARSET_INFO *charset;
4242
} HASH;
4343

44+
/* A search iterator state */
45+
typedef uint HASH_SEARCH_STATE;
46+
4447
#define hash_init(A,B,C,D,E,F,G,H) _hash_init(A,B,C,D,E,F,G, H CALLER_INFO)
4548
my_bool _hash_init(HASH *hash, CHARSET_INFO *charset,
4649
uint default_array_elements, uint key_offset,
@@ -49,12 +52,15 @@ my_bool _hash_init(HASH *hash, CHARSET_INFO *charset,
4952
void hash_free(HASH *tree);
5053
void my_hash_reset(HASH *hash);
5154
byte *hash_element(HASH *hash,uint idx);
52-
gptr hash_search(HASH *info,const byte *key,uint length);
53-
gptr hash_next(HASH *info,const byte *key,uint length);
55+
gptr hash_search(const HASH *info, const byte *key, uint length);
56+
gptr hash_first(const HASH *info, const byte *key, uint length,
57+
HASH_SEARCH_STATE *state);
58+
gptr hash_next(const HASH *info, const byte *key, uint length,
59+
HASH_SEARCH_STATE *state);
5460
my_bool my_hash_insert(HASH *info,const byte *data);
5561
my_bool hash_delete(HASH *hash,byte *record);
5662
my_bool hash_update(HASH *hash,byte *record,byte *old_key,uint old_key_length);
57-
void hash_replace(HASH *hash, uint idx, byte *new_row);
63+
void hash_replace(HASH *hash, HASH_SEARCH_STATE *state, byte *new_row);
5864
my_bool hash_check(HASH *hash); /* Only in debug library */
5965

6066
#define hash_clear(H) bzero((char*) (H),sizeof(*(H)))

mysys/hash.c

+35-25
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ typedef struct st_hash_info {
3636

3737
static uint hash_mask(uint hashnr,uint buffmax,uint maxlength);
3838
static void movelink(HASH_LINK *array,uint pos,uint next_link,uint newlink);
39-
static int hashcmp(HASH *hash,HASH_LINK *pos,const byte *key,uint length);
39+
static int hashcmp(const HASH *hash, HASH_LINK *pos, const byte *key,
40+
uint length);
4041

41-
static uint calc_hash(HASH *hash,const byte *key,uint length)
42+
static uint calc_hash(const HASH *hash, const byte *key, uint length)
4243
{
4344
ulong nr1=1, nr2=4;
4445
hash->charset->coll->hash_sort(hash->charset,(uchar*) key,length,&nr1,&nr2);
@@ -63,7 +64,6 @@ _hash_init(HASH *hash,CHARSET_INFO *charset,
6364
hash->key_offset=key_offset;
6465
hash->key_length=key_length;
6566
hash->blength=1;
66-
hash->current_record= NO_RECORD; /* For the future */
6767
hash->get_key=get_key;
6868
hash->free=free_element;
6969
hash->flags=flags;
@@ -135,7 +135,6 @@ void my_hash_reset(HASH *hash)
135135
reset_dynamic(&hash->array);
136136
/* Set row pointers so that the hash can be reused at once */
137137
hash->blength= 1;
138-
hash->current_record= NO_RECORD;
139138
DBUG_VOID_RETURN;
140139
}
141140

@@ -147,7 +146,8 @@ void my_hash_reset(HASH *hash)
147146
*/
148147

149148
static inline char*
150-
hash_key(HASH *hash,const byte *record,uint *length,my_bool first)
149+
hash_key(const HASH *hash, const byte *record, uint *length,
150+
my_bool first)
151151
{
152152
if (hash->get_key)
153153
return (*hash->get_key)(record,length,first);
@@ -163,8 +163,8 @@ static uint hash_mask(uint hashnr,uint buffmax,uint maxlength)
163163
return (hashnr & ((buffmax >> 1) -1));
164164
}
165165

166-
static uint hash_rec_mask(HASH *hash,HASH_LINK *pos,uint buffmax,
167-
uint maxlength)
166+
static uint hash_rec_mask(const HASH *hash, HASH_LINK *pos,
167+
uint buffmax, uint maxlength)
168168
{
169169
uint length;
170170
byte *key= (byte*) hash_key(hash,pos->data,&length,0);
@@ -186,14 +186,25 @@ unsigned int rec_hashnr(HASH *hash,const byte *record)
186186
}
187187

188188

189-
/* Search after a record based on a key */
190-
/* Sets info->current_ptr to found record */
189+
gptr hash_search(const HASH *hash, const byte *key, uint length)
190+
{
191+
HASH_SEARCH_STATE state;
192+
return hash_first(hash, key, length, &state);
193+
}
194+
195+
/*
196+
Search after a record based on a key
197+
198+
NOTE
199+
Assigns the number of the found record to HASH_SEARCH_STATE state
200+
*/
191201

192-
gptr hash_search(HASH *hash,const byte *key,uint length)
202+
gptr hash_first(const HASH *hash, const byte *key, uint length,
203+
HASH_SEARCH_STATE *current_record)
193204
{
194205
HASH_LINK *pos;
195206
uint flag,idx;
196-
DBUG_ENTER("hash_search");
207+
DBUG_ENTER("hash_first");
197208

198209
flag=1;
199210
if (hash->records)
@@ -206,7 +217,7 @@ gptr hash_search(HASH *hash,const byte *key,uint length)
206217
if (!hashcmp(hash,pos,key,length))
207218
{
208219
DBUG_PRINT("exit",("found key at %d",idx));
209-
hash->current_record= idx;
220+
*current_record= idx;
210221
DBUG_RETURN (pos->data);
211222
}
212223
if (flag)
@@ -218,31 +229,32 @@ gptr hash_search(HASH *hash,const byte *key,uint length)
218229
}
219230
while ((idx=pos->next) != NO_RECORD);
220231
}
221-
hash->current_record= NO_RECORD;
232+
*current_record= NO_RECORD;
222233
DBUG_RETURN(0);
223234
}
224235

225236
/* Get next record with identical key */
226237
/* Can only be called if previous calls was hash_search */
227238

228-
gptr hash_next(HASH *hash,const byte *key,uint length)
239+
gptr hash_next(const HASH *hash, const byte *key, uint length,
240+
HASH_SEARCH_STATE *current_record)
229241
{
230242
HASH_LINK *pos;
231243
uint idx;
232244

233-
if (hash->current_record != NO_RECORD)
245+
if (*current_record != NO_RECORD)
234246
{
235247
HASH_LINK *data=dynamic_element(&hash->array,0,HASH_LINK*);
236-
for (idx=data[hash->current_record].next; idx != NO_RECORD ; idx=pos->next)
248+
for (idx=data[*current_record].next; idx != NO_RECORD ; idx=pos->next)
237249
{
238250
pos=data+idx;
239251
if (!hashcmp(hash,pos,key,length))
240252
{
241-
hash->current_record= idx;
253+
*current_record= idx;
242254
return pos->data;
243255
}
244256
}
245-
hash->current_record=NO_RECORD;
257+
*current_record= NO_RECORD;
246258
}
247259
return 0;
248260
}
@@ -282,7 +294,8 @@ static void movelink(HASH_LINK *array,uint find,uint next_link,uint newlink)
282294
> 0 key of record > key
283295
*/
284296

285-
static int hashcmp(HASH *hash,HASH_LINK *pos,const byte *key,uint length)
297+
static int hashcmp(const HASH *hash, HASH_LINK *pos, const byte *key,
298+
uint length)
286299
{
287300
uint rec_keylength;
288301
byte *rec_key= (byte*) hash_key(hash,pos->data,&rec_keylength,1);
@@ -308,7 +321,6 @@ my_bool my_hash_insert(HASH *info,const byte *record)
308321
if (!(empty=(HASH_LINK*) alloc_dynamic(&info->array)))
309322
return(TRUE); /* No more memory */
310323

311-
info->current_record= NO_RECORD;
312324
data=dynamic_element(&info->array,0,HASH_LINK*);
313325
halfbuff= info->blength >> 1;
314326

@@ -451,7 +463,6 @@ my_bool hash_delete(HASH *hash,byte *record)
451463
}
452464

453465
if ( --(hash->records) < hash->blength >> 1) hash->blength>>=1;
454-
hash->current_record= NO_RECORD;
455466
lastpos=data+hash->records;
456467

457468
/* Remove link to record */
@@ -544,7 +555,6 @@ my_bool hash_update(HASH *hash,byte *record,byte *old_key,uint old_key_length)
544555
if ((idx=pos->next) == NO_RECORD)
545556
DBUG_RETURN(1); /* Not found in links */
546557
}
547-
hash->current_record= NO_RECORD;
548558
org_link= *pos;
549559
empty=idx;
550560

@@ -594,10 +604,10 @@ byte *hash_element(HASH *hash,uint idx)
594604
isn't changed
595605
*/
596606

597-
void hash_replace(HASH *hash, uint idx, byte *new_row)
607+
void hash_replace(HASH *hash, HASH_SEARCH_STATE *current_record, byte *new_row)
598608
{
599-
if (idx != NO_RECORD) /* Safety */
600-
dynamic_element(&hash->array,idx,HASH_LINK*)->data=new_row;
609+
if (*current_record != NO_RECORD) /* Safety */
610+
dynamic_element(&hash->array, *current_record, HASH_LINK*)->data= new_row;
601611
}
602612

603613

mysys/testhash.c

+5-4
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ static int do_test()
7474
bzero((char*) key1,sizeof(key1[0])*1000);
7575

7676
printf("- Creating hash\n");
77-
if (hash_init(&hash,recant/2,0,6,0,free_record,0))
77+
if (hash_init(&hash, default_charset_info, recant/2, 0, 6, 0, free_record, 0))
7878
goto err;
7979
printf("- Writing records:\n");
8080

@@ -172,15 +172,16 @@ static int do_test()
172172
break;
173173
if (key1[j] > 1)
174174
{
175+
HASH_SEARCH_STATE state;
175176
printf("- Testing identical read\n");
176177
sprintf(key,"%6d",j);
177178
pos=1;
178-
if (!(recpos=hash_search(&hash,key,0)))
179+
if (!(recpos= hash_first(&hash, key, 0, &state)))
179180
{
180181
printf("can't find key1: \"%s\"\n",key);
181182
goto err;
182183
}
183-
while (hash_next(&hash,key,0) && pos < (ulong) (key1[j]+10))
184+
while (hash_next(&hash, key, 0, &state) && pos < (ulong) (key1[j]+10))
184185
pos++;
185186
if (pos != (ulong) key1[j])
186187
{
@@ -189,7 +190,7 @@ static int do_test()
189190
}
190191
}
191192
printf("- Creating output heap-file 2\n");
192-
if (hash_init(&hash2,hash.records,0,0,hash2_key,free_record,0))
193+
if (hash_init(&hash2, default_charset_info, hash.records, 0, 0, hash2_key, free_record,0))
193194
goto err;
194195

195196
printf("- Copying and removing records\n");

sql/lock.cc

+3-2
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,7 @@ int lock_table_name(THD *thd, TABLE_LIST *table_list)
641641
char key[MAX_DBKEY_LENGTH];
642642
char *db= table_list->db;
643643
uint key_length;
644+
HASH_SEARCH_STATE state;
644645
DBUG_ENTER("lock_table_name");
645646
DBUG_PRINT("enter",("db: %s name: %s", db, table_list->real_name));
646647

@@ -651,9 +652,9 @@ int lock_table_name(THD *thd, TABLE_LIST *table_list)
651652

652653

653654
/* Only insert the table if we haven't insert it already */
654-
for (table=(TABLE*) hash_search(&open_cache,(byte*) key,key_length) ;
655+
for (table=(TABLE*) hash_first(&open_cache, (byte*)key, key_length, &state);
655656
table ;
656-
table = (TABLE*) hash_next(&open_cache,(byte*) key,key_length))
657+
table = (TABLE*) hash_next(&open_cache, (byte*)key, key_length, &state))
657658
if (table->in_use == thd)
658659
DBUG_RETURN(0);
659660

sql/sql_acl.cc

+5-4
Original file line numberDiff line numberDiff line change
@@ -1988,14 +1988,15 @@ static GRANT_TABLE *table_hash_search(const char *host,const char* ip,
19881988
char helping [NAME_LEN*2+USERNAME_LENGTH+3];
19891989
uint len;
19901990
GRANT_TABLE *grant_table,*found=0;
1991+
HASH_SEARCH_STATE state;
19911992

19921993
len = (uint) (strmov(strmov(strmov(helping,user)+1,db)+1,tname)-helping)+ 1;
1993-
for (grant_table=(GRANT_TABLE*) hash_search(&column_priv_hash,
1994-
(byte*) helping,
1995-
len) ;
1994+
for (grant_table=(GRANT_TABLE*) hash_first(&column_priv_hash,
1995+
(byte*) helping,
1996+
len, &state) ;
19961997
grant_table ;
19971998
grant_table= (GRANT_TABLE*) hash_next(&column_priv_hash,(byte*) helping,
1998-
len))
1999+
len, &state))
19992000
{
20002001
if (exact)
20012002
{

sql/sql_base.cc

+15-7
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,7 @@ TABLE *open_table(THD *thd,const char *db,const char *table_name,
799799
reg1 TABLE *table;
800800
char key[MAX_DBKEY_LENGTH];
801801
uint key_length;
802+
HASH_SEARCH_STATE state;
802803
DBUG_ENTER("open_table");
803804

804805
/* find a unused table in the open table cache */
@@ -863,9 +864,11 @@ TABLE *open_table(THD *thd,const char *db,const char *table_name,
863864
/* close handler tables which are marked for flush */
864865
mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE, TRUE);
865866

866-
for (table=(TABLE*) hash_search(&open_cache,(byte*) key,key_length) ;
867+
for (table= (TABLE*) hash_first(&open_cache, (byte*) key, key_length,
868+
&state);
867869
table && table->in_use ;
868-
table = (TABLE*) hash_next(&open_cache,(byte*) key,key_length))
870+
table= (TABLE*) hash_next(&open_cache, (byte*) key, key_length,
871+
&state))
869872
{
870873
if (table->version != refresh_version)
871874
{
@@ -1236,12 +1239,14 @@ bool table_is_used(TABLE *table, bool wait_for_name_lock)
12361239
{
12371240
do
12381241
{
1242+
HASH_SEARCH_STATE state;
12391243
char *key= table->table_cache_key;
12401244
uint key_length=table->key_length;
1241-
for (TABLE *search=(TABLE*) hash_search(&open_cache,
1242-
(byte*) key,key_length) ;
1245+
for (TABLE *search= (TABLE*) hash_first(&open_cache, (byte*) key,
1246+
key_length, &state);
12431247
search ;
1244-
search = (TABLE*) hash_next(&open_cache,(byte*) key,key_length))
1248+
search= (TABLE*) hash_next(&open_cache, (byte*) key,
1249+
key_length, &state))
12451250
{
12461251
if (search->locked_by_flush ||
12471252
search->locked_by_name && wait_for_name_lock ||
@@ -2958,11 +2963,14 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name,
29582963
key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
29592964
for (;;)
29602965
{
2966+
HASH_SEARCH_STATE state;
29612967
result= signalled= 0;
29622968

2963-
for (table=(TABLE*) hash_search(&open_cache,(byte*) key,key_length) ;
2969+
for (table= (TABLE*) hash_first(&open_cache, (byte*) key, key_length,
2970+
&state);
29642971
table;
2965-
table = (TABLE*) hash_next(&open_cache,(byte*) key,key_length))
2972+
table= (TABLE*) hash_next(&open_cache, (byte*) key, key_length,
2973+
&state))
29662974
{
29672975
THD *in_use;
29682976
table->version=0L; /* Free when thread is ready */

sql/sql_cache.cc

+6-4
Original file line numberDiff line numberDiff line change
@@ -2873,6 +2873,7 @@ my_bool Query_cache::move_by_type(byte **border,
28732873
}
28742874
case Query_cache_block::TABLE:
28752875
{
2876+
HASH_SEARCH_STATE record_idx;
28762877
DBUG_PRINT("qcache", ("block 0x%lx TABLE", (ulong) block));
28772878
if (*border == 0)
28782879
break;
@@ -2890,7 +2891,7 @@ my_bool Query_cache::move_by_type(byte **border,
28902891
byte *key;
28912892
uint key_length;
28922893
key=query_cache_table_get_key((byte*) block, &key_length, 0);
2893-
hash_search(&tables, (byte*) key, key_length);
2894+
hash_first(&tables, (byte*) key, key_length, &record_idx);
28942895

28952896
block->destroy();
28962897
new_block->init(len);
@@ -2924,14 +2925,15 @@ my_bool Query_cache::move_by_type(byte **border,
29242925
/* Fix pointer to table name */
29252926
new_block->table()->table(new_block->table()->db() + tablename_offset);
29262927
/* Fix hash to point at moved block */
2927-
hash_replace(&tables, tables.current_record, (byte*) new_block);
2928+
hash_replace(&tables, &record_idx, (byte*) new_block);
29282929

29292930
DBUG_PRINT("qcache", ("moved %lu bytes to 0x%lx, new gap at 0x%lx",
29302931
len, (ulong) new_block, (ulong) *border));
29312932
break;
29322933
}
29332934
case Query_cache_block::QUERY:
29342935
{
2936+
HASH_SEARCH_STATE record_idx;
29352937
DBUG_PRINT("qcache", ("block 0x%lx QUERY", (ulong) block));
29362938
if (*border == 0)
29372939
break;
@@ -2949,7 +2951,7 @@ my_bool Query_cache::move_by_type(byte **border,
29492951
byte *key;
29502952
uint key_length;
29512953
key=query_cache_query_get_key((byte*) block, &key_length, 0);
2952-
hash_search(&queries, (byte*) key, key_length);
2954+
hash_first(&queries, (byte*) key, key_length, &record_idx);
29532955
// Move table of used tables
29542956
memmove((char*) new_block->table(0), (char*) block->table(0),
29552957
ALIGN_SIZE(n_tables*sizeof(Query_cache_block_table)));
@@ -3017,7 +3019,7 @@ my_bool Query_cache::move_by_type(byte **border,
30173019
net->query_cache_query= (gptr) new_block;
30183020
}
30193021
/* Fix hash to point at moved block */
3020-
hash_replace(&queries, queries.current_record, (byte*) new_block);
3022+
hash_replace(&queries, &record_idx, (byte*) new_block);
30213023
DBUG_PRINT("qcache", ("moved %lu bytes to 0x%lx, new gap at 0x%lx",
30223024
len, (ulong) new_block, (ulong) *border));
30233025
break;

0 commit comments

Comments
 (0)