Skip to content

Commit 21e1844

Browse files
committed
Add tests for recovery deadlock conflicts.
The recovery conflict tests added in 9f8a050 surfaced a bug in the interaction between buffer pin and deadlock recovery conflicts. To make sure that the bugfix won't break deadlock conflict detection, add a test for that scenario. 031_recovery_conflict.pl will later be backpatched, with this included. Discussion: https://postgr.es/m/20220413002626.udl7lll7f3o7nre7@alap3.anarazel.de
1 parent 7307988 commit 21e1844

File tree

1 file changed

+79
-7
lines changed

1 file changed

+79
-7
lines changed

src/test/recovery/t/031_recovery_conflict.pl

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
# recovery conflict is detected Also, test that statistics in
55
# pg_stat_database_conflicts are populated correctly
66

7-
# TODO: add a test for deadlock recovery conflicts.
8-
97
use strict;
108
use warnings;
119
use PostgreSQL::Test::Cluster;
@@ -24,6 +22,9 @@
2422
allow_in_place_tablespaces = on
2523
log_temp_files = 0
2624
25+
# for deadlock test
26+
max_prepared_transactions = 10
27+
2728
# wait some to test the wait paths as well, but not long for obvious reasons
2829
max_standby_streaming_delay = 50ms
2930
@@ -55,9 +56,13 @@
5556

5657
# test schema / data
5758
my $table1 = "test_recovery_conflict_table1";
58-
$node_primary->safe_psql($test_db, qq[CREATE TABLE ${table1}(a int, b int);]);
59-
$node_primary->safe_psql($test_db,
60-
qq[INSERT INTO $table1 SELECT i % 3, 0 FROM generate_series(1,20) i]);
59+
my $table2 = "test_recovery_conflict_table2";
60+
$node_primary->safe_psql(
61+
$test_db, qq[
62+
CREATE TABLE ${table1}(a int, b int);
63+
INSERT INTO $table1 SELECT i % 3, 0 FROM generate_series(1,20) i;
64+
CREATE TABLE ${table2}(a int, b int);
65+
]);
6166
my $primary_lsn = $node_primary->lsn('flush');
6267
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
6368

@@ -217,6 +222,67 @@
217222
check_conflict_stat("tablespace");
218223

219224

225+
## RECOVERY CONFLICT 5: Deadlock
226+
$sect = "startup deadlock";
227+
$expected_conflicts++;
228+
229+
# Generate a few dead rows, to later be cleaned up by vacuum. Then acquire a
230+
# lock on another relation in a prepared xact, so it's held continuously by
231+
# the startup process. The standby psql will block acquiring that lock while
232+
# holding a pin that vacuum needs, triggering the deadlock.
233+
$node_primary->safe_psql(
234+
$test_db,
235+
qq[
236+
CREATE TABLE $table1(a int, b int);
237+
INSERT INTO $table1 VALUES (1);
238+
BEGIN;
239+
INSERT INTO $table1(a) SELECT generate_series(1, 100) i;
240+
ROLLBACK;
241+
BEGIN;
242+
LOCK TABLE $table2;
243+
PREPARE TRANSACTION 'lock';
244+
INSERT INTO $table1(a) VALUES (170);
245+
SELECT txid_current();
246+
]);
247+
248+
$primary_lsn = $node_primary->lsn('flush');
249+
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
250+
251+
$psql_standby{stdin} .= qq[
252+
BEGIN;
253+
-- hold pin
254+
DECLARE $cursor1 CURSOR FOR SELECT a FROM $table1;
255+
FETCH FORWARD FROM $cursor1;
256+
-- wait for lock held by prepared transaction
257+
SELECT * FROM $table2;
258+
];
259+
ok( pump_until(
260+
$psql_standby{run}, $psql_timeout,
261+
\$psql_standby{stdout}, qr/^1$/m,),
262+
"$sect: cursor holding conflicting pin, also waiting for lock, established"
263+
);
264+
265+
# just to make sure we're waiting for lock already
266+
ok( $node_standby->poll_query_until(
267+
'postgres', qq[
268+
SELECT 'waiting' FROM pg_locks WHERE locktype = 'relation' AND NOT granted;
269+
], 'waiting'),
270+
"$sect: lock acquisition is waiting");
271+
272+
# VACUUM will prune away rows, causing a buffer pin conflict, while standby
273+
# psql is waiting on lock
274+
$node_primary->safe_psql($test_db, qq[VACUUM $table1;]);
275+
$primary_lsn = $node_primary->lsn('flush');
276+
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
277+
278+
check_conflict_log("User transaction caused buffer deadlock with recovery.");
279+
reconnect_and_clear();
280+
check_conflict_stat("deadlock");
281+
282+
# clean up for next tests
283+
$node_primary->safe_psql($test_db, qq[ROLLBACK PREPARED 'lock';]);
284+
285+
220286
# Check that expected number of conflicts show in pg_stat_database. Needs to
221287
# be tested before database is dropped, for obvious reasons.
222288
is( $node_standby->safe_psql(
@@ -226,7 +292,7 @@
226292
qq[$expected_conflicts recovery conflicts shown in pg_stat_database]);
227293

228294

229-
## RECOVERY CONFLICT 5: Database conflict
295+
## RECOVERY CONFLICT 6: Database conflict
230296
$sect = "database conflict";
231297

232298
$node_primary->safe_psql('postgres', qq[DROP DATABASE $test_db;]);
@@ -259,7 +325,13 @@ sub pump_until_standby
259325

260326
sub reconnect_and_clear
261327
{
262-
$psql_standby{stdin} .= "\\q\n";
328+
# If psql isn't dead already, tell it to quit as \q, when already dead,
329+
# causes IPC::Run to unhelpfully error out with "ack Broken pipe:".
330+
$psql_standby{run}->pump_nb();
331+
if ($psql_standby{run}->pumpable())
332+
{
333+
$psql_standby{stdin} .= "\\q\n";
334+
}
263335
$psql_standby{run}->finish;
264336

265337
# restart

0 commit comments

Comments
 (0)