1+ # Tests dedicated to two-phase commit in recovery
2+ use strict;
3+ use warnings;
4+ use PostgresNode;
5+ use TestLib;
6+ use Test::More tests => 13;
7+
8+ # Setup master node
9+ my $node_master = get_new_node(" master" );
10+ $node_master -> init(allows_streaming => 1);
11+ $node_master -> append_conf(' postgresql.conf' , qq(
12+ max_prepared_transactions = 10
13+ ) );
14+ $node_master -> start;
15+ $node_master -> backup(' master_backup' );
16+ $node_master -> psql(' postgres' , " create table t(id int)" );
17+
18+ # Setup master node
19+ my $node_slave = get_new_node(' slave' );
20+ $node_slave -> init_from_backup($node_master , ' master_backup' , has_streaming => 1);
21+ $node_slave -> start;
22+
23+ # Switch to synchronous replication
24+ $node_master -> append_conf(' postgresql.conf' , qq(
25+ synchronous_standby_names = '*'
26+ ) );
27+ $node_master -> psql(' postgres' , " select pg_reload_conf()" );
28+
29+ my $psql_out = ' ' ;
30+ my $psql_rc = ' ' ;
31+
32+ # ##############################################################################
33+ # Check that we can commit and abort tx after soft restart.
34+ # Here checkpoint happens before shutdown and no WAL replay will occur at next
35+ # startup. In this case postgres re-create shared-memory state from twophase
36+ # files.
37+ # ##############################################################################
38+
39+ $node_master -> psql(' postgres' , "
40+ begin;
41+ insert into t values (42);
42+ savepoint s1;
43+ insert into t values (43);
44+ prepare transaction 'x';
45+ begin;
46+ insert into t values (142);
47+ savepoint s1;
48+ insert into t values (143);
49+ prepare transaction 'y';" );
50+ $node_master -> stop;
51+ $node_master -> start;
52+
53+ $psql_rc = $node_master -> psql(' postgres' , " commit prepared 'x'" );
54+ is($psql_rc , ' 0' , ' Commit prepared transaction after restart.' );
55+
56+ $psql_rc = $node_master -> psql(' postgres' , " rollback prepared 'y'" );
57+ is($psql_rc , ' 0' , ' Rollback prepared transaction after restart.' );
58+
59+ # ##############################################################################
60+ # Check that we can commit and abort after hard restart.
61+ # At next startup, WAL replay will re-create shared memory state for prepared
62+ # transaction using dedicated WAL records.
63+ # ##############################################################################
64+
65+ $node_master -> psql(' postgres' , "
66+ checkpoint;
67+ begin;
68+ insert into t values (42);
69+ savepoint s1;
70+ insert into t values (43);
71+ prepare transaction 'x';
72+ begin;
73+ insert into t values (142);
74+ savepoint s1;
75+ insert into t values (143);
76+ prepare transaction 'y';" );
77+ $node_master -> teardown_node;
78+ $node_master -> start;
79+
80+ $psql_rc = $node_master -> psql(' postgres' , " commit prepared 'x'" );
81+ is($psql_rc , ' 0' , ' Commit prepared tx after teardown.' );
82+
83+ $psql_rc = $node_master -> psql(' postgres' , " rollback prepared 'y'" );
84+ is($psql_rc , ' 0' , ' Rollback prepared transaction after teardown.' );
85+
86+ # ##############################################################################
87+ # Check that WAL replay can handle several transactions with same name GID.
88+ # ##############################################################################
89+
90+ $node_master -> psql(' postgres' , "
91+ checkpoint;
92+ begin;
93+ insert into t values (42);
94+ savepoint s1;
95+ insert into t values (43);
96+ prepare transaction 'x';
97+ commit prepared 'x';
98+ begin;
99+ insert into t values (42);
100+ savepoint s1;
101+ insert into t values (43);
102+ prepare transaction 'x';" );
103+ $node_master -> teardown_node;
104+ $node_master -> start;
105+
106+ $psql_rc = $node_master -> psql(' postgres' , " commit prepared 'x'" );
107+ is($psql_rc , ' 0' , ' Replay several transactions with same GID.' );
108+
109+ # ##############################################################################
110+ # Check that WAL replay cleans up its shared memory state and releases locks
111+ # while replaying transaction commits.
112+ # ##############################################################################
113+
114+ $node_master -> psql(' postgres' , "
115+ begin;
116+ insert into t values (42);
117+ savepoint s1;
118+ insert into t values (43);
119+ prepare transaction 'x';
120+ commit prepared 'x';" );
121+ $node_master -> teardown_node;
122+ $node_master -> start;
123+ $psql_rc = $node_master -> psql(' postgres' , " begin;
124+ insert into t values (42);
125+ savepoint s1;
126+ insert into t values (43);
127+ -- This prepare can fail due to conflicting GID or locks conflicts if
128+ -- replay did not fully cleanup its state on previous commit.
129+ prepare transaction 'x';" );
130+ is($psql_rc , ' 0' , " Cleanup of shared memory state for 2PC commit" );
131+
132+ $node_master -> psql(' postgres' , " commit prepared 'x'" );
133+
134+ # ##############################################################################
135+ # Check that WAL replay will cleanup its shared memory state on running slave.
136+ # ##############################################################################
137+
138+ $node_master -> psql(' postgres' , "
139+ begin;
140+ insert into t values (42);
141+ savepoint s1;
142+ insert into t values (43);
143+ prepare transaction 'x';
144+ commit prepared 'x';" );
145+ $node_slave -> psql(' postgres' , " select count(*) from pg_prepared_xacts;" ,
146+ stdout => \$psql_out );
147+ is($psql_out , ' 0' ,
148+ " Cleanup of shared memory state on running standby without checkpoint." );
149+
150+ # ##############################################################################
151+ # Same as in previous case, but let's force checkpoint on slave between
152+ # prepare and commit to use on-disk twophase files.
153+ # ##############################################################################
154+
155+ $node_master -> psql(' postgres' , "
156+ begin;
157+ insert into t values (42);
158+ savepoint s1;
159+ insert into t values (43);
160+ prepare transaction 'x';" );
161+ $node_slave -> psql(' postgres' , " checkpoint;" );
162+ $node_master -> psql(' postgres' , " commit prepared 'x';" );
163+ $node_slave -> psql(' postgres' , " select count(*) from pg_prepared_xacts;" ,
164+ stdout => \$psql_out );
165+ is($psql_out , ' 0' ,
166+ " Cleanup of shared memory state on running standby after checkpoint." );
167+
168+ # ##############################################################################
169+ # Check that prepared transactions can be committed on promoted slave.
170+ # ##############################################################################
171+
172+ $node_master -> psql(' postgres' , "
173+ begin;
174+ insert into t values (42);
175+ savepoint s1;
176+ insert into t values (43);
177+ prepare transaction 'x';" );
178+ $node_master -> teardown_node;
179+ $node_slave -> promote;
180+ $node_slave -> poll_query_until(' postgres' , " SELECT pg_is_in_recovery() <> true" );
181+
182+ $psql_rc = $node_slave -> psql(' postgres' , " commit prepared 'x';" );
183+ is($psql_rc , ' 0' , " Restore of prepared transaction on promoted slave." );
184+
185+ # change roles
186+ ($node_master , $node_slave ) = ($node_slave , $node_master );
187+ $node_slave -> enable_streaming($node_master );
188+ $node_slave -> append_conf(' recovery.conf' , qq(
189+ recovery_target_timeline='latest'
190+ ) );
191+ $node_slave -> start;
192+
193+ # ##############################################################################
194+ # Check that prepared transactions are replayed after soft restart of standby
195+ # while master is down. Since standby knows that master is down it uses
196+ # different code path on start to be sure that the status of transactions is
197+ # consistent.
198+ # ##############################################################################
199+
200+ $node_master -> psql(' postgres' , "
201+ begin;
202+ insert into t values (42);
203+ savepoint s1;
204+ insert into t values (43);
205+ prepare transaction 'x';" );
206+ $node_master -> stop;
207+ $node_slave -> restart;
208+ $node_slave -> promote;
209+ $node_slave -> poll_query_until(' postgres' , " SELECT pg_is_in_recovery() <> true" );
210+
211+ $node_slave -> psql(' postgres' , " select count(*) from pg_prepared_xacts" ,
212+ stdout => \$psql_out );
213+ is($psql_out , ' 1' ,
214+ " Restore prepared transactions from files with master down." );
215+
216+ # restore state
217+ ($node_master , $node_slave ) = ($node_slave , $node_master );
218+ $node_slave -> enable_streaming($node_master );
219+ $node_slave -> append_conf(' recovery.conf' , qq(
220+ recovery_target_timeline='latest'
221+ ) );
222+ $node_slave -> start;
223+ $node_master -> psql(' postgres' , " commit prepared 'x'" );
224+
225+ # ##############################################################################
226+ # Check that prepared transactions are correctly replayed after slave hard
227+ # restart while master is down.
228+ # ##############################################################################
229+
230+ $node_master -> psql(' postgres' , "
231+ begin;
232+ insert into t values (242);
233+ savepoint s1;
234+ insert into t values (243);
235+ prepare transaction 'x';
236+ " );
237+ $node_master -> stop;
238+ $node_slave -> teardown_node;
239+ $node_slave -> start;
240+ $node_slave -> promote;
241+ $node_slave -> poll_query_until(' postgres' ,
242+ " SELECT pg_is_in_recovery() <> true" );
243+
244+ $node_slave -> psql(' postgres' , " select count(*) from pg_prepared_xacts" ,
245+ stdout => \$psql_out );
246+ is($psql_out , ' 1' ,
247+ " Restore prepared transactions from records with master down." );
248+
249+ # restore state
250+ ($node_master , $node_slave ) = ($node_slave , $node_master );
251+ $node_slave -> enable_streaming($node_master );
252+ $node_slave -> append_conf(' recovery.conf' , qq(
253+ recovery_target_timeline='latest'
254+ ) );
255+ $node_slave -> start;
256+ $node_master -> psql(' postgres' , " commit prepared 'x'" );
257+
258+
259+ # ##############################################################################
260+ # Check for a lock confcict between prepared tx with DDL inside and replay of
261+ # XLOG_STANDBY_LOCK wal record.
262+ # ##############################################################################
263+
264+ $node_master -> psql(' postgres' , "
265+ begin;
266+ create table t2(id int);
267+ savepoint s1;
268+ insert into t2 values (42);
269+ prepare transaction 'x';
270+ -- checkpoint will issue XLOG_STANDBY_LOCK that can conflict with lock
271+ -- held by 'create table' statement
272+ checkpoint;
273+ commit prepared 'x';" );
274+
275+ $node_slave -> psql(' postgres' , " select count(*) from pg_prepared_xacts" ,
276+ stdout => \$psql_out );
277+ is($psql_out , ' 0' , " Replay prepared transaction with DDL." );
278+
279+
280+ # ##############################################################################
281+ # Check that replay will correctly set SUBTRANS and properly andvance nextXid
282+ # so it won't conflict with savepoint xids.
283+ # ##############################################################################
284+
285+ $node_master -> psql(' postgres' , "
286+ begin;
287+ delete from t;
288+ insert into t values (43);
289+ savepoint s1;
290+ insert into t values (43);
291+ savepoint s2;
292+ insert into t values (43);
293+ savepoint s3;
294+ insert into t values (43);
295+ savepoint s4;
296+ insert into t values (43);
297+ savepoint s5;
298+ insert into t values (43);
299+ prepare transaction 'x';
300+ checkpoint;" );
301+
302+ $node_master -> stop;
303+ $node_master -> start;
304+ $node_master -> psql(' postgres' , "
305+ -- here we can get xid of previous savepoint if nextXid
306+ -- wasn't properly advanced
307+ begin;
308+ insert into t values (142);
309+ abort;
310+ commit prepared 'x';" );
311+
312+ $node_master -> psql(' postgres' , " select count(*) from t" ,
313+ stdout => \$psql_out );
314+ is($psql_out , ' 6' , " Check nextXid handling for prepared subtransactions" );
0 commit comments