1
+ # Tests dedicated to two-phase commit in recovery
2
+ use strict;
3
+ use warnings;
4
+ use PostgresNode;
5
+ use TestLib;
6
+ use Test::Moretests => 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" );