File: lock_collision_report.test

package info (click to toggle)
mysql-8.0 8.0.43-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,273,924 kB
  • sloc: cpp: 4,684,605; ansic: 412,450; pascal: 108,398; java: 83,641; perl: 30,221; cs: 27,067; sql: 26,594; sh: 24,181; python: 21,816; yacc: 17,169; php: 11,522; xml: 7,388; javascript: 7,076; makefile: 2,194; lex: 1,075; awk: 670; asm: 520; objc: 183; ruby: 97; lisp: 86
file content (113 lines) | stat: -rw-r--r-- 4,319 bytes parent folder | download
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
# BUG#32586458 INNODB CLUSTER SLOWNESS SEEING SESSIONS WITH STATUS "WAITING
#              FOR HANDLER TO COMMIT"
#
--source include/have_debug_sync.inc

# This scenario demonstrates that when there are N reasons for a lock wait,
# (connections rk_1,..,rk_N hold S lock on a row needed by rt_wait)
# which go away one by one (as rk_$k get rolled back), then InnoDB correctly
# reports one of the remaining reasons for wait.
# InnoDB either grants a requested lock or picks one of conflicting transactions
# as the current reason it has to wait. If there is more than one conflicting
# transaction, the choice is non-deterministic. Thus, this test uses a trick:
# it relies on the InnoDB deadlock checker's logic, which rolls back a
# transaction which is part of a deadlock cycle consisting of edges that are
# "current reason for wait".
# Therefore this MTR does not have to know in which order it should rollback the
# transactions rt_1,...,rt_N to follow the order of picking "current reason for
# wait". Instead this MTR simply arranges it so that there are N small deadlock
# cycles, k-th of them involving rt_k and rt_wait only:
#
# rt_wait <--x-locked-by----- 100 <---has-requested-s-lock---\
#   |                                                        |
#   |                                                      / | \
#   |                                                  rt_1 ... rt_N
#   |                                                     ^  ^  ^
#   |                                                      \ | /
#   |                                                        |
#   \--has-requested-x-lock--> 20 --s-locked-----------------/
#
# The wait-for graph between transactions inspected by InnoDB's deadlock checker
# is a sparse subgraph of the above: only one of the paths from rt_wait to one
# rt_k is picked. It is the same edge which is reported to Server layer.
# As it is a part of a deadlock cycle, the victim will be chosen, cycle removed,
# and another edge will be picked and so on.
# We make sure rt_wait is never chosen as a victim, by making its TRX_WEIGHT
# high, by INSERTing several records.
# What remains is to count that the number of reported wait-for edges is N*2.

# Create a table
CREATE TABLE t (
  id INT PRIMARY KEY
) ENGINE=InnoDB;
INSERT INTO t VALUES (10), (20), (30), (40), (50);

# We want to be informed about any lock waits reported to Server layer
--let $debug_point = syncpoint_report_lock_collision
--source include/add_debug_point.inc

--let $n_blockers = 13

# Create N connections to the server.
# Using connections 1..N, start a transaction, acquire shared lock over a given
# record using `SELECT ... FOR SHARE`
--let $k = $n_blockers
while ($k != 0)
{
  --connect (rt_$k,127.0.0.1,root,,test,,)
    BEGIN;
    SELECT id FROM t FORCE INDEX (PRIMARY) WHERE id = 20 FOR SHARE;
    --dec $k
}


# Using connection N + 1, acquire exclusive lock over the same record
# using `SELECT ... FOR UPDATE`. This will cause the rt_wait to wait on a lock.
--connect (rt_wait,127.0.0.1,root,,test,,)
  BEGIN;
  # make it heavy by inserting a lot of records
  INSERT INTO t VALUES (60), (70), (80), (90), (100), (110), (120);
  --send SELECT * FROM t FORCE INDEX (PRIMARY) WHERE id = 20 FOR UPDATE


# Verify that the first reason for wait was reported
--connection default
  SET DEBUG_SYNC = 'now WAIT_FOR reached_report_lock_collision';
  SET DEBUG_SYNC = 'now SIGNAL continue_report_lock_collision';

# create N small deadlocks cycles between rt_$k and and rt_wait
while ($k != $n_blockers)
{
  --inc $k
  --connection rt_$k
    --send SELECT * FROM t FORCE INDEX (PRIMARY) WHERE id=100 FOR SHARE
}

--connection default

--let expected_reports = `SELECT $n_blockers * 2 - 1`
while ($expected_reports != 0)
{
  --echo # expect $expected_reports more edges to be reported
  SET DEBUG_SYNC = 'now WAIT_FOR reached_report_lock_collision';
  SET DEBUG_SYNC = 'now SIGNAL continue_report_lock_collision';
  --dec $expected_reports
}
while ($k != 0)
{
  --echo # excpecting that connection $k was rolled back as a deadlock victim
  --connection rt_$k
    --error ER_LOCK_DEADLOCK
    --reap
  --disconnect rt_$k
  --dec $k
}

# Clean up
--connection rt_wait
  --reap
  ROLLBACK;
--connection default
--disconnect rt_wait
--source include/remove_debug_point.inc
DROP TABLE t;