From 6edccb3906472e88b57f7642d496c42078fddc81 Mon Sep 17 00:00:00 2001 From: Andrey Borodin Date: Mon, 26 Jul 2021 15:46:01 +0500 Subject: [PATCH v10 1/4] Introduce TAP test for 2PC with CIC behavior Currently there are two tests: 1. Deterministic reproduction of the bug with 3 psql sessions 2. Probablistic reproduction of the bug with 2 pgbench sessions --- contrib/amcheck/Makefile | 2 +- contrib/amcheck/t/002_cic_2pc.pl | 238 +++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 contrib/amcheck/t/002_cic_2pc.pl diff --git a/contrib/amcheck/Makefile b/contrib/amcheck/Makefile index b82f221e50..5b5427d193 100644 --- a/contrib/amcheck/Makefile +++ b/contrib/amcheck/Makefile @@ -12,7 +12,7 @@ PGFILEDESC = "amcheck - function for verifying relation integrity" REGRESS = check check_btree check_heap -TAP_TESTS = 1 +TAP_TESTS = 2 ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/contrib/amcheck/t/002_cic_2pc.pl b/contrib/amcheck/t/002_cic_2pc.pl new file mode 100644 index 0000000000..541ac2e8a9 --- /dev/null +++ b/contrib/amcheck/t/002_cic_2pc.pl @@ -0,0 +1,238 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +# Test for point-in-time-recovery (PITR) with prepared transactions +use strict; +use warnings; + +use PostgresNode; +use TestLib; + +use Test::More tests => 5; + +my ($node, $result); + +# +# Test set-up +# +$node = get_new_node('CIC_2PC_test'); +$node->init; +$node->append_conf('postgresql.conf', 'max_prepared_transactions = 10'); +$node->append_conf('postgresql.conf', 'lock_timeout = 5000'); +$node->start; +$node->safe_psql('postgres', q(CREATE EXTENSION amcheck)); +$node->safe_psql('postgres', q(CREATE TABLE tbl(i int))); +$node->safe_psql('postgres', q(CREATE INDEX idx on tbl(i))); + + +# +# Test 1. Run 3 overlapping 2PC transactions with concurrent reindex rebuild +# +# We have two concurrent background psqls: main_h for transaction and +# reindex_h for cuncurrent reindex. Also we commit transactions from independent +# psql's sometimes. +# + +my $main_in = ''; +my $main_out = ''; +my $main_timer = IPC::Run::timeout(5); + +my $main_h = $node->background_psql('postgres', \$main_in, \$main_out, $main_timer, + on_error_stop => 1); +$main_in .= q( +BEGIN; +INSERT INTO tbl VALUES(0); +); +$main_h->pump_nb; + +my $reindex_in = ''; +my $reindex_out = ''; +my $reindex_timer = IPC::Run::timeout(5); +my $reindex_h = $node->background_psql('postgres', \$reindex_in, \$reindex_out, $reindex_timer, + on_error_stop => 1); +$reindex_in .= q( +\echo start +REINDEX TABLE CONCURRENTLY tbl; +); +pump $reindex_h until $reindex_out =~ /start/ || $reindex_timer->is_expired; + +$main_in .= q( +PREPARE TRANSACTION 'a'; +); +$main_h->pump_nb; + +$main_in .= q( +BEGIN; +INSERT INTO tbl VALUES(0); +); +$main_h->pump_nb; + +$node->safe_psql('postgres', q(COMMIT PREPARED 'a';)); + +$main_in .= q( +PREPARE TRANSACTION 'b'; +BEGIN; +INSERT INTO tbl VALUES(0); +); +$main_h->pump_nb; + +$node->safe_psql('postgres', q(COMMIT PREPARED 'b';)); + +$main_in .= q( +PREPARE TRANSACTION 'c'; +COMMIT PREPARED 'c'; +); +$main_h->pump_nb; + +$main_h->finish; +$reindex_h->finish; + +$result = $node->psql('postgres', q(SELECT bt_index_check('idx',true))); +is($result, '0', 'bt_index_check checks index'); + + +# +# Test 2. Stress CIC+2PC with pgbench +# + +# Fix broken index first +$node->safe_psql('postgres', q(REINDEX TABLE CONCURRENTLY tbl;)); + +# Run background pgbench with CIC. We cannot mix-in this script into single pgbench: +# CIC will deadlock with itself occasionally. +my $pgbench_in = ''; +my $pgbench_out = ''; +my $pgbench_timer = IPC::Run::timeout(180); +my $pgbench_h = background_pgbench('postgres', \$reindex_in, \$reindex_out, $reindex_timer, + { + '002_pgbench_concurrent_cic' => + q( + REINDEX INDEX CONCURRENTLY idx; + SELECT bt_index_check('idx',true); + ) + }, + '--no-vacuum --client=1 --time=1'); + +# Run pgbench. +pgbench( + '--no-vacuum --client=5 --time=1', + 0, + [qr{actually processed}], + [qr{^$}], + 'concurrent CID and 2PC', + { + '002_pgbench_concurrent_2pc' => + q( + \set txid random(1, 1000000000) + BEGIN; + INSERT INTO tbl VALUES(0); + PREPARE TRANSACTION 'tx:txid'; + COMMIT PREPARED 'tx:txid'; + ) + }); + +$pgbench_h->pump_nb; +$pgbench_h->finish(); +$result = $pgbench_h->result(0); # Need extra check for ($Config{osname} eq "MSWin32") ? +is($result, 0, "pgbench with CIC works"); + +# done +$node->safe_psql('postgres', q(DROP TABLE tbl;)); +$node->stop; +done_testing(); + +# AUX stuff, taken from 001_pgbench_with_server.pl + +# invoke pgbench, with parameters: +# $opts: options as a string to be split on spaces +# $stat: expected exit status +# $out: reference to a regexp list that must match stdout +# $err: reference to a regexp list that must match stderr +# $name: name of test for error messages +# $files: reference to filename/contents dictionary +# @args: further raw options or arguments +sub pgbench +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($opts, $stat, $out, $err, $name, $files, @args) = @_; + my @cmd = ('pgbench', split /\s+/, $opts); + my @filenames = (); + if (defined $files) + { + + # note: files are ordered for determinism + for my $fn (sort keys %$files) + { + my $filename = $node->basedir . '/' . $fn; + push @cmd, '-f', $filename; + + # cleanup file weight + $filename =~ s/\@\d+$//; + + #push @filenames, $filename; + # filenames are expected to be unique on a test + if (-e $filename) + { + ok(0, "$filename must not already exist"); + unlink $filename or die "cannot unlink $filename: $!"; + } + append_to_file($filename, $$files{$fn}); + } + } + + push @cmd, @args; + + $node->command_checks_all(\@cmd, $stat, $out, $err, $name); + + # cleanup? + #unlink @filenames or die "cannot unlink files (@filenames): $!"; + + return; +} + +# invoke pgbench in background alike background_psql +sub background_pgbench +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + my ($dbname, $stdin, $stdout, $timer, $files, $opts) = @_; + + my @cmd = ('pgbench', split /\s+/, $opts); + my @filenames = (); + if (defined $files) + { + + # note: files are ordered for determinism + for my $fn (sort keys %$files) + { + my $filename = $node->basedir . '/' . $fn; + push @cmd, '-f', $filename; + + # cleanup file weight + $filename =~ s/\@\d+$//; + + #push @filenames, $filename; + # filenames are expected to be unique on a test + if (-e $filename) + { + ok(0, "$filename must not already exist"); + unlink $filename or die "cannot unlink $filename: $!"; + } + append_to_file($filename, $$files{$fn}); + } + } + + local %ENV = $node->_get_env(); + + # Ensure there is no data waiting to be sent: + $$stdin = "" if ref($stdin); + # IPC::Run would otherwise append to existing contents: + $$stdout = "" if ref($stdout); + + my $harness = IPC::Run::start \@cmd, + '<', $stdin, '>', $stdout, $timer; + + # Not checking actual input unlike in background_psql + + return $harness; +} \ No newline at end of file -- 2.24.3 (Apple Git-128)