|
4 | 4 | # recovery conflict is detected Also, test that statistics in
|
5 | 5 | # pg_stat_database_conflicts are populated correctly
|
6 | 6 |
|
7 |
| -# TODO: add a test for deadlock recovery conflicts. |
8 |
| - |
9 | 7 | use strict;
|
10 | 8 | use warnings;
|
11 | 9 | use PostgreSQL::Test::Cluster;
|
|
24 | 22 | allow_in_place_tablespaces = on
|
25 | 23 | log_temp_files = 0
|
26 | 24 |
|
| 25 | +# for deadlock test |
| 26 | +max_prepared_transactions = 10 |
| 27 | +
|
27 | 28 | # wait some to test the wait paths as well, but not long for obvious reasons
|
28 | 29 | max_standby_streaming_delay = 50ms
|
29 | 30 |
|
|
55 | 56 |
|
56 | 57 | # test schema / data
|
57 | 58 | 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 | +]); |
61 | 66 | my $primary_lsn = $node_primary->lsn('flush');
|
62 | 67 | $node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
63 | 68 |
|
|
217 | 222 | check_conflict_stat("tablespace");
|
218 | 223 |
|
219 | 224 |
|
| 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 | + |
220 | 286 | # Check that expected number of conflicts show in pg_stat_database. Needs to
|
221 | 287 | # be tested before database is dropped, for obvious reasons.
|
222 | 288 | is( $node_standby->safe_psql(
|
|
226 | 292 | qq[$expected_conflicts recovery conflicts shown in pg_stat_database]);
|
227 | 293 |
|
228 | 294 |
|
229 |
| -## RECOVERY CONFLICT 5: Database conflict |
| 295 | +## RECOVERY CONFLICT 6: Database conflict |
230 | 296 | $sect = "database conflict";
|
231 | 297 |
|
232 | 298 | $node_primary->safe_psql('postgres', qq[DROP DATABASE $test_db;]);
|
@@ -259,7 +325,13 @@ sub pump_until_standby
|
259 | 325 |
|
260 | 326 | sub reconnect_and_clear
|
261 | 327 | {
|
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 | + } |
263 | 335 | $psql_standby{run}->finish;
|
264 | 336 |
|
265 | 337 | # restart
|
|
0 commit comments