|
1 | 1 | use strict;
|
2 | 2 | use warnings;
|
3 | 3 |
|
| 4 | +use Config; |
4 | 5 | use PostgresNode;
|
5 | 6 | use TestLib;
|
6 |
| -use Test::More tests => 18; |
| 7 | +use Test::More tests => 27; |
| 8 | + |
| 9 | +use constant |
| 10 | +{ |
| 11 | + READ_COMMITTED => 0, |
| 12 | + REPEATABLE_READ => 1, |
| 13 | + SERIALIZABLE => 2, |
| 14 | +}; |
| 15 | + |
| 16 | +my @isolation_level_sql = ('read committed', 'repeatable read', 'serializable'); |
| 17 | +my @isolation_level_abbreviations = ('RC', 'RR', 'S'); |
7 | 18 |
|
8 | 19 | # Test concurrent deadlock updates in table with different default transaction
|
9 | 20 | # isolation levels.
|
|
18 | 29 | append_to_file($script1,
|
19 | 30 | "\\set delta1 random(-5000, 5000)\n"
|
20 | 31 | . "\\set delta2 random(-5000, 5000)\n"
|
21 |
| - . "BEGIN;" |
22 |
| - . "UPDATE xy SET y = y + :delta1 WHERE x = 1;" |
23 |
| - . "UPDATE xy SET y = y + :delta2 WHERE x = 2;" |
| 32 | + . "BEGIN;\n" |
| 33 | + . "UPDATE xy SET y = y + :delta1 WHERE x = 1;\n" |
| 34 | + . "SELECT pg_sleep(20);\n" |
| 35 | + . "UPDATE xy SET y = y + :delta2 WHERE x = 2;\n" |
24 | 36 | . "END;");
|
25 | 37 |
|
26 | 38 | my $script2 = $node->basedir . '/pgbench_script2';
|
27 | 39 | append_to_file($script2,
|
28 | 40 | "\\set delta1 random(-5000, 5000)\n"
|
29 | 41 | . "\\set delta2 random(-5000, 5000)\n"
|
30 |
| - . "BEGIN;" |
31 |
| - . "UPDATE xy SET y = y + :delta2 WHERE x = 2;" |
32 |
| - . "UPDATE xy SET y = y + :delta1 WHERE x = 1;" |
| 42 | + . "BEGIN;\n" |
| 43 | + . "UPDATE xy SET y = y + :delta2 WHERE x = 2;\n" |
| 44 | + . "UPDATE xy SET y = y + :delta1 WHERE x = 1;\n" |
33 | 45 | . "END;");
|
34 | 46 |
|
35 |
| -# Test deadlock transactions with Read committed default isolation level: |
36 |
| -$node->command_like( |
37 |
| - [ qw(pgbench --no-vacuum --client=5 --transactions=10 |
38 |
| - --default-isolation-level=RC --file), $script1, qw(--file), $script2], |
39 |
| - qr{processed: 50/50}, |
40 |
| - 'concurrent deadlock update: Read Committed: check processed transactions'); |
41 |
| - |
42 |
| -$node->command_like( |
43 |
| - [ qw(pgbench --no-vacuum --client=5 --transactions=10 |
44 |
| - --default-isolation-level=RC --file), $script1, qw(--file), $script2], |
45 |
| - qr{deadlock failures: [1-9]\d* \([1-9]\d*\.\d* %\)}, |
46 |
| - 'concurrent deadlock update: Read Committed: check deadlock failures'); |
47 |
| - |
48 |
| -# Test deadlock transactions with Repeatable read default isolation level: |
49 |
| -$node->command_like( |
50 |
| - [ qw(pgbench --no-vacuum --client=5 --transactions=10 |
51 |
| - --default-isolation-level=RR --file), $script1, qw(--file), $script2], |
52 |
| - qr{processed: 50/50}, |
53 |
| - 'concurrent deadlock update: Repeatable Read: check processed transactions'); |
54 |
| - |
55 |
| -$node->command_like( |
56 |
| - [ qw(pgbench --no-vacuum --client=5 --transactions=10 |
57 |
| - --default-isolation-level=RR --file), $script1, qw(--file), $script2], |
58 |
| - qr{deadlock failures: [1-9]\d* \([1-9]\d*\.\d* %\)}, |
59 |
| - 'concurrent deadlock update: Repeatable Read: check deadlock failures'); |
60 |
| - |
61 |
| -# Test deadlock transactions with Serializable default isolation level: |
62 |
| -$node->command_like( |
63 |
| - [ qw(pgbench --no-vacuum --client=5 --transactions=10 |
64 |
| - --default-isolation-level=S --file), $script1, qw(--file), $script2], |
65 |
| - qr{processed: 50/50}, |
66 |
| - 'concurrent deadlock update: Serializable: check processed transactions'); |
67 |
| - |
68 |
| -$node->command_like( |
69 |
| - [ qw(pgbench --no-vacuum --client=5 --transactions=10 |
70 |
| - --default-isolation-level=S --file), $script1, qw(--file), $script2], |
71 |
| - qr{deadlock failures: [1-9]\d* \([1-9]\d*\.\d* %\)}, |
72 |
| - 'concurrent deadlock update: Serializable: check deadlock failures'); |
| 47 | +sub test_pgbench |
| 48 | +{ |
| 49 | + my ($isolation_level) = @_; |
| 50 | + |
| 51 | + my $isolation_level_sql = $isolation_level_sql[$isolation_level]; |
| 52 | + my $isolation_level_abbreviation = |
| 53 | + $isolation_level_abbreviations[$isolation_level]; |
| 54 | + |
| 55 | + local $ENV{PGPORT} = $node->port; |
| 56 | + |
| 57 | + my ($h1, $in1, $out1, $err1); |
| 58 | + my ($h2, $in2, $out2, $err2); |
| 59 | + |
| 60 | + # Run first pgbench |
| 61 | + my @command1 = ( |
| 62 | + qw(pgbench --no-vacuum --transactions=1 --default-isolation-level), |
| 63 | + $isolation_level_abbreviation, |
| 64 | + "--file", |
| 65 | + $script1); |
| 66 | + print "# Running: " . join(" ", @command1) . "\n"; |
| 67 | + $h1 = IPC::Run::start \@command1, \$in1, \$out1, \$err1; |
| 68 | + |
| 69 | + # Let pgbench run first update command in the transaction: |
| 70 | + sleep 10; |
| 71 | + |
| 72 | + # Run second pgbench |
| 73 | + my @command2 = ( |
| 74 | + qw(pgbench --no-vacuum --transactions=1 --default-isolation-level), |
| 75 | + $isolation_level_abbreviation, |
| 76 | + "--file", |
| 77 | + $script2); |
| 78 | + print "# Running: " . join(" ", @command2) . "\n"; |
| 79 | + $h2 = IPC::Run::start \@command2, \$in2, \$out2, \$err2; |
| 80 | + |
| 81 | + # Get all pgbench results |
| 82 | + $h1->pump() until length $out1; |
| 83 | + $h1->finish(); |
| 84 | + |
| 85 | + $h2->pump() until length $out2; |
| 86 | + $h2->finish(); |
| 87 | + |
| 88 | + # On Windows, the exit status of the process is returned directly as the |
| 89 | + # process's exit code, while on Unix, it's returned in the high bits |
| 90 | + # of the exit code (see WEXITSTATUS macro in the standard <sys/wait.h> |
| 91 | + # header file). IPC::Run's result function always returns exit code >> 8, |
| 92 | + # assuming the Unix convention, which will always return 0 on Windows as |
| 93 | + # long as the process was not terminated by an exception. To work around |
| 94 | + # that, use $h->full_result on Windows instead. |
| 95 | + my $result1 = |
| 96 | + ($Config{osname} eq "MSWin32") |
| 97 | + ? ($h1->full_results)[0] |
| 98 | + : $h1->result(0); |
| 99 | + |
| 100 | + my $result2 = |
| 101 | + ($Config{osname} eq "MSWin32") |
| 102 | + ? ($h2->full_results)[0] |
| 103 | + : $h2->result(0); |
| 104 | + |
| 105 | + # Check all pgbench results |
| 106 | + ok(!$result1, "@command1 exit code 0"); |
| 107 | + ok(!$result2, "@command2 exit code 0"); |
| 108 | + |
| 109 | + is($err1, '', "@command1 no stderr"); |
| 110 | + is($err2, '', "@command2 no stderr"); |
| 111 | + |
| 112 | + like($out1, |
| 113 | + qr{default transaction isolation level: $isolation_level_sql}, |
| 114 | + "concurrent deadlock update: " |
| 115 | + . $isolation_level_sql |
| 116 | + . ": pgbench 1: check default isolation level"); |
| 117 | + like($out2, |
| 118 | + qr{default transaction isolation level: $isolation_level_sql}, |
| 119 | + "concurrent deadlock update: " |
| 120 | + . $isolation_level_sql |
| 121 | + . ": pgbench 2: check default isolation level"); |
| 122 | + |
| 123 | + like($out1, |
| 124 | + qr{processed: 1/1}, |
| 125 | + "concurrent deadlock update: " |
| 126 | + . $isolation_level_sql |
| 127 | + . ": pgbench 1: check processed transactions"); |
| 128 | + like($out2, |
| 129 | + qr{processed: 1/1}, |
| 130 | + "concurrent deadlock update: " |
| 131 | + . $isolation_level_sql |
| 132 | + . ": pgbench 2: check processed transactions"); |
| 133 | + |
| 134 | + # First or second pgbench should get a deadlock error |
| 135 | + like($out1 . $out2, |
| 136 | + qr{deadlock failures: 1 \(100\.000 %\)}, |
| 137 | + "concurrent deadlock update: " |
| 138 | + . $isolation_level_sql |
| 139 | + . ": check deadlock failures"); |
| 140 | +} |
| 141 | + |
| 142 | +test_pgbench(READ_COMMITTED); |
| 143 | +test_pgbench(REPEATABLE_READ); |
| 144 | +test_pgbench(SERIALIZABLE); |
0 commit comments