Skip to content

Commit b2ebf2e

Browse files
MDEV-39101 Make BACKUP SERVER mutually exclusive with itself and BACKUP STAGE
MTR tests for running concurrent backups. BACKUP SERVER acquires backup lock before starting the backup. This prevents it from running concurrently with another instance of BACKUP SERVER or BACKUP STAGE, blocking when a backup is running concurrently in another connection. If BACKUP SERVER is run on a connection on which BACKUP STAGE has been started, it will fail with an error.
1 parent 39aab26 commit b2ebf2e

5 files changed

Lines changed: 205 additions & 31 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--- main/backup_server_locking.result
2+
+++ main/backup_server_locking.reject
3+
@@ -1,3 +1,31 @@
4+
+connect blocking,localhost,root,,test;
5+
+SET DEBUG_SYNC='after_backup_server_lock_acquired SIGNAL blocking WAIT_FOR unblock';
6+
+SET DEBUG_SYNC='backup_server_finalizing SIGNAL finalizing WAIT_FOR finish';
7+
+BACKUP SERVER TO '<MYSQLTEST_VARDIR>/some_directory';
8+
+connect blocked,localhost,root,,test;
9+
+SET DEBUG_SYNC='now WAIT_FOR blocking';
10+
+BACKUP SERVER TO '<MYSQLTEST_VARDIR>/other_directory';
11+
+connection default;
12+
+SET @blocked_conn_id = $blocked_conn_id;
13+
+SELECT COMMAND, STATE, INFO FROM INFORMATION_SCHEMA.PROCESSLIST
14+
+WHERE ID = @blocked_conn_id;
15+
+COMMAND STATE INFO
16+
+Query Waiting for backup lock BACKUP SERVER TO '<MYSQLTEST_VARDIR>/other_directory'
17+
+SET DEBUG_SYNC='now SIGNAL unblock';
18+
+SET DEBUG_SYNC='now WAIT_FOR finalizing';
19+
+connection blocked;
20+
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST
21+
+WHERE Info LIKE 'BACKUP SERVER TO %' AND State = 'Waiting for backup lock';
22+
+COUNT(*)
23+
+0
24+
+BACKUP SERVER TO '$MYSQLTEST_VARDIR/another_directory';
25+
+disconnect blocked;
26+
+connection default;
27+
+SET DEBUG_SYNC='now SIGNAL finish';
28+
+connection blocking;
29+
+disconnect blocking;
30+
+connection default;
31+
+SET DEBUG_SYNC='RESET';
32+
BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory';
33+
ERROR HY000: Can't create directory 'MYSQLTEST_VARDIR/some_directory' (Errcode: 17 "File exists")
34+
BACKUP STAGE START;

mysql-test/main/backup_server_locking.result renamed to mysql-test/main/backup_server_concurrency.result

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,19 @@ BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory';
1515
ERROR HY000: Can't create directory 'MYSQLTEST_VARDIR/some_directory' (Errcode: 17 "File exists")
1616
disconnect backup;
1717
connection default;
18+
BACKUP STAGE START;
19+
connect con1,localhost,root,,test;
20+
BACKUP SERVER TO '<MYSQLTEST_VARDIR>/some_directory';
21+
connection default;
22+
SET @con1_id = <con1_id>;
23+
SELECT COMMAND, STATE, INFO FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID = @con1_id;
24+
COMMAND STATE INFO
25+
Query Waiting for backup lock BACKUP SERVER TO '<MYSQLTEST_VARDIR>/some_directory'
26+
BACKUP STAGE END;
27+
connection con1;
28+
disconnect con1;
29+
connection default;
30+
BACKUP STAGE START;
31+
BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory';
32+
ERROR HY000: Can't execute the command as you have a BACKUP STAGE active
33+
BACKUP STAGE END;
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# BACKUP SERVER acquires Bacup Lock, disallowing concurrent backup either
2+
# by another instance of BACKUP SERVER or using BACKUP STAGE.
3+
4+
# Using DEBUG_SYNC facility in debug configuration we directly test
5+
# that while one instance of BACKUP SERVER has already acquired the
6+
# backup lock, a second instance running on another connection will
7+
# block waiting on that lock. BACKUP SERVER releases the lock before
8+
# it returns, when it reaches its finalization stage, as actions
9+
# taken in that stage affect only the backup directory and may be
10+
# performed concurrently with other backup processes.
11+
12+
--source include/not_embedded.inc
13+
--source include/maybe_debug.inc
14+
if ($have_debug) {
15+
16+
--source include/have_debug_sync.inc
17+
--connect (blocking,localhost,root,,test)
18+
# Simulate long-running backup by blocking it on a debug sync point
19+
SET DEBUG_SYNC='after_backup_server_lock_acquired SIGNAL blocking WAIT_FOR unblock';
20+
SET DEBUG_SYNC='backup_server_finalizing SIGNAL finalizing WAIT_FOR finish';
21+
--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
22+
--send_eval BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory'
23+
24+
--connect (blocked,localhost,root,,test)
25+
# Wait for the "long-running" backup to lock the system
26+
SET DEBUG_SYNC='now WAIT_FOR blocking';
27+
let $blocked_conn_id= `SELECT CONNECTION_ID()`;
28+
# Attempt a backup while the other backup is running
29+
--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
30+
--send_eval BACKUP SERVER TO '$MYSQLTEST_VARDIR/other_directory'
31+
32+
--connection default
33+
# Check that the backup on "blocked" connection is waiting for the blocking
34+
# backup to finish
35+
evalp SET @blocked_conn_id = $blocked_conn_id;
36+
let $wait_condition=
37+
SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST
38+
WHERE ID = @blocked_conn_id AND State = 'Waiting for backup lock';
39+
--source include/wait_condition.inc
40+
--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
41+
SELECT COMMAND, STATE, INFO FROM INFORMATION_SCHEMA.PROCESSLIST
42+
WHERE ID = @blocked_conn_id;
43+
44+
SET DEBUG_SYNC='now SIGNAL unblock';
45+
SET DEBUG_SYNC='now WAIT_FOR finalizing';
46+
# The blocking backup is still running, but no longer blocking
47+
48+
--connection blocked
49+
# Original blocked backup can complete now
50+
--reap
51+
# No blocked backup process at this point
52+
SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST
53+
WHERE Info LIKE 'BACKUP SERVER TO %' AND State = 'Waiting for backup lock';
54+
# Run new BACKUP SERVER while the old one is finalizing
55+
--evalp BACKUP SERVER TO '$MYSQLTEST_VARDIR/another_directory'
56+
57+
--disconnect blocked
58+
59+
--connection default
60+
SET DEBUG_SYNC='now SIGNAL finish';
61+
62+
--connection blocking
63+
--reap
64+
--disconnect blocking
65+
66+
--connection default
67+
SET DEBUG_SYNC='RESET';
68+
69+
--rmdir $MYSQLTEST_VARDIR/some_directory
70+
--rmdir $MYSQLTEST_VARDIR/other_directory
71+
--rmdir $MYSQLTEST_VARDIR/another_directory
72+
73+
}
74+
75+
# To test that BACKUP SERVER blocks itself on a release server we rely
76+
# on timing: a BACKUP SERVER set up to fail because the target directory
77+
# exists will fail with error code 21 if it is not blocked, but if
78+
# blocked will time out if max_statement_time is sufficiently short.
79+
#
80+
# This is a heuristic test that might fail depending on how long it takes
81+
# the initial backup to fail; it may give false positives or false
82+
# negatives, and does not test that the lcok is released in the
83+
# finalization phase, but it may uncover bugs that the DEBUG_SYNC test
84+
# would miss.
85+
86+
87+
--mkdir $MYSQLTEST_VARDIR/some_directory
88+
--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
89+
--error 21
90+
evalp BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory';
91+
92+
BACKUP STAGE START;
93+
--connect (backup,localhost,root)
94+
--error ER_STATEMENT_TIMEOUT
95+
evalp SET STATEMENT max_statement_time=0.1 FOR
96+
BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory';
97+
98+
--connection default
99+
100+
--error ER_BACKUP_LOCK_IS_ACTIVE
101+
evalp BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory';
102+
103+
BACKUP STAGE END;
104+
--connection backup
105+
--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
106+
--error 21
107+
evalp SET STATEMENT max_statement_time=0.1 FOR
108+
BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory';
109+
--disconnect backup
110+
--connection default
111+
112+
--rmdir $MYSQLTEST_VARDIR/some_directory
113+
114+
# Test that BACKUP SERVER blocks when a BACKUP STAGE process is in progress
115+
116+
BACKUP STAGE START;
117+
118+
--connect (con1,localhost,root,,test)
119+
let $con1_id= `SELECT CONNECTION_ID()`;
120+
--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
121+
--send_eval BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory'
122+
123+
--connection default
124+
--replace_result $con1_id <con1_id>
125+
eval SET @con1_id = $con1_id;
126+
let $wait_condition=
127+
SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST
128+
WHERE ID = @con1_id AND State = 'Waiting for backup lock';
129+
--source include/wait_condition.inc
130+
--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
131+
SELECT COMMAND, STATE, INFO FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID = @con1_id;
132+
133+
BACKUP STAGE END;
134+
135+
# BACKUP SERVER is no longer blocked
136+
137+
--connection con1
138+
--reap
139+
--disconnect con1
140+
141+
--connection default
142+
--rmdir $MYSQLTEST_VARDIR/some_directory
143+
144+
# Test that BACKUP SERVER will fail when ran on a connection which is
145+
# between BACKUP STAGE START and BACKUP STAGE END
146+
147+
BACKUP STAGE START;
148+
--error ER_BACKUP_LOCK_IS_ACTIVE
149+
--evalp BACKUP SERVER TO '$MYSQLTEST_VARDIR/some_directory'
150+
BACKUP STAGE END;

mysql-test/main/backup_server_locking.test

Lines changed: 0 additions & 31 deletions
This file was deleted.

sql/sql_backup.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ bool Sql_cmd_backup::execute(THD *thd)
7979
thd->variables.lock_wait_timeout))
8080
return true;
8181

82+
DEBUG_SYNC(thd, "after_backup_server_lock_acquired");
83+
8284
if (my_mkdir(target.str, 0755, MYF(MY_WME)))
8385
{
8486
#ifndef _WIN32
@@ -115,6 +117,9 @@ bool Sql_cmd_backup::execute(THD *thd)
115117
/* The final part will not interfere with the use of the server datadir.
116118
Release the locks. */
117119
thd->mdl_context.release_lock(mdl_request.ticket);
120+
121+
DEBUG_SYNC(thd, "backup_server_finalizing");
122+
118123
plugin_foreach_with_mask(thd, backup_finalize, MYSQL_STORAGE_ENGINE_PLUGIN,
119124
PLUGIN_IS_DELETED|PLUGIN_IS_READY, nullptr);
120125
#ifndef _WIN32

0 commit comments

Comments
 (0)