1 | /* Copyright (c) 2000, 2012, Oracle and/or its affiliates. |
2 | Copyright (c) 2010, 2011 Monty Program Ab |
3 | Copyright (C) 2013 Sergey Vojtovich and MariaDB Foundation |
4 | |
5 | This program is free software; you can redistribute it and/or modify |
6 | it under the terms of the GNU General Public License as published by |
7 | the Free Software Foundation; version 2 of the License. |
8 | |
9 | This program is distributed in the hope that it will be useful, |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | GNU General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU General Public License |
15 | along with this program; if not, write to the Free Software |
16 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
17 | |
18 | /** |
19 | @file |
20 | Table definition cache and table cache implementation. |
21 | |
22 | Table definition cache actions: |
23 | - add new TABLE_SHARE object to cache (tdc_acquire_share()) |
24 | - acquire TABLE_SHARE object from cache (tdc_acquire_share()) |
25 | - release TABLE_SHARE object to cache (tdc_release_share()) |
26 | - purge unused TABLE_SHARE objects from cache (tdc_purge()) |
27 | - remove TABLE_SHARE object from cache (tdc_remove_table()) |
28 | - get number of TABLE_SHARE objects in cache (tdc_records()) |
29 | |
30 | Table cache actions: |
31 | - add new TABLE object to cache (tc_add_table()) |
32 | - acquire TABLE object from cache (tc_acquire_table()) |
33 | - release TABLE object to cache (tc_release_table()) |
34 | - purge unused TABLE objects from cache (tc_purge()) |
35 | - purge unused TABLE objects of a table from cache (tdc_remove_table()) |
36 | - get number of TABLE objects in cache (tc_records()) |
37 | |
38 | Dependencies: |
39 | - close_cached_tables(): flush tables on shutdown |
40 | - alloc_table_share() |
41 | - free_table_share() |
42 | |
43 | Table cache invariants: |
44 | - TABLE_SHARE::free_tables shall not contain objects with TABLE::in_use != 0 |
45 | - TABLE_SHARE::free_tables shall not receive new objects if |
46 | TABLE_SHARE::tdc.flushed is true |
47 | */ |
48 | |
49 | #include "mariadb.h" |
50 | #include "lf.h" |
51 | #include "table.h" |
52 | #include "sql_base.h" |
53 | |
54 | |
55 | /** Configuration. */ |
56 | ulong tdc_size; /**< Table definition cache threshold for LRU eviction. */ |
57 | ulong tc_size; /**< Table cache threshold for LRU eviction. */ |
58 | uint32 tc_instances; |
59 | uint32 tc_active_instances= 1; |
60 | static uint32 tc_contention_warning_reported; |
61 | |
62 | /** Data collections. */ |
63 | static LF_HASH tdc_hash; /**< Collection of TABLE_SHARE objects. */ |
64 | /** Collection of unused TABLE_SHARE objects. */ |
65 | static |
66 | I_P_List <TDC_element, |
67 | I_P_List_adapter<TDC_element, &TDC_element::next, &TDC_element::prev>, |
68 | I_P_List_null_counter, |
69 | I_P_List_fast_push_back<TDC_element> > unused_shares; |
70 | |
71 | static tdc_version_t tdc_version; /* Increments on each reload */ |
72 | static bool tdc_inited; |
73 | |
74 | |
75 | /** |
76 | Protects unused shares list. |
77 | |
78 | TDC_element::prev |
79 | TDC_element::next |
80 | unused_shares |
81 | */ |
82 | |
83 | static mysql_mutex_t LOCK_unused_shares; |
84 | |
85 | #ifdef HAVE_PSI_INTERFACE |
86 | static PSI_mutex_key key_LOCK_unused_shares, key_TABLE_SHARE_LOCK_table_share, |
87 | key_LOCK_table_cache; |
88 | static PSI_mutex_info all_tc_mutexes[]= |
89 | { |
90 | { &key_LOCK_unused_shares, "LOCK_unused_shares" , PSI_FLAG_GLOBAL }, |
91 | { &key_TABLE_SHARE_LOCK_table_share, "TABLE_SHARE::tdc.LOCK_table_share" , 0 }, |
92 | { &key_LOCK_table_cache, "LOCK_table_cache" , 0 } |
93 | }; |
94 | |
95 | static PSI_cond_key key_TABLE_SHARE_COND_release; |
96 | static PSI_cond_info all_tc_conds[]= |
97 | { |
98 | { &key_TABLE_SHARE_COND_release, "TABLE_SHARE::tdc.COND_release" , 0 } |
99 | }; |
100 | #endif |
101 | |
102 | |
103 | static int fix_thd_pins(THD *thd) |
104 | { |
105 | return thd->tdc_hash_pins ? 0 : |
106 | (thd->tdc_hash_pins= lf_hash_get_pins(&tdc_hash)) == 0; |
107 | } |
108 | |
109 | |
110 | /* |
111 | Auxiliary routines for manipulating with per-share all/unused lists |
112 | and tc_count counter. |
113 | Responsible for preserving invariants between those lists, counter |
114 | and TABLE::in_use member. |
115 | In fact those routines implement sort of implicit table cache as |
116 | part of table definition cache. |
117 | */ |
118 | |
119 | struct Table_cache_instance |
120 | { |
121 | /** |
122 | Protects free_tables (TABLE::global_free_next and TABLE::global_free_prev), |
123 | records, Share_free_tables::List (TABLE::prev and TABLE::next), |
124 | TABLE::in_use. |
125 | */ |
126 | mysql_mutex_t LOCK_table_cache; |
127 | I_P_List <TABLE, I_P_List_adapter<TABLE, &TABLE::global_free_next, |
128 | &TABLE::global_free_prev>, |
129 | I_P_List_null_counter, I_P_List_fast_push_back<TABLE> > |
130 | free_tables; |
131 | ulong records; |
132 | uint mutex_waits; |
133 | uint mutex_nowaits; |
134 | /** Avoid false sharing between instances */ |
135 | char pad[CPU_LEVEL1_DCACHE_LINESIZE]; |
136 | |
137 | Table_cache_instance(): records(0), mutex_waits(0), mutex_nowaits(0) |
138 | { |
139 | mysql_mutex_init(key_LOCK_table_cache, &LOCK_table_cache, |
140 | MY_MUTEX_INIT_FAST); |
141 | } |
142 | |
143 | ~Table_cache_instance() |
144 | { |
145 | mysql_mutex_destroy(&LOCK_table_cache); |
146 | DBUG_ASSERT(free_tables.is_empty()); |
147 | DBUG_ASSERT(records == 0); |
148 | } |
149 | |
150 | /** |
151 | Lock table cache mutex and check contention. |
152 | |
153 | Instance is considered contested if more than 20% of mutex acquisiotions |
154 | can't be served immediately. Up to 100 000 probes may be performed to avoid |
155 | instance activation on short sporadic peaks. 100 000 is estimated maximum |
156 | number of queries one instance can serve in one second. |
157 | |
158 | These numbers work well on a 2 socket / 20 core / 40 threads Intel Broadwell |
159 | system, that is expected number of instances is activated within reasonable |
160 | warmup time. It may have to be adjusted for other systems. |
161 | |
162 | Only TABLE object acquistion is instrumented. We intentionally avoid this |
163 | overhead on TABLE object release. All other table cache mutex acquistions |
164 | are considered out of hot path and are not instrumented either. |
165 | */ |
166 | void lock_and_check_contention(uint32 n_instances, uint32 instance) |
167 | { |
168 | if (mysql_mutex_trylock(&LOCK_table_cache)) |
169 | { |
170 | mysql_mutex_lock(&LOCK_table_cache); |
171 | if (++mutex_waits == 20000) |
172 | { |
173 | if (n_instances < tc_instances) |
174 | { |
175 | if (my_atomic_cas32_weak_explicit((int32*) &tc_active_instances, |
176 | (int32*) &n_instances, |
177 | (int32) n_instances + 1, |
178 | MY_MEMORY_ORDER_RELAXED, |
179 | MY_MEMORY_ORDER_RELAXED)) |
180 | { |
181 | sql_print_information("Detected table cache mutex contention at instance %d: " |
182 | "%d%% waits. Additional table cache instance " |
183 | "activated. Number of instances after " |
184 | "activation: %d." , |
185 | instance + 1, |
186 | mutex_waits * 100 / (mutex_nowaits + mutex_waits), |
187 | n_instances + 1); |
188 | } |
189 | } |
190 | else if (!my_atomic_fas32_explicit((int32*) &tc_contention_warning_reported, |
191 | 1, MY_MEMORY_ORDER_RELAXED)) |
192 | { |
193 | sql_print_warning("Detected table cache mutex contention at instance %d: " |
194 | "%d%% waits. Additional table cache instance " |
195 | "cannot be activated: consider raising " |
196 | "table_open_cache_instances. Number of active " |
197 | "instances: %d." , |
198 | instance + 1, |
199 | mutex_waits * 100 / (mutex_nowaits + mutex_waits), |
200 | n_instances); |
201 | } |
202 | mutex_waits= 0; |
203 | mutex_nowaits= 0; |
204 | } |
205 | } |
206 | else if (++mutex_nowaits == 80000) |
207 | { |
208 | mutex_waits= 0; |
209 | mutex_nowaits= 0; |
210 | } |
211 | } |
212 | }; |
213 | |
214 | |
215 | static Table_cache_instance *tc; |
216 | |
217 | |
218 | static void intern_close_table(TABLE *table) |
219 | { |
220 | delete table->triggers; |
221 | DBUG_ASSERT(table->file); |
222 | closefrm(table); |
223 | tdc_release_share(table->s); |
224 | my_free(table); |
225 | } |
226 | |
227 | |
228 | /** |
229 | Get number of TABLE objects (used and unused) in table cache. |
230 | */ |
231 | |
232 | uint tc_records(void) |
233 | { |
234 | ulong total= 0; |
235 | for (ulong i= 0; i < tc_instances; i++) |
236 | { |
237 | mysql_mutex_lock(&tc[i].LOCK_table_cache); |
238 | total+= tc[i].records; |
239 | mysql_mutex_unlock(&tc[i].LOCK_table_cache); |
240 | } |
241 | return total; |
242 | } |
243 | |
244 | |
245 | /** |
246 | Remove TABLE object from table cache. |
247 | */ |
248 | |
249 | static void tc_remove_table(TABLE *table) |
250 | { |
251 | TDC_element *element= table->s->tdc; |
252 | |
253 | mysql_mutex_lock(&element->LOCK_table_share); |
254 | /* Wait for MDL deadlock detector to complete traversing tdc.all_tables. */ |
255 | while (element->all_tables_refs) |
256 | mysql_cond_wait(&element->COND_release, &element->LOCK_table_share); |
257 | element->all_tables.remove(table); |
258 | mysql_mutex_unlock(&element->LOCK_table_share); |
259 | |
260 | intern_close_table(table); |
261 | } |
262 | |
263 | |
264 | static void tc_remove_all_unused_tables(TDC_element *element, |
265 | Share_free_tables::List *purge_tables, |
266 | bool mark_flushed) |
267 | { |
268 | TABLE *table; |
269 | |
270 | /* |
271 | Mark share flushed in order to ensure that it gets |
272 | automatically deleted once it is no longer referenced. |
273 | |
274 | Note that code in TABLE_SHARE::wait_for_old_version() assumes that |
275 | marking share flushed is followed by purge of unused table |
276 | shares. |
277 | */ |
278 | if (mark_flushed) |
279 | element->flushed= true; |
280 | for (ulong i= 0; i < tc_instances; i++) |
281 | { |
282 | mysql_mutex_lock(&tc[i].LOCK_table_cache); |
283 | while ((table= element->free_tables[i].list.pop_front())) |
284 | { |
285 | tc[i].records--; |
286 | tc[i].free_tables.remove(table); |
287 | DBUG_ASSERT(element->all_tables_refs == 0); |
288 | element->all_tables.remove(table); |
289 | purge_tables->push_front(table); |
290 | } |
291 | mysql_mutex_unlock(&tc[i].LOCK_table_cache); |
292 | } |
293 | } |
294 | |
295 | |
296 | /** |
297 | Free all unused TABLE objects. |
298 | |
299 | While locked: |
300 | - remove unused objects from TABLE_SHARE::tdc.free_tables and |
301 | TABLE_SHARE::tdc.all_tables |
302 | - decrement tc_count |
303 | |
304 | While unlocked: |
305 | - free resources related to unused objects |
306 | |
307 | @note This is called by 'handle_manager' when one wants to |
308 | periodicly flush all not used tables. |
309 | */ |
310 | |
311 | struct tc_purge_arg |
312 | { |
313 | Share_free_tables::List purge_tables; |
314 | bool mark_flushed; |
315 | }; |
316 | |
317 | |
318 | static my_bool tc_purge_callback(TDC_element *element, tc_purge_arg *arg) |
319 | { |
320 | mysql_mutex_lock(&element->LOCK_table_share); |
321 | tc_remove_all_unused_tables(element, &arg->purge_tables, arg->mark_flushed); |
322 | mysql_mutex_unlock(&element->LOCK_table_share); |
323 | return FALSE; |
324 | } |
325 | |
326 | |
327 | void tc_purge(bool mark_flushed) |
328 | { |
329 | tc_purge_arg argument; |
330 | TABLE *table; |
331 | |
332 | argument.mark_flushed= mark_flushed; |
333 | tdc_iterate(0, (my_hash_walk_action) tc_purge_callback, &argument); |
334 | while ((table= argument.purge_tables.pop_front())) |
335 | intern_close_table(table); |
336 | } |
337 | |
338 | |
339 | /** |
340 | Add new TABLE object to table cache. |
341 | |
342 | @pre TABLE object is used by caller. |
343 | |
344 | Added object cannot be evicted or acquired. |
345 | |
346 | While locked: |
347 | - add object to TABLE_SHARE::tdc.all_tables |
348 | - increment tc_count |
349 | - evict LRU object from table cache if we reached threshold |
350 | |
351 | While unlocked: |
352 | - free evicted object |
353 | */ |
354 | |
355 | void tc_add_table(THD *thd, TABLE *table) |
356 | { |
357 | uint32 i= thd->thread_id % my_atomic_load32_explicit((int32*) &tc_active_instances, |
358 | MY_MEMORY_ORDER_RELAXED); |
359 | TABLE *LRU_table= 0; |
360 | TDC_element *element= table->s->tdc; |
361 | |
362 | DBUG_ASSERT(table->in_use == thd); |
363 | table->instance= i; |
364 | mysql_mutex_lock(&element->LOCK_table_share); |
365 | /* Wait for MDL deadlock detector to complete traversing tdc.all_tables. */ |
366 | while (element->all_tables_refs) |
367 | mysql_cond_wait(&element->COND_release, &element->LOCK_table_share); |
368 | element->all_tables.push_front(table); |
369 | mysql_mutex_unlock(&element->LOCK_table_share); |
370 | |
371 | mysql_mutex_lock(&tc[i].LOCK_table_cache); |
372 | if (tc[i].records == tc_size) |
373 | { |
374 | if ((LRU_table= tc[i].free_tables.pop_front())) |
375 | { |
376 | LRU_table->s->tdc->free_tables[i].list.remove(LRU_table); |
377 | /* Needed if MDL deadlock detector chimes in before tc_remove_table() */ |
378 | LRU_table->in_use= thd; |
379 | mysql_mutex_unlock(&tc[i].LOCK_table_cache); |
380 | /* Keep out of locked LOCK_table_cache */ |
381 | tc_remove_table(LRU_table); |
382 | } |
383 | else |
384 | { |
385 | tc[i].records++; |
386 | mysql_mutex_unlock(&tc[i].LOCK_table_cache); |
387 | } |
388 | /* Keep out of locked LOCK_table_cache */ |
389 | status_var_increment(thd->status_var.table_open_cache_overflows); |
390 | } |
391 | else |
392 | { |
393 | tc[i].records++; |
394 | mysql_mutex_unlock(&tc[i].LOCK_table_cache); |
395 | } |
396 | } |
397 | |
398 | |
399 | /** |
400 | Acquire TABLE object from table cache. |
401 | |
402 | @pre share must be protected against removal. |
403 | |
404 | Acquired object cannot be evicted or acquired again. |
405 | |
406 | @return TABLE object, or NULL if no unused objects. |
407 | */ |
408 | |
409 | static TABLE *tc_acquire_table(THD *thd, TDC_element *element) |
410 | { |
411 | uint32 n_instances= |
412 | my_atomic_load32_explicit((int32*) &tc_active_instances, |
413 | MY_MEMORY_ORDER_RELAXED); |
414 | uint32 i= thd->thread_id % n_instances; |
415 | TABLE *table; |
416 | |
417 | tc[i].lock_and_check_contention(n_instances, i); |
418 | table= element->free_tables[i].list.pop_front(); |
419 | if (table) |
420 | { |
421 | DBUG_ASSERT(!table->in_use); |
422 | table->in_use= thd; |
423 | /* The ex-unused table must be fully functional. */ |
424 | DBUG_ASSERT(table->db_stat && table->file); |
425 | /* The children must be detached from the table. */ |
426 | DBUG_ASSERT(!table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); |
427 | tc[i].free_tables.remove(table); |
428 | } |
429 | mysql_mutex_unlock(&tc[i].LOCK_table_cache); |
430 | return table; |
431 | } |
432 | |
433 | |
434 | /** |
435 | Release TABLE object to table cache. |
436 | |
437 | @pre object is used by caller. |
438 | |
439 | Released object may be evicted or acquired again. |
440 | |
441 | While locked: |
442 | - if object is marked for purge, decrement tc_count |
443 | - add object to TABLE_SHARE::tdc.free_tables |
444 | - evict LRU object from table cache if we reached threshold |
445 | |
446 | While unlocked: |
447 | - mark object not in use by any thread |
448 | - free evicted/purged object |
449 | |
450 | @note Another thread may mark share for purge any moment (even |
451 | after version check). It means to-be-purged object may go to |
452 | unused lists. This other thread is expected to call tc_purge(), |
453 | which is synchronized with us on TABLE_SHARE::tdc.LOCK_table_share. |
454 | |
455 | @return |
456 | @retval true object purged |
457 | @retval false object released |
458 | */ |
459 | |
460 | void tc_release_table(TABLE *table) |
461 | { |
462 | uint32 i= table->instance; |
463 | DBUG_ENTER("tc_release_table" ); |
464 | DBUG_ASSERT(table->in_use); |
465 | DBUG_ASSERT(table->file); |
466 | |
467 | mysql_mutex_lock(&tc[i].LOCK_table_cache); |
468 | if (table->needs_reopen() || table->s->tdc->flushed || |
469 | tc[i].records > tc_size) |
470 | { |
471 | tc[i].records--; |
472 | mysql_mutex_unlock(&tc[i].LOCK_table_cache); |
473 | tc_remove_table(table); |
474 | } |
475 | else |
476 | { |
477 | table->in_use= 0; |
478 | table->s->tdc->free_tables[i].list.push_front(table); |
479 | tc[i].free_tables.push_back(table); |
480 | mysql_mutex_unlock(&tc[i].LOCK_table_cache); |
481 | } |
482 | DBUG_VOID_RETURN; |
483 | } |
484 | |
485 | |
486 | static void tdc_assert_clean_share(TDC_element *element) |
487 | { |
488 | DBUG_ASSERT(element->share == 0); |
489 | DBUG_ASSERT(element->ref_count == 0); |
490 | DBUG_ASSERT(element->m_flush_tickets.is_empty()); |
491 | DBUG_ASSERT(element->all_tables.is_empty()); |
492 | #ifndef DBUG_OFF |
493 | for (ulong i= 0; i < tc_instances; i++) |
494 | DBUG_ASSERT(element->free_tables[i].list.is_empty()); |
495 | #endif |
496 | DBUG_ASSERT(element->all_tables_refs == 0); |
497 | DBUG_ASSERT(element->next == 0); |
498 | DBUG_ASSERT(element->prev == 0); |
499 | } |
500 | |
501 | |
502 | /** |
503 | Delete share from hash and free share object. |
504 | */ |
505 | |
506 | static void tdc_delete_share_from_hash(TDC_element *element) |
507 | { |
508 | THD *thd= current_thd; |
509 | LF_PINS *pins; |
510 | TABLE_SHARE *share; |
511 | DBUG_ENTER("tdc_delete_share_from_hash" ); |
512 | |
513 | mysql_mutex_assert_owner(&element->LOCK_table_share); |
514 | share= element->share; |
515 | DBUG_ASSERT(share); |
516 | element->share= 0; |
517 | PSI_CALL_release_table_share(share->m_psi); |
518 | share->m_psi= 0; |
519 | |
520 | if (!element->m_flush_tickets.is_empty()) |
521 | { |
522 | Wait_for_flush_list::Iterator it(element->m_flush_tickets); |
523 | Wait_for_flush *ticket; |
524 | while ((ticket= it++)) |
525 | (void) ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED); |
526 | |
527 | do |
528 | { |
529 | mysql_cond_wait(&element->COND_release, &element->LOCK_table_share); |
530 | } while (!element->m_flush_tickets.is_empty()); |
531 | } |
532 | |
533 | mysql_mutex_unlock(&element->LOCK_table_share); |
534 | |
535 | if (thd) |
536 | { |
537 | fix_thd_pins(thd); |
538 | pins= thd->tdc_hash_pins; |
539 | } |
540 | else |
541 | pins= lf_hash_get_pins(&tdc_hash); |
542 | |
543 | DBUG_ASSERT(pins); // What can we do about it? |
544 | tdc_assert_clean_share(element); |
545 | lf_hash_delete(&tdc_hash, pins, element->m_key, element->m_key_length); |
546 | if (!thd) |
547 | lf_hash_put_pins(pins); |
548 | free_table_share(share); |
549 | DBUG_VOID_RETURN; |
550 | } |
551 | |
552 | |
553 | /** |
554 | Prepeare table share for use with table definition cache. |
555 | */ |
556 | |
557 | static void lf_alloc_constructor(uchar *arg) |
558 | { |
559 | TDC_element *element= (TDC_element*) (arg + LF_HASH_OVERHEAD); |
560 | DBUG_ENTER("lf_alloc_constructor" ); |
561 | mysql_mutex_init(key_TABLE_SHARE_LOCK_table_share, |
562 | &element->LOCK_table_share, MY_MUTEX_INIT_FAST); |
563 | mysql_cond_init(key_TABLE_SHARE_COND_release, &element->COND_release, 0); |
564 | element->m_flush_tickets.empty(); |
565 | element->all_tables.empty(); |
566 | for (ulong i= 0; i < tc_instances; i++) |
567 | element->free_tables[i].list.empty(); |
568 | element->all_tables_refs= 0; |
569 | element->share= 0; |
570 | element->ref_count= 0; |
571 | element->next= 0; |
572 | element->prev= 0; |
573 | DBUG_VOID_RETURN; |
574 | } |
575 | |
576 | |
577 | /** |
578 | Release table definition cache specific resources of table share. |
579 | */ |
580 | |
581 | static void lf_alloc_destructor(uchar *arg) |
582 | { |
583 | TDC_element *element= (TDC_element*) (arg + LF_HASH_OVERHEAD); |
584 | DBUG_ENTER("lf_alloc_destructor" ); |
585 | tdc_assert_clean_share(element); |
586 | mysql_cond_destroy(&element->COND_release); |
587 | mysql_mutex_destroy(&element->LOCK_table_share); |
588 | DBUG_VOID_RETURN; |
589 | } |
590 | |
591 | |
592 | static void tdc_hash_initializer(LF_HASH *, |
593 | TDC_element *element, LEX_STRING *key) |
594 | { |
595 | memcpy(element->m_key, key->str, key->length); |
596 | element->m_key_length= (uint)key->length; |
597 | tdc_assert_clean_share(element); |
598 | } |
599 | |
600 | |
601 | static uchar *tdc_hash_key(const TDC_element *element, size_t *length, |
602 | my_bool) |
603 | { |
604 | *length= element->m_key_length; |
605 | return (uchar*) element->m_key; |
606 | } |
607 | |
608 | |
609 | /** |
610 | Initialize table definition cache. |
611 | */ |
612 | |
613 | bool tdc_init(void) |
614 | { |
615 | DBUG_ENTER("tdc_init" ); |
616 | #ifdef HAVE_PSI_INTERFACE |
617 | mysql_mutex_register("sql" , all_tc_mutexes, array_elements(all_tc_mutexes)); |
618 | mysql_cond_register("sql" , all_tc_conds, array_elements(all_tc_conds)); |
619 | #endif |
620 | /* Extra instance is allocated to avoid false sharing */ |
621 | if (!(tc= new Table_cache_instance[tc_instances + 1])) |
622 | DBUG_RETURN(true); |
623 | tdc_inited= true; |
624 | mysql_mutex_init(key_LOCK_unused_shares, &LOCK_unused_shares, |
625 | MY_MUTEX_INIT_FAST); |
626 | tdc_version= 1L; /* Increments on each reload */ |
627 | lf_hash_init(&tdc_hash, sizeof(TDC_element) + |
628 | sizeof(Share_free_tables) * (tc_instances - 1), |
629 | LF_HASH_UNIQUE, 0, 0, |
630 | (my_hash_get_key) tdc_hash_key, |
631 | &my_charset_bin); |
632 | tdc_hash.alloc.constructor= lf_alloc_constructor; |
633 | tdc_hash.alloc.destructor= lf_alloc_destructor; |
634 | tdc_hash.initializer= (lf_hash_initializer) tdc_hash_initializer; |
635 | DBUG_RETURN(false); |
636 | } |
637 | |
638 | |
639 | /** |
640 | Notify table definition cache that process of shutting down server |
641 | has started so it has to keep number of TABLE and TABLE_SHARE objects |
642 | minimal in order to reduce number of references to pluggable engines. |
643 | */ |
644 | |
645 | void tdc_start_shutdown(void) |
646 | { |
647 | DBUG_ENTER("table_def_start_shutdown" ); |
648 | if (tdc_inited) |
649 | { |
650 | /* |
651 | Ensure that TABLE and TABLE_SHARE objects which are created for |
652 | tables that are open during process of plugins' shutdown are |
653 | immediately released. This keeps number of references to engine |
654 | plugins minimal and allows shutdown to proceed smoothly. |
655 | */ |
656 | tdc_size= 0; |
657 | tc_size= 0; |
658 | /* Free all cached but unused TABLEs and TABLE_SHAREs. */ |
659 | close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT); |
660 | } |
661 | DBUG_VOID_RETURN; |
662 | } |
663 | |
664 | |
665 | /** |
666 | Deinitialize table definition cache. |
667 | */ |
668 | |
669 | void tdc_deinit(void) |
670 | { |
671 | DBUG_ENTER("tdc_deinit" ); |
672 | if (tdc_inited) |
673 | { |
674 | tdc_inited= false; |
675 | lf_hash_destroy(&tdc_hash); |
676 | mysql_mutex_destroy(&LOCK_unused_shares); |
677 | delete [] tc; |
678 | } |
679 | DBUG_VOID_RETURN; |
680 | } |
681 | |
682 | |
683 | /** |
684 | Get number of cached table definitions. |
685 | |
686 | @return Number of cached table definitions |
687 | */ |
688 | |
689 | ulong tdc_records(void) |
690 | { |
691 | return my_atomic_load32_explicit(&tdc_hash.count, MY_MEMORY_ORDER_RELAXED); |
692 | } |
693 | |
694 | |
695 | void tdc_purge(bool all) |
696 | { |
697 | DBUG_ENTER("tdc_purge" ); |
698 | while (all || tdc_records() > tdc_size) |
699 | { |
700 | TDC_element *element; |
701 | |
702 | mysql_mutex_lock(&LOCK_unused_shares); |
703 | if (!(element= unused_shares.pop_front())) |
704 | { |
705 | mysql_mutex_unlock(&LOCK_unused_shares); |
706 | break; |
707 | } |
708 | |
709 | /* Concurrent thread may start using share again, reset prev and next. */ |
710 | element->prev= 0; |
711 | element->next= 0; |
712 | mysql_mutex_lock(&element->LOCK_table_share); |
713 | if (element->ref_count) |
714 | { |
715 | mysql_mutex_unlock(&element->LOCK_table_share); |
716 | mysql_mutex_unlock(&LOCK_unused_shares); |
717 | continue; |
718 | } |
719 | mysql_mutex_unlock(&LOCK_unused_shares); |
720 | |
721 | tdc_delete_share_from_hash(element); |
722 | } |
723 | DBUG_VOID_RETURN; |
724 | } |
725 | |
726 | |
727 | /** |
728 | Lock table share. |
729 | |
730 | Find table share with given db.table_name in table definition cache. Return |
731 | locked table share if found. |
732 | |
733 | Locked table share means: |
734 | - table share is protected against removal from table definition cache |
735 | - no other thread can acquire/release table share |
736 | |
737 | Caller is expected to unlock table share with tdc_unlock_share(). |
738 | |
739 | @retval 0 Share not found |
740 | @retval MY_ERRPTR OOM |
741 | @retval ptr Pointer to locked table share |
742 | */ |
743 | |
744 | TDC_element *tdc_lock_share(THD *thd, const char *db, const char *table_name) |
745 | { |
746 | TDC_element *element; |
747 | char key[MAX_DBKEY_LENGTH]; |
748 | |
749 | DBUG_ENTER("tdc_lock_share" ); |
750 | if (unlikely(fix_thd_pins(thd))) |
751 | DBUG_RETURN((TDC_element*) MY_ERRPTR); |
752 | |
753 | element= (TDC_element *) lf_hash_search(&tdc_hash, thd->tdc_hash_pins, |
754 | (uchar*) key, |
755 | tdc_create_key(key, db, table_name)); |
756 | if (element) |
757 | { |
758 | mysql_mutex_lock(&element->LOCK_table_share); |
759 | if (unlikely(!element->share || element->share->error)) |
760 | { |
761 | mysql_mutex_unlock(&element->LOCK_table_share); |
762 | element= 0; |
763 | } |
764 | lf_hash_search_unpin(thd->tdc_hash_pins); |
765 | } |
766 | |
767 | DBUG_RETURN(element); |
768 | } |
769 | |
770 | |
771 | /** |
772 | Unlock share locked by tdc_lock_share(). |
773 | */ |
774 | |
775 | void tdc_unlock_share(TDC_element *element) |
776 | { |
777 | DBUG_ENTER("tdc_unlock_share" ); |
778 | mysql_mutex_unlock(&element->LOCK_table_share); |
779 | DBUG_VOID_RETURN; |
780 | } |
781 | |
782 | |
783 | /* |
784 | Get TABLE_SHARE for a table. |
785 | |
786 | tdc_acquire_share() |
787 | thd Thread handle |
788 | tl Table that should be opened |
789 | flags operation: what to open table or view |
790 | out_table TABLE for the requested table |
791 | |
792 | IMPLEMENTATION |
793 | Get a table definition from the table definition cache. |
794 | If it doesn't exist, create a new from the table definition file. |
795 | |
796 | RETURN |
797 | 0 Error |
798 | # Share for table |
799 | */ |
800 | |
801 | TABLE_SHARE *tdc_acquire_share(THD *thd, TABLE_LIST *tl, uint flags, |
802 | TABLE **out_table) |
803 | { |
804 | TABLE_SHARE *share; |
805 | TDC_element *element; |
806 | const char *key; |
807 | uint key_length= get_table_def_key(tl, &key); |
808 | my_hash_value_type hash_value= tl->mdl_request.key.tc_hash_value(); |
809 | bool was_unused; |
810 | DBUG_ENTER("tdc_acquire_share" ); |
811 | |
812 | if (fix_thd_pins(thd)) |
813 | DBUG_RETURN(0); |
814 | |
815 | retry: |
816 | while (!(element= (TDC_element*) lf_hash_search_using_hash_value(&tdc_hash, |
817 | thd->tdc_hash_pins, hash_value, (uchar*) key, key_length))) |
818 | { |
819 | LEX_STRING tmp= { const_cast<char*>(key), key_length }; |
820 | int res= lf_hash_insert(&tdc_hash, thd->tdc_hash_pins, (uchar*) &tmp); |
821 | |
822 | if (res == -1) |
823 | DBUG_RETURN(0); |
824 | else if (res == 1) |
825 | continue; |
826 | |
827 | element= (TDC_element*) lf_hash_search_using_hash_value(&tdc_hash, |
828 | thd->tdc_hash_pins, hash_value, (uchar*) key, key_length); |
829 | lf_hash_search_unpin(thd->tdc_hash_pins); |
830 | DBUG_ASSERT(element); |
831 | |
832 | if (!(share= alloc_table_share(tl->db.str, tl->table_name.str, key, key_length))) |
833 | { |
834 | lf_hash_delete(&tdc_hash, thd->tdc_hash_pins, key, key_length); |
835 | DBUG_RETURN(0); |
836 | } |
837 | |
838 | /* note that tdc_acquire_share() *always* uses discovery */ |
839 | open_table_def(thd, share, flags | GTS_USE_DISCOVERY); |
840 | |
841 | if (checked_unlikely(share->error)) |
842 | { |
843 | free_table_share(share); |
844 | lf_hash_delete(&tdc_hash, thd->tdc_hash_pins, key, key_length); |
845 | DBUG_RETURN(0); |
846 | } |
847 | |
848 | mysql_mutex_lock(&element->LOCK_table_share); |
849 | element->share= share; |
850 | share->tdc= element; |
851 | element->ref_count++; |
852 | element->version= tdc_refresh_version(); |
853 | element->flushed= false; |
854 | mysql_mutex_unlock(&element->LOCK_table_share); |
855 | |
856 | tdc_purge(false); |
857 | if (out_table) |
858 | { |
859 | status_var_increment(thd->status_var.table_open_cache_misses); |
860 | *out_table= 0; |
861 | } |
862 | share->m_psi= PSI_CALL_get_table_share(false, share); |
863 | goto end; |
864 | } |
865 | |
866 | /* cannot force discovery of a cached share */ |
867 | DBUG_ASSERT(!(flags & GTS_FORCE_DISCOVERY)); |
868 | |
869 | if (out_table && (flags & GTS_TABLE)) |
870 | { |
871 | if ((*out_table= tc_acquire_table(thd, element))) |
872 | { |
873 | lf_hash_search_unpin(thd->tdc_hash_pins); |
874 | DBUG_ASSERT(!(flags & GTS_NOLOCK)); |
875 | DBUG_ASSERT(element->share); |
876 | DBUG_ASSERT(!element->share->error); |
877 | DBUG_ASSERT(!element->share->is_view); |
878 | status_var_increment(thd->status_var.table_open_cache_hits); |
879 | DBUG_RETURN(element->share); |
880 | } |
881 | status_var_increment(thd->status_var.table_open_cache_misses); |
882 | } |
883 | |
884 | mysql_mutex_lock(&element->LOCK_table_share); |
885 | if (!(share= element->share)) |
886 | { |
887 | mysql_mutex_unlock(&element->LOCK_table_share); |
888 | lf_hash_search_unpin(thd->tdc_hash_pins); |
889 | goto retry; |
890 | } |
891 | lf_hash_search_unpin(thd->tdc_hash_pins); |
892 | |
893 | /* |
894 | We found an existing table definition. Return it if we didn't get |
895 | an error when reading the table definition from file. |
896 | */ |
897 | if (unlikely(share->error)) |
898 | { |
899 | open_table_error(share, share->error, share->open_errno); |
900 | goto err; |
901 | } |
902 | |
903 | if (share->is_view && !(flags & GTS_VIEW)) |
904 | { |
905 | open_table_error(share, OPEN_FRM_NOT_A_TABLE, ENOENT); |
906 | goto err; |
907 | } |
908 | if (!share->is_view && !(flags & GTS_TABLE)) |
909 | { |
910 | open_table_error(share, OPEN_FRM_NOT_A_VIEW, ENOENT); |
911 | goto err; |
912 | } |
913 | |
914 | was_unused= !element->ref_count; |
915 | element->ref_count++; |
916 | mysql_mutex_unlock(&element->LOCK_table_share); |
917 | if (was_unused) |
918 | { |
919 | mysql_mutex_lock(&LOCK_unused_shares); |
920 | if (element->prev) |
921 | { |
922 | /* |
923 | Share was not used before and it was in the old_unused_share list |
924 | Unlink share from this list |
925 | */ |
926 | DBUG_PRINT("info" , ("Unlinking from not used list" )); |
927 | unused_shares.remove(element); |
928 | element->next= 0; |
929 | element->prev= 0; |
930 | } |
931 | mysql_mutex_unlock(&LOCK_unused_shares); |
932 | } |
933 | |
934 | end: |
935 | DBUG_PRINT("exit" , ("share: %p ref_count: %u" , |
936 | share, share->tdc->ref_count)); |
937 | if (flags & GTS_NOLOCK) |
938 | { |
939 | tdc_release_share(share); |
940 | /* |
941 | if GTS_NOLOCK is requested, the returned share pointer cannot be used, |
942 | the share it points to may go away any moment. |
943 | But perhaps the caller is only interested to know whether a share or |
944 | table existed? |
945 | Let's return an invalid pointer here to catch dereferencing attempts. |
946 | */ |
947 | share= (TABLE_SHARE*) 1; |
948 | } |
949 | DBUG_RETURN(share); |
950 | |
951 | err: |
952 | mysql_mutex_unlock(&element->LOCK_table_share); |
953 | DBUG_RETURN(0); |
954 | } |
955 | |
956 | |
957 | /** |
958 | Release table share acquired by tdc_acquire_share(). |
959 | */ |
960 | |
961 | void tdc_release_share(TABLE_SHARE *share) |
962 | { |
963 | DBUG_ENTER("tdc_release_share" ); |
964 | |
965 | mysql_mutex_lock(&share->tdc->LOCK_table_share); |
966 | DBUG_PRINT("enter" , |
967 | ("share: %p table: %s.%s ref_count: %u version: %lld" , |
968 | share, share->db.str, share->table_name.str, |
969 | share->tdc->ref_count, share->tdc->version)); |
970 | DBUG_ASSERT(share->tdc->ref_count); |
971 | |
972 | if (share->tdc->ref_count > 1) |
973 | { |
974 | share->tdc->ref_count--; |
975 | if (!share->is_view) |
976 | mysql_cond_broadcast(&share->tdc->COND_release); |
977 | mysql_mutex_unlock(&share->tdc->LOCK_table_share); |
978 | DBUG_VOID_RETURN; |
979 | } |
980 | mysql_mutex_unlock(&share->tdc->LOCK_table_share); |
981 | |
982 | mysql_mutex_lock(&LOCK_unused_shares); |
983 | mysql_mutex_lock(&share->tdc->LOCK_table_share); |
984 | if (--share->tdc->ref_count) |
985 | { |
986 | if (!share->is_view) |
987 | mysql_cond_broadcast(&share->tdc->COND_release); |
988 | mysql_mutex_unlock(&share->tdc->LOCK_table_share); |
989 | mysql_mutex_unlock(&LOCK_unused_shares); |
990 | DBUG_VOID_RETURN; |
991 | } |
992 | if (share->tdc->flushed || tdc_records() > tdc_size) |
993 | { |
994 | mysql_mutex_unlock(&LOCK_unused_shares); |
995 | tdc_delete_share_from_hash(share->tdc); |
996 | DBUG_VOID_RETURN; |
997 | } |
998 | /* Link share last in used_table_share list */ |
999 | DBUG_PRINT("info" , ("moving share to unused list" )); |
1000 | DBUG_ASSERT(share->tdc->next == 0); |
1001 | unused_shares.push_back(share->tdc); |
1002 | mysql_mutex_unlock(&share->tdc->LOCK_table_share); |
1003 | mysql_mutex_unlock(&LOCK_unused_shares); |
1004 | DBUG_VOID_RETURN; |
1005 | } |
1006 | |
1007 | |
1008 | /** |
1009 | Auxiliary function which allows to kill delayed threads for |
1010 | particular table identified by its share. |
1011 | |
1012 | @param share Table share. |
1013 | |
1014 | @pre Caller should have TABLE_SHARE::tdc.LOCK_table_share mutex. |
1015 | */ |
1016 | |
1017 | static void kill_delayed_threads_for_table(TDC_element *element) |
1018 | { |
1019 | All_share_tables_list::Iterator it(element->all_tables); |
1020 | TABLE *tab; |
1021 | |
1022 | mysql_mutex_assert_owner(&element->LOCK_table_share); |
1023 | |
1024 | if (!delayed_insert_threads) |
1025 | return; |
1026 | |
1027 | while ((tab= it++)) |
1028 | { |
1029 | THD *in_use= tab->in_use; |
1030 | |
1031 | DBUG_ASSERT(in_use && tab->s->tdc->flushed); |
1032 | if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && |
1033 | ! in_use->killed) |
1034 | { |
1035 | in_use->killed= KILL_SYSTEM_THREAD; |
1036 | mysql_mutex_lock(&in_use->mysys_var->mutex); |
1037 | if (in_use->mysys_var->current_cond) |
1038 | { |
1039 | mysql_mutex_lock(in_use->mysys_var->current_mutex); |
1040 | mysql_cond_broadcast(in_use->mysys_var->current_cond); |
1041 | mysql_mutex_unlock(in_use->mysys_var->current_mutex); |
1042 | } |
1043 | mysql_mutex_unlock(&in_use->mysys_var->mutex); |
1044 | } |
1045 | } |
1046 | } |
1047 | |
1048 | |
1049 | /** |
1050 | Remove all or some (depending on parameter) instances of TABLE and |
1051 | TABLE_SHARE from the table definition cache. |
1052 | |
1053 | @param thd Thread context |
1054 | @param remove_type Type of removal: |
1055 | TDC_RT_REMOVE_ALL - remove all TABLE instances and |
1056 | TABLE_SHARE instance. There |
1057 | should be no used TABLE objects |
1058 | and caller should have exclusive |
1059 | metadata lock on the table. |
1060 | TDC_RT_REMOVE_NOT_OWN - remove all TABLE instances |
1061 | except those that belong to |
1062 | this thread. There should be |
1063 | no TABLE objects used by other |
1064 | threads and caller should have |
1065 | exclusive metadata lock on the |
1066 | table. |
1067 | TDC_RT_REMOVE_UNUSED - remove all unused TABLE |
1068 | instances (if there are no |
1069 | used instances will also |
1070 | remove TABLE_SHARE). |
1071 | TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE - |
1072 | remove all TABLE instances |
1073 | except those that belong to |
1074 | this thread, but don't mark |
1075 | TABLE_SHARE as old. There |
1076 | should be no TABLE objects |
1077 | used by other threads and |
1078 | caller should have exclusive |
1079 | metadata lock on the table. |
1080 | @param db Name of database |
1081 | @param table_name Name of table |
1082 | @param kill_delayed_threads If TRUE, kill INSERT DELAYED threads |
1083 | |
1084 | @note It assumes that table instances are already not used by any |
1085 | (other) thread (this should be achieved by using meta-data locks). |
1086 | */ |
1087 | |
1088 | bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, |
1089 | const char *db, const char *table_name, |
1090 | bool kill_delayed_threads) |
1091 | { |
1092 | Share_free_tables::List purge_tables; |
1093 | TABLE *table; |
1094 | TDC_element *element; |
1095 | uint my_refs= 1; |
1096 | DBUG_ENTER("tdc_remove_table" ); |
1097 | DBUG_PRINT("enter" ,("name: %s remove_type: %d" , table_name, remove_type)); |
1098 | |
1099 | DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED || |
1100 | thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, |
1101 | MDL_EXCLUSIVE)); |
1102 | |
1103 | |
1104 | mysql_mutex_lock(&LOCK_unused_shares); |
1105 | if (!(element= tdc_lock_share(thd, db, table_name))) |
1106 | { |
1107 | mysql_mutex_unlock(&LOCK_unused_shares); |
1108 | DBUG_ASSERT(remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE); |
1109 | DBUG_RETURN(false); |
1110 | } |
1111 | |
1112 | DBUG_ASSERT(element != MY_ERRPTR); // What can we do about it? |
1113 | |
1114 | if (!element->ref_count) |
1115 | { |
1116 | if (element->prev) |
1117 | { |
1118 | unused_shares.remove(element); |
1119 | element->prev= 0; |
1120 | element->next= 0; |
1121 | } |
1122 | mysql_mutex_unlock(&LOCK_unused_shares); |
1123 | |
1124 | tdc_delete_share_from_hash(element); |
1125 | DBUG_RETURN(true); |
1126 | } |
1127 | mysql_mutex_unlock(&LOCK_unused_shares); |
1128 | |
1129 | element->ref_count++; |
1130 | |
1131 | tc_remove_all_unused_tables(element, &purge_tables, |
1132 | remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE); |
1133 | |
1134 | if (kill_delayed_threads) |
1135 | kill_delayed_threads_for_table(element); |
1136 | |
1137 | if (remove_type == TDC_RT_REMOVE_NOT_OWN || |
1138 | remove_type == TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) |
1139 | { |
1140 | All_share_tables_list::Iterator it(element->all_tables); |
1141 | while ((table= it++)) |
1142 | { |
1143 | my_refs++; |
1144 | DBUG_ASSERT(table->in_use == thd); |
1145 | } |
1146 | } |
1147 | DBUG_ASSERT(element->all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL); |
1148 | mysql_mutex_unlock(&element->LOCK_table_share); |
1149 | |
1150 | while ((table= purge_tables.pop_front())) |
1151 | intern_close_table(table); |
1152 | |
1153 | if (remove_type != TDC_RT_REMOVE_UNUSED) |
1154 | { |
1155 | /* |
1156 | Even though current thread holds exclusive metadata lock on this share |
1157 | (asserted above), concurrent FLUSH TABLES threads may be in process of |
1158 | closing unused table instances belonging to this share. E.g.: |
1159 | thr1 (FLUSH TABLES): table= share->tdc.free_tables.pop_front(); |
1160 | thr1 (FLUSH TABLES): share->tdc.all_tables.remove(table); |
1161 | thr2 (ALTER TABLE): tdc_remove_table(); |
1162 | thr1 (FLUSH TABLES): intern_close_table(table); |
1163 | |
1164 | Current remove type assumes that all table instances (except for those |
1165 | that are owned by current thread) must be closed before |
1166 | thd_remove_table() returns. Wait for such tables now. |
1167 | |
1168 | intern_close_table() decrements ref_count and signals COND_release. When |
1169 | ref_count drops down to number of references owned by current thread |
1170 | waiting is completed. |
1171 | |
1172 | Unfortunately TABLE_SHARE::wait_for_old_version() cannot be used here |
1173 | because it waits for all table instances, whereas we have to wait only |
1174 | for those that are not owned by current thread. |
1175 | */ |
1176 | mysql_mutex_lock(&element->LOCK_table_share); |
1177 | while (element->ref_count > my_refs) |
1178 | mysql_cond_wait(&element->COND_release, &element->LOCK_table_share); |
1179 | mysql_mutex_unlock(&element->LOCK_table_share); |
1180 | } |
1181 | |
1182 | tdc_release_share(element->share); |
1183 | |
1184 | DBUG_RETURN(true); |
1185 | } |
1186 | |
1187 | |
1188 | /** |
1189 | Check if table's share is being removed from the table definition |
1190 | cache and, if yes, wait until the flush is complete. |
1191 | |
1192 | @param thd Thread context. |
1193 | @param table_list Table which share should be checked. |
1194 | @param timeout Timeout for waiting. |
1195 | @param deadlock_weight Weight of this wait for deadlock detector. |
1196 | |
1197 | @retval 0 Success. Share is up to date or has been flushed. |
1198 | @retval 1 Error (OOM, was killed, the wait resulted |
1199 | in a deadlock or timeout). Reported. |
1200 | */ |
1201 | |
1202 | int tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name, |
1203 | ulong wait_timeout, uint deadlock_weight, tdc_version_t refresh_version) |
1204 | { |
1205 | TDC_element *element; |
1206 | |
1207 | if (!(element= tdc_lock_share(thd, db, table_name))) |
1208 | return FALSE; |
1209 | else if (element == MY_ERRPTR) |
1210 | return TRUE; |
1211 | else if (element->flushed && refresh_version > element->version) |
1212 | { |
1213 | struct timespec abstime; |
1214 | set_timespec(abstime, wait_timeout); |
1215 | return element->share->wait_for_old_version(thd, &abstime, deadlock_weight); |
1216 | } |
1217 | tdc_unlock_share(element); |
1218 | return FALSE; |
1219 | } |
1220 | |
1221 | |
1222 | tdc_version_t tdc_refresh_version(void) |
1223 | { |
1224 | return (tdc_version_t)my_atomic_load64_explicit(&tdc_version, MY_MEMORY_ORDER_RELAXED); |
1225 | } |
1226 | |
1227 | |
1228 | tdc_version_t tdc_increment_refresh_version(void) |
1229 | { |
1230 | tdc_version_t v= (tdc_version_t)my_atomic_add64_explicit(&tdc_version, 1, MY_MEMORY_ORDER_RELAXED); |
1231 | DBUG_PRINT("tcache" , ("incremented global refresh_version to: %lld" , v)); |
1232 | return v + 1; |
1233 | } |
1234 | |
1235 | |
1236 | /** |
1237 | Iterate table definition cache. |
1238 | |
1239 | Object is protected against removal from table definition cache. |
1240 | |
1241 | @note Returned TABLE_SHARE is not guaranteed to be fully initialized: |
1242 | tdc_acquire_share() added new share, but didn't open it yet. If caller |
1243 | needs fully initializer share, it must lock table share mutex. |
1244 | */ |
1245 | |
1246 | struct eliminate_duplicates_arg |
1247 | { |
1248 | HASH hash; |
1249 | MEM_ROOT root; |
1250 | my_hash_walk_action action; |
1251 | void *argument; |
1252 | }; |
1253 | |
1254 | |
1255 | static uchar *eliminate_duplicates_get_key(const uchar *element, size_t *length, |
1256 | my_bool not_used __attribute__((unused))) |
1257 | { |
1258 | LEX_STRING *key= (LEX_STRING *) element; |
1259 | *length= key->length; |
1260 | return (uchar *) key->str; |
1261 | } |
1262 | |
1263 | |
1264 | static my_bool eliminate_duplicates(TDC_element *element, |
1265 | eliminate_duplicates_arg *arg) |
1266 | { |
1267 | LEX_STRING *key= (LEX_STRING *) alloc_root(&arg->root, sizeof(LEX_STRING)); |
1268 | |
1269 | if (!key || !(key->str= (char*) memdup_root(&arg->root, element->m_key, |
1270 | element->m_key_length))) |
1271 | return TRUE; |
1272 | |
1273 | key->length= element->m_key_length; |
1274 | |
1275 | if (my_hash_insert(&arg->hash, (uchar *) key)) |
1276 | return FALSE; |
1277 | |
1278 | return arg->action(element, arg->argument); |
1279 | } |
1280 | |
1281 | |
1282 | int tdc_iterate(THD *thd, my_hash_walk_action action, void *argument, |
1283 | bool no_dups) |
1284 | { |
1285 | eliminate_duplicates_arg no_dups_argument; |
1286 | LF_PINS *pins; |
1287 | myf alloc_flags= 0; |
1288 | uint hash_flags= HASH_UNIQUE; |
1289 | int res; |
1290 | |
1291 | if (thd) |
1292 | { |
1293 | fix_thd_pins(thd); |
1294 | pins= thd->tdc_hash_pins; |
1295 | alloc_flags= MY_THREAD_SPECIFIC; |
1296 | hash_flags|= HASH_THREAD_SPECIFIC; |
1297 | } |
1298 | else |
1299 | pins= lf_hash_get_pins(&tdc_hash); |
1300 | |
1301 | if (!pins) |
1302 | return ER_OUTOFMEMORY; |
1303 | |
1304 | if (no_dups) |
1305 | { |
1306 | init_alloc_root(&no_dups_argument.root, "no_dups" , 4096, 4096, |
1307 | MYF(alloc_flags)); |
1308 | my_hash_init(&no_dups_argument.hash, &my_charset_bin, tdc_records(), 0, 0, |
1309 | eliminate_duplicates_get_key, 0, hash_flags); |
1310 | no_dups_argument.action= action; |
1311 | no_dups_argument.argument= argument; |
1312 | action= (my_hash_walk_action) eliminate_duplicates; |
1313 | argument= &no_dups_argument; |
1314 | } |
1315 | |
1316 | res= lf_hash_iterate(&tdc_hash, pins, action, argument); |
1317 | |
1318 | if (!thd) |
1319 | lf_hash_put_pins(pins); |
1320 | |
1321 | if (no_dups) |
1322 | { |
1323 | my_hash_free(&no_dups_argument.hash); |
1324 | free_root(&no_dups_argument.root, MYF(0)); |
1325 | } |
1326 | return res; |
1327 | } |
1328 | |