-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
mdl.cc
4896 lines (4086 loc) · 158 KB
/
mdl.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
#include "mdl.h"
#include "debug_sync.h"
#include "prealloced_array.h"
#include <lf.h>
#include <mysqld_error.h>
#include <mysql/plugin.h>
#include <mysql/service_thd_wait.h>
#include <pfs_metadata_provider.h>
#include <mysql/psi/mysql_mdl.h>
#include <pfs_stage_provider.h>
#include <mysql/psi/mysql_stage.h>
#include <my_murmur3.h>
#include <algorithm>
#include <functional>
static PSI_memory_key key_memory_MDL_context_acquire_locks;
#ifdef HAVE_PSI_INTERFACE
static PSI_mutex_key key_MDL_wait_LOCK_wait_status;
static PSI_mutex_info all_mdl_mutexes[]=
{
{ &key_MDL_wait_LOCK_wait_status, "MDL_wait::LOCK_wait_status", 0}
};
static PSI_rwlock_key key_MDL_lock_rwlock;
static PSI_rwlock_key key_MDL_context_LOCK_waiting_for;
static PSI_rwlock_info all_mdl_rwlocks[]=
{
{ &key_MDL_lock_rwlock, "MDL_lock::rwlock", 0},
{ &key_MDL_context_LOCK_waiting_for, "MDL_context::LOCK_waiting_for", 0}
};
static PSI_cond_key key_MDL_wait_COND_wait_status;
static PSI_cond_info all_mdl_conds[]=
{
{ &key_MDL_wait_COND_wait_status, "MDL_context::COND_wait_status", 0}
};
static PSI_memory_info all_mdl_memory[]=
{
{ &key_memory_MDL_context_acquire_locks, "MDL_context::acquire_locks", 0}
};
/**
Initialise all the performance schema instrumentation points
used by the MDL subsystem.
*/
static void init_mdl_psi_keys(void)
{
int count;
count= array_elements(all_mdl_mutexes);
mysql_mutex_register("sql", all_mdl_mutexes, count);
count= array_elements(all_mdl_rwlocks);
mysql_rwlock_register("sql", all_mdl_rwlocks, count);
count= array_elements(all_mdl_conds);
mysql_cond_register("sql", all_mdl_conds, count);
count= array_elements(all_mdl_memory);
mysql_memory_register("sql", all_mdl_memory, count);
MDL_key::init_psi_keys();
}
#endif /* HAVE_PSI_INTERFACE */
/**
Thread state names to be used in case when we have to wait on resource
belonging to certain namespace.
*/
PSI_stage_info MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]=
{
{0, "Waiting for global read lock", 0},
{0, "Waiting for tablespace metadata lock", 0},
{0, "Waiting for schema metadata lock", 0},
{0, "Waiting for table metadata lock", 0},
{0, "Waiting for stored function metadata lock", 0},
{0, "Waiting for stored procedure metadata lock", 0},
{0, "Waiting for trigger metadata lock", 0},
{0, "Waiting for event metadata lock", 0},
{0, "Waiting for commit lock", 0},
{0, "User lock", 0}, /* Be compatible with old status. */
{0, "Waiting for locking service lock", 0}
};
#ifdef HAVE_PSI_INTERFACE
void MDL_key::init_psi_keys()
{
int i;
int count;
PSI_stage_info *info MY_ATTRIBUTE((unused));
count= array_elements(MDL_key::m_namespace_to_wait_state_name);
for (i= 0; i<count; i++)
{
/* mysql_stage_register wants an array of pointers, registering 1 by 1. */
info= & MDL_key::m_namespace_to_wait_state_name[i];
mysql_stage_register("sql", &info, 1);
}
}
#endif
static bool mdl_initialized= 0;
/**
A collection of all MDL locks. A singleton,
there is only one instance of the map in the server.
*/
class MDL_map
{
public:
void init();
void destroy();
inline MDL_lock *find(LF_PINS *pins, const MDL_key *key, bool *pinned);
inline MDL_lock *find_or_insert(LF_PINS *pins, const MDL_key *key, bool *pinned);
/**
Decrement unused MDL_lock objects counter.
*/
void lock_object_used()
{
my_atomic_add32(&m_unused_lock_objects, -1);
}
/**
Increment unused MDL_lock objects counter. If number of such objects
exceeds threshold and unused/total objects ratio is high enough try
to free some of them.
*/
void lock_object_unused(MDL_context *ctx, LF_PINS *pins)
{
/*
Use thread local copy of unused locks counter for performance/
scalability reasons. It is updated on both successfull and failed
attempts to delete unused MDL_lock objects in order to avoid infinite
loops,
*/
int32 unused_locks= my_atomic_add32(&m_unused_lock_objects, 1) + 1;
while (unused_locks > mdl_locks_unused_locks_low_water &&
(unused_locks > m_locks.count * MDL_LOCKS_UNUSED_LOCKS_MIN_RATIO))
{
/*
If number of unused lock objects exceeds low water threshold and
unused/total objects ratio is high enough - try to do random dive
into m_locks hash, find an unused object by iterating upwards
through its split-ordered list and try to free it.
If we fail to do this - update local copy of unused objects
counter and retry if needed,
Note that:
*) It is not big deal if "m_unused_lock_objects" due to races becomes
negative temporarily as we perform signed comparison.
*) There is a good chance that we will find an unused object quickly
because unused/total ratio is high enough.
*) There is no possibility for infinite loop since our PRNG works
in such way that we eventually cycle through all LF_HASH hash
buckets (@sa MDL_context::get_random()).
*) Thanks to the fact that we choose random object to expel -
objects which are used more often will naturally stay
in the cache and rarely used objects will be expelled from it.
*) Non-atomic read of LF_HASH::count which happens above should be
OK as LF_HASH code does them too + preceding atomic operation
provides memory barrier.
*/
remove_random_unused(ctx, pins, &unused_locks);
}
}
/**
Get number of unused MDL_lock objects in MDL_map cache.
@note Does non-atomic read so can return stale results. This is OK since
this method is used only in unit-tests. The latter employ means
of thread synchronization which are external to MDL and prevent
memory reordering/ensure that thread calling this method have
up-to-date view on the memory. @sa m_unused_lock_objects.
*/
int32 get_unused_locks_count() const
{
return m_unused_lock_objects;
}
/**
Allocate pins which are necessary for MDL_context/thread to be able
to work with MDL_map container.
*/
LF_PINS *get_pins() { return lf_hash_get_pins(&m_locks); }
/**
Check if MDL_lock object corresponding to the key is going to be
singleton.
*/
bool is_lock_object_singleton(const MDL_key *mdl_key) const
{
return (mdl_key->mdl_namespace() == MDL_key::GLOBAL ||
mdl_key->mdl_namespace() == MDL_key::COMMIT);
}
private:
void remove_random_unused(MDL_context *ctx, LF_PINS *pins, int32 *unused_locks);
/** LF_HASH with all locks in the server. */
LF_HASH m_locks;
/** Pre-allocated MDL_lock object for GLOBAL namespace. */
MDL_lock *m_global_lock;
/** Pre-allocated MDL_lock object for COMMIT namespace. */
MDL_lock *m_commit_lock;
/**
Number of unused MDL_lock objects in the server.
Updated using atomic operations, read using both atomic and ordinary
reads. We assume that ordinary reads of 32-bit words can't result in
partial results, but may produce stale results thanks to memory
reordering, LF_HASH seems to be using similar assumption.
Note that due to fact that updates to this counter are not atomic with
marking MDL_lock objects as used/unused it might easily get negative
for some short period of time. Code which uses its value needs to take
this into account.
*/
volatile int32 m_unused_lock_objects;
};
/**
Threshold for number of unused MDL_lock objects.
We will start considering freeing some unused objects only after exceeding
this value and if unused/total objects ratio is high enough.
Normally this threshold is constant. It is exposed outside of MDL subsystem
as a variable only in order to simplify unit testing.
*/
int32 mdl_locks_unused_locks_low_water=
MDL_LOCKS_UNUSED_LOCKS_LOW_WATER_DEFAULT;
/**
A context of the recursive traversal through all contexts
in all sessions in search for deadlock.
*/
class Deadlock_detection_visitor: public MDL_wait_for_graph_visitor
{
public:
Deadlock_detection_visitor(MDL_context *start_node_arg)
: m_start_node(start_node_arg),
m_victim(NULL),
m_current_search_depth(0),
m_found_deadlock(FALSE)
{}
virtual bool enter_node(MDL_context *node);
virtual void leave_node(MDL_context *node);
virtual bool inspect_edge(MDL_context *dest);
MDL_context *get_victim() const { return m_victim; }
private:
/**
Change the deadlock victim to a new one if it has lower deadlock
weight.
*/
void opt_change_victim_to(MDL_context *new_victim);
private:
/**
The context which has initiated the search. There
can be multiple searches happening in parallel at the same time.
*/
MDL_context *m_start_node;
/** If a deadlock is found, the context that identifies the victim. */
MDL_context *m_victim;
/** Set to the 0 at start. Increased whenever
we descend into another MDL context (aka traverse to the next
wait-for graph node). When MAX_SEARCH_DEPTH is reached, we
assume that a deadlock is found, even if we have not found a
loop.
*/
uint m_current_search_depth;
/** TRUE if we found a deadlock. */
bool m_found_deadlock;
/**
Maximum depth for deadlock searches. After this depth is
achieved we will unconditionally declare that there is a
deadlock.
@note This depth should be small enough to avoid stack
being exhausted by recursive search algorithm.
TODO: Find out what is the optimal value for this parameter.
Current value is safe, but probably sub-optimal,
as there is an anecdotal evidence that real-life
deadlocks are even shorter typically.
*/
static const uint MAX_SEARCH_DEPTH= 32;
};
/**
Enter a node of a wait-for graph. After
a node is entered, inspect_edge() will be called
for all wait-for destinations of this node. Then
leave_node() will be called.
We call "enter_node()" for all nodes we inspect,
including the starting node.
@retval TRUE Maximum search depth exceeded.
@retval FALSE OK.
*/
bool Deadlock_detection_visitor::enter_node(MDL_context *node)
{
m_found_deadlock= ++m_current_search_depth >= MAX_SEARCH_DEPTH;
if (m_found_deadlock)
{
DBUG_ASSERT(! m_victim);
opt_change_victim_to(node);
}
return m_found_deadlock;
}
/**
Done inspecting this node. Decrease the search
depth. If a deadlock is found, and we are
backtracking to the start node, optionally
change the deadlock victim to one with lower
deadlock weight.
*/
void Deadlock_detection_visitor::leave_node(MDL_context *node)
{
--m_current_search_depth;
if (m_found_deadlock)
opt_change_victim_to(node);
}
/**
Inspect a wait-for graph edge from one MDL context to another.
@retval TRUE A loop is found.
@retval FALSE No loop is found.
*/
bool Deadlock_detection_visitor::inspect_edge(MDL_context *node)
{
m_found_deadlock= node == m_start_node;
return m_found_deadlock;
}
/**
Change the deadlock victim to a new one if it has lower deadlock
weight.
@param new_victim New candidate for deadlock victim.
*/
void
Deadlock_detection_visitor::opt_change_victim_to(MDL_context *new_victim)
{
if (m_victim == NULL ||
m_victim->get_deadlock_weight() >= new_victim->get_deadlock_weight())
{
/* Swap victims, unlock the old one. */
MDL_context *tmp= m_victim;
m_victim= new_victim;
m_victim->lock_deadlock_victim();
if (tmp)
tmp->unlock_deadlock_victim();
}
}
/**
Get a bit corresponding to enum_mdl_type value in a granted/waiting bitmaps
and compatibility matrices.
*/
#define MDL_BIT(A) static_cast<MDL_lock::bitmap_t>(1U << A)
/**
The lock context. Created internally for an acquired lock.
For a given name, there exists only one MDL_lock instance,
and it exists only when the lock has been granted.
Can be seen as an MDL subsystem's version of TABLE_SHARE.
This is an abstract class which lacks information about
compatibility rules for lock types. They should be specified
in its descendants.
*/
class MDL_lock
{
public:
typedef unsigned short bitmap_t;
class Ticket_list
{
public:
typedef I_P_List<MDL_ticket,
I_P_List_adapter<MDL_ticket,
&MDL_ticket::next_in_lock,
&MDL_ticket::prev_in_lock>,
I_P_List_null_counter,
I_P_List_fast_push_back<MDL_ticket> >
List;
operator const List &() const { return m_list; }
Ticket_list() :m_bitmap(0) {}
void add_ticket(MDL_ticket *ticket);
void remove_ticket(MDL_ticket *ticket);
bool is_empty() const { return m_list.is_empty(); }
bitmap_t bitmap() const { return m_bitmap; }
private:
void clear_bit_if_not_in_list(enum_mdl_type type);
private:
/** List of tickets. */
List m_list;
/** Bitmap of types of tickets in this list. */
bitmap_t m_bitmap;
};
typedef Ticket_list::List::Iterator Ticket_iterator;
typedef longlong fast_path_state_t;
/**
Helper struct which defines how different types of locks are handled
for a specific MDL_lock. In practice we use only two strategies: "scoped"
lock strategy for locks in GLOBAL, COMMIT, TABLESPACE and SCHEMA namespaces
and "object" lock strategy for all other namespaces.
*/
struct MDL_lock_strategy
{
/**
Compatibility (or rather "incompatibility") matrices for lock types.
Array of bitmaps which elements specify which granted locks are
incompatible with the type of lock being requested.
*/
bitmap_t m_granted_incompatible[MDL_TYPE_END];
/**
Arrays of bitmaps which elements specify which waiting locks are
incompatible with the type of lock being requested. Basically, each
array defines priorities between lock types.
We need 4 separate arrays since in order to prevent starvation for
some of lock request types, we use different priority matrices:
0) in "normal" situation.
1) in situation when the number of successively granted "piglet" requests
exceeds the max_write_lock_count limit.
2) in situation when the number of successively granted "hog" requests
exceeds the max_write_lock_count limit.
3) in situation when both "piglet" and "hog" counters exceed limit.
*/
bitmap_t m_waiting_incompatible[4][MDL_TYPE_END];
/**
Array of increments for "unobtrusive" types of lock requests for locks.
@sa MDL_lock::get_unobtrusive_lock_increment().
*/
fast_path_state_t m_unobtrusive_lock_increment[MDL_TYPE_END];
/**
Indicates that locks of this type are affected by
the max_write_lock_count limit.
*/
bool m_is_affected_by_max_write_lock_count;
/**
Pointer to a static method which determines if the type of lock
requested requires notification of conflicting locks. NULL if there
are no lock types requiring notification.
*/
bool (*m_needs_notification)(const MDL_ticket *ticket);
/**
Pointer to a static method which allows notification of owners of
conflicting locks about the fact that a type of lock requiring
notification was requested.
*/
void (*m_notify_conflicting_locks)(MDL_context *ctx, MDL_lock *lock);
/**
Pointer to a static method which converts information about
locks granted using "fast" path from fast_path_state_t
representation to bitmap of lock types.
*/
bitmap_t (*m_fast_path_granted_bitmap)(const MDL_lock &lock);
/**
Pointer to a static method which determines if waiting for the lock
should be aborted when when connection is lost. NULL if locks of
this type don't require such aborts.
*/
bool (*m_needs_connection_check)(const MDL_lock *lock);
};
public:
/** The key of the object (data) being protected. */
MDL_key key;
/**
Read-write lock protecting this lock context.
@note The fact that we use read-write lock prefers readers here is
important as deadlock detector won't work correctly otherwise.
For example, imagine that we have following waiters graph:
ctxA -> obj1 -> ctxB -> obj1 -|
^ |
|----------------------------|
and both ctxA and ctxB start deadlock detection process:
ctxA read-locks obj1 ctxB read-locks obj2
ctxA goes deeper ctxB goes deeper
Now ctxC comes in who wants to start waiting on obj1, also
ctxD comes in who wants to start waiting on obj2.
ctxC tries to write-lock obj1 ctxD tries to write-lock obj2
ctxC is blocked ctxD is blocked
Now ctxA and ctxB resume their search:
ctxA tries to read-lock obj2 ctxB tries to read-lock obj1
If m_rwlock prefers writes (or fair) both ctxA and ctxB would be
blocked because of pending write locks from ctxD and ctxC
correspondingly. Thus we will get a deadlock in deadlock detector.
If m_wrlock prefers readers (actually ignoring pending writers is
enough) ctxA and ctxB will continue and no deadlock will occur.
*/
mysql_prlock_t m_rwlock;
const bitmap_t *incompatible_granted_types_bitmap() const
{
return m_strategy->m_granted_incompatible;
}
const bitmap_t *incompatible_waiting_types_bitmap() const
{
return
m_strategy->m_waiting_incompatible[m_current_waiting_incompatible_idx];
}
/**
Get index of priority matrice in MDL_lock_strategy::m_waiting_incompatible
array which corresponds to current values of the m_piglet_lock_count and
m_hog_lock_count counters and the max_write_lock_count threshold.
*/
uint get_incompatible_waiting_types_bitmap_idx() const
{
mysql_prlock_assert_write_owner(&m_rwlock);
/*
To prevent starvation for lock types with lower priority use:
*) MDL_lock_strategy::m_waiting_incompatible[0] matrice by default.
*) MDL_lock_strategy::m_waiting_incompatible[1] when the number of
successively granted "piglet" requests exceeds max_write_lock_count.
*) MDL_lock_strategy::m_waiting_incompatible[2] when the number of
successively granted "hog" requests exceeds max_write_lock_count.
*) MDL_lock_strategy::m_waiting_incompatible[3] when both "piglet" and
"hog" counters exceed this limit.
*/
uint idx= 0;
if (m_piglet_lock_count >= max_write_lock_count)
idx+= 1;
if (m_hog_lock_count >= max_write_lock_count)
idx+= 2;
return idx;
}
/**
Switch priority matrice for the MDL_lock object if m_piglet_lock_count or/
and m_hog_lock_count counters have crossed max_write_lock_count threshold.
@returns true - if priority matrice has been changed, false - otherwise.
*/
bool switch_incompatible_waiting_types_bitmap_if_needed()
{
mysql_prlock_assert_write_owner(&m_rwlock);
uint new_idx= get_incompatible_waiting_types_bitmap_idx();
if (m_current_waiting_incompatible_idx == new_idx)
return false;
m_current_waiting_incompatible_idx= new_idx;
return true;
}
bool has_pending_conflicting_lock(enum_mdl_type type);
bool can_grant_lock(enum_mdl_type type,
const MDL_context *requestor_ctx) const;
void reschedule_waiters();
void remove_ticket(MDL_context *ctx, LF_PINS *pins,
Ticket_list MDL_lock::*queue,
MDL_ticket *ticket);
bool visit_subgraph(MDL_ticket *waiting_ticket,
MDL_wait_for_graph_visitor *gvisitor);
bool needs_notification(const MDL_ticket *ticket) const
{
return m_strategy->m_needs_notification ?
m_strategy->m_needs_notification(ticket) : false;
}
void notify_conflicting_locks(MDL_context *ctx)
{
if (m_strategy->m_notify_conflicting_locks)
m_strategy->m_notify_conflicting_locks(ctx, this);
}
bool needs_connection_check() const
{
return m_strategy->m_needs_connection_check ?
m_strategy->m_needs_connection_check(this) : false;
}
inline static bool needs_hton_notification(MDL_key::enum_mdl_namespace
mdl_namespace);
bool is_affected_by_max_write_lock_count() const
{
return m_strategy->m_is_affected_by_max_write_lock_count;
}
/**
If we just have granted a lock of "piglet" or "hog" type and there are
pending lower priority locks, increase the appropriate counter. If this
counter now exceeds the max_write_lock_count threshold, switch priority
matrice for the MDL_lock object.
@returns true - if priority matrice has been changed, false - otherwise.
*/
bool count_piglets_and_hogs(enum_mdl_type type)
{
mysql_prlock_assert_write_owner(&m_rwlock);
if ((MDL_BIT(type) & MDL_OBJECT_HOG_LOCK_TYPES) != 0)
{
if (m_waiting.bitmap() & ~MDL_OBJECT_HOG_LOCK_TYPES)
{
m_hog_lock_count++;
if (switch_incompatible_waiting_types_bitmap_if_needed())
return true;
}
}
else if (type == MDL_SHARED_WRITE)
{
if (m_waiting.bitmap() & MDL_BIT(MDL_SHARED_READ_ONLY))
{
m_piglet_lock_count++;
if (switch_incompatible_waiting_types_bitmap_if_needed())
return true;
}
}
return false;
}
/**
@returns "Fast path" increment for request for "unobtrusive" type
of lock, 0 - if it is request for "obtrusive" type of
lock.
@note We split all lock types for each of MDL namespaces
in two sets:
A) "unobtrusive" lock types
1) Each type from this set should be compatible with all other
types from the set (including itself).
2) These types should be common for DML operations
Our goal is to optimize acquisition and release of locks of this
type by avoiding complex checks and manipulations on m_waiting/
m_granted bitmaps/lists. We replace them with a check of and
increment/decrement of integer counters.
We call the latter type of acquisition/release "fast path".
Use of "fast path" reduces the size of critical section associated
with MDL_lock::m_rwlock lock in the common case and thus increases
scalability.
The amount by which acquisition/release of specific type
"unobtrusive" lock increases/decreases packed counter in
MDL_lock::m_fast_path_state is returned by this function.
B) "obtrusive" lock types
1) Granted or pending lock of those type is incompatible with
some other types of locks or with itself.
2) Not common for DML operations
These locks have to be always acquired involving manipulations on
m_waiting/m_granted bitmaps/lists, i.e. we have to use "slow path"
for them. Moreover in the presence of active/pending locks from
"obtrusive" set we have to acquire using "slow path" even locks of
"unobtrusive" type.
@sa MDL_scoped_lock/MDL_object_lock::m_unobtrusive_lock_increment for
definitions of these sets for scoped and per-object locks.
*/
inline static fast_path_state_t
get_unobtrusive_lock_increment(const MDL_request *request);
/**
@returns "Fast path" increment if type of lock is "unobtrusive" type,
0 - if it is "obtrusive" type of lock.
*/
fast_path_state_t get_unobtrusive_lock_increment(enum_mdl_type type) const
{
return m_strategy->m_unobtrusive_lock_increment[type];
}
/**
Check if type of lock requested is "obtrusive" type of lock.
@sa MDL_lock::get_unobtrusive_lock_increment() description.
*/
bool is_obtrusive_lock(enum_mdl_type type) const
{
return get_unobtrusive_lock_increment(type) == 0;
}
/**
Return set of types of lock requests which were granted using
"fast path" algorithm in the bitmap_t form.
This method is only called from MDL_lock::can_grant_lock() and its
return value is only important when we are trying to figure out if
we can grant an obtrusive lock. But this means that the HAS_OBTRUSIVE
flag is set so all changes to m_fast_path_state happen under protection
of MDL_lock::m_rwlock (see invariant [INV1]).
Since can_grant_lock() is called only when MDL_lock::m_rwlock is held,
it is safe to do an ordinary read of m_fast_path_state here.
*/
bitmap_t fast_path_granted_bitmap() const
{
return m_strategy->m_fast_path_granted_bitmap(*this);
}
/** List of granted tickets for this lock. */
Ticket_list m_granted;
/** Tickets for contexts waiting to acquire a lock. */
Ticket_list m_waiting;
private:
/**
Number of times high priority, "hog" lock requests (X, SNRW, SNW) have been
granted while lower priority lock requests (all other types) were waiting.
Currently used only for object locks. Protected by m_rwlock lock.
*/
ulong m_hog_lock_count;
/**
Number of times high priority, "piglet" lock requests (SW) have been
granted while locks requests with lower priority (SRO) were waiting.
Currently used only for object locks. Protected by m_rwlock lock.
*/
ulong m_piglet_lock_count;
/**
Index of one of the MDL_lock_strategy::m_waiting_incompatible
arrays which represents the current priority matrice.
*/
uint m_current_waiting_incompatible_idx;
public:
/**
Do "expensive" part of MDL_lock object initialization,
Called by LF_ALLOCATOR for each newly malloc()'ed MDL_lock object, is not
called in cases when LF_ALLOCATOR decides to reuse object which was
returned to it earlier. "Full" initialization happens later by calling
MDL_lock::reinit(). So @sa MDL_lock::reiniti()
*/
MDL_lock()
: m_obtrusive_locks_granted_waiting_count(0)
{
mysql_prlock_init(key_MDL_lock_rwlock, &m_rwlock);
}
inline void reinit(const MDL_key *mdl_key);
~MDL_lock()
{
mysql_prlock_destroy(&m_rwlock);
}
inline static MDL_lock *create(const MDL_key *key);
inline static void destroy(MDL_lock *lock);
inline MDL_context *get_lock_owner() const;
public:
/**
Number of granted or waiting lock requests of "obtrusive" type.
Also includes "obtrusive" lock requests for which we about to check
if they can be granted.
@sa MDL_lock::get_unobtrusive_lock_increment() description.
@note This number doesn't include "unobtrusive" locks which were acquired
using "slow path".
*/
uint m_obtrusive_locks_granted_waiting_count;
/**
Flag in MDL_lock::m_fast_path_state that indicates that the MDL_lock
object was marked for destruction and will be destroyed once all threads
referencing to it through hazard pointers have unpinned it.
Set using atomic compare-and-swap AND under protection of
MDL_lock::m_rwlock lock.
Thanks to this can be read either by using atomic compare-and-swap OR
using ordinary read under protection of MDL_lock::m_rwlock lock.
*/
static const fast_path_state_t IS_DESTROYED= 1ULL << 62;
/**
Flag in MDL_lock::m_fast_path_state that indicates that there are
"obtrusive" locks which are granted, waiting or for which we are
about to check if they can be granted.
Corresponds to "MDL_lock::m_obtrusive_locks_granted_waiting_count == 0"
predicate.
Set using atomic compare-and-swap AND under protection of
MDL_lock::m_rwlock lock.
Thanks to this can be read either by using atomic compare-and-swap OR
using ordinary read under protection of MDL_lock::m_rwlock lock.
Invariant [INV1]: When this flag is set all changes to m_fast_path_state
member has to be done under protection of m_rwlock lock.
*/
static const fast_path_state_t HAS_OBTRUSIVE= 1ULL << 61;
/**
Flag in MDL_lock::m_fast_path_state that indicates that there are
"slow" path locks which are granted, waiting or for which we are
about to check if they can be granted.
Corresponds to MDL_lock::m_granted/m_waiting lists being non-empty
(except special case in MDL_context::try_acquire_lock()).
Set using atomic compare-and-swap AND under protection of m_rwlock
lock. The latter is necessary because value of this flag needs to be
synchronized with contents of MDL_lock::m_granted/m_waiting lists.
*/
static const fast_path_state_t HAS_SLOW_PATH= 1ULL << 60;
/**
Combination of IS_DESTROYED/HAS_OBTRUSIVE/HAS_SLOW_PATH flags and packed
counters of specific types of "unobtrusive" locks which were granted using
"fast path".
@sa MDL_scoped_lock::m_unobtrusive_lock_increment and
MDL_object_lock::m_unobtrusive_lock_increment for details about how
counts of different types of locks are packed into this field.
@note Doesn't include "unobtrusive" locks granted using "slow path".
@note We use combination of atomic operations and protection by
MDL_lock::m_rwlock lock to work with this member:
* Write and Read-Modify-Write operations are always carried out
atomically. This is necessary to avoid lost updates on 32-bit
platforms among other things.
* In some cases Reads can be done non-atomically because we don't
really care about value which they will return (for example,
if further down the line there will be an atomic compare-and-swap
operation, which will validate this value and provide the correct
value if the validation will fail).
* In other cases Reads can be done non-atomically since they happen
under protection of MDL_lock::m_rwlock and there is some invariant
which ensures that concurrent updates of the m_fast_path_state
member can't happen while MDL_lock::m_rwlock is held
(@sa IS_DESTROYED, HAS_OBTRUSIVE, HAS_SLOW_PATH).
@note IMPORTANT!!!
In order to enforce the above rules and other invariants,
MDL_lock::m_fast_path_state should not be updated directly.
Use fast_path_state_cas()/add()/reset() wrapper methods instead.
@note Needs to be volatile in order to be compatible with our
my_atomic_*() API.
*/
volatile fast_path_state_t m_fast_path_state;
/**
Wrapper for my_atomic_cas64 operation on m_fast_path_state member
which enforces locking and other invariants.
*/
bool fast_path_state_cas(fast_path_state_t *old_state,
fast_path_state_t new_state)
{
/*
IS_DESTROYED, HAS_OBTRUSIVE and HAS_SLOW_PATH flags can be set or
cleared only while holding MDL_lock::m_rwlock lock.
If HAS_SLOW_PATH flag is set all changes to m_fast_path_state
should happen under protection of MDL_lock::m_rwlock ([INV1]).
*/
#if !defined(DBUG_OFF)
if (((*old_state & (IS_DESTROYED | HAS_OBTRUSIVE | HAS_SLOW_PATH)) !=
(new_state & (IS_DESTROYED | HAS_OBTRUSIVE | HAS_SLOW_PATH))) ||
*old_state & HAS_OBTRUSIVE)
{
mysql_prlock_assert_write_owner(&m_rwlock);
}
#endif
/*
We should not change state of destroyed object
(fast_path_state_reset() being exception).
*/
DBUG_ASSERT(! (*old_state & IS_DESTROYED));
return my_atomic_cas64(&m_fast_path_state, old_state, new_state);
}
/**
Wrapper for my_atomic_add64 operation on m_fast_path_state member
which enforces locking and other invariants.
*/
fast_path_state_t fast_path_state_add(fast_path_state_t value)
{
/*
Invariant [INV1] requires all changes to m_fast_path_state happen
under protection of m_rwlock if HAS_OBTRUSIVE flag is set.
Since this operation doesn't check this flag it can be called only
under protection of m_rwlock.
*/
mysql_prlock_assert_write_owner(&m_rwlock);
fast_path_state_t old_state= my_atomic_add64(&m_fast_path_state, value);
/*
We should not change state of destroyed object
(fast_path_state_reset() being exception).
*/
DBUG_ASSERT(! (old_state & IS_DESTROYED));
return old_state;
}
/**
Wrapper for resetting m_fast_path_state enforcing locking invariants.
*/
void fast_path_state_reset()
{
/* HAS_DESTROYED flag can be cleared only under protection of m_rwlock. */
mysql_prlock_assert_write_owner(&m_rwlock);
my_atomic_store64(&m_fast_path_state, 0);
}
/**
Pointer to strategy object which defines how different types of lock
requests should be handled for the namespace to which this lock belongs.
@sa MDL_lock::m_scoped_lock_strategy and MDL_lock:m_object_lock_strategy.
*/
const MDL_lock_strategy *m_strategy;
/**
Get bitmap of "unobtrusive" locks granted using "fast path" algorithm
for scoped locks.
@sa MDL_lock::fast_path_granted_bitmap() for explanation about why it
is safe to use non-atomic read of MDL_lock::m_fast_path_state here.
*/
static bitmap_t scoped_lock_fast_path_granted_bitmap(const MDL_lock &lock)
{
return (lock.m_fast_path_state & 0xFFFFFFFFFFFFFFFULL) ?
MDL_BIT(MDL_INTENTION_EXCLUSIVE) : 0;
}
/**
Check if we are requesting X lock on the object, so threads holding
conflicting S/SH metadata locks on it need to be notified.
@sa MDL_lock::object_lock_notify_conflicting_locks.
*/
static bool object_lock_needs_notification(const MDL_ticket *ticket)
{
return (ticket->get_type() == MDL_EXCLUSIVE);
}
static void object_lock_notify_conflicting_locks(MDL_context *ctx,
MDL_lock *lock);
/**