21
21
#endif
22
22
23
23
#include "libpq-fe.h"
24
+ #include "pqexpbuffer.h"
24
25
25
26
#include "isolationtester.h"
26
27
31
32
* connections represent spec-defined sessions.
32
33
*/
33
34
static PGconn * * conns = NULL ;
34
- static const char * * backend_ids = NULL ;
35
+ static const char * * backend_pids = NULL ;
35
36
static int nconns = 0 ;
36
37
37
38
static void run_all_permutations (TestSpec * testspec );
@@ -67,6 +68,7 @@ main(int argc, char **argv)
67
68
TestSpec * testspec ;
68
69
int i ;
69
70
PGresult * res ;
71
+ PQExpBufferData wait_query ;
70
72
71
73
/*
72
74
* If the user supplies a parameter on the command line, use it as the
@@ -89,7 +91,7 @@ main(int argc, char **argv)
89
91
*/
90
92
nconns = 1 + testspec -> nsessions ;
91
93
conns = calloc (nconns , sizeof (PGconn * ));
92
- backend_ids = calloc (nconns , sizeof (* backend_ids ));
94
+ backend_pids = calloc (nconns , sizeof (* backend_pids ));
93
95
for (i = 0 ; i < nconns ; i ++ )
94
96
{
95
97
conns [i ] = PQconnectdb (conninfo );
@@ -112,23 +114,22 @@ main(int argc, char **argv)
112
114
}
113
115
PQclear (res );
114
116
115
- /* Get the backend ID for lock wait checking. */
116
- res = PQexec (conns [i ], "SELECT i FROM pg_stat_get_backend_idset() t(i) "
117
- "WHERE pg_stat_get_backend_pid(i) = pg_backend_pid()" );
117
+ /* Get the backend pid for lock wait checking. */
118
+ res = PQexec (conns [i ], "SELECT pg_backend_pid()" );
118
119
if (PQresultStatus (res ) == PGRES_TUPLES_OK )
119
120
{
120
121
if (PQntuples (res ) == 1 && PQnfields (res ) == 1 )
121
- backend_ids [i ] = strdup (PQgetvalue (res , 0 , 0 ));
122
+ backend_pids [i ] = strdup (PQgetvalue (res , 0 , 0 ));
122
123
else
123
124
{
124
- fprintf (stderr , "backend id query returned %d rows and %d columns, expected 1 row and 1 column" ,
125
+ fprintf (stderr , "backend pid query returned %d rows and %d columns, expected 1 row and 1 column" ,
125
126
PQntuples (res ), PQnfields (res ));
126
127
exit_nicely ();
127
128
}
128
129
}
129
130
else
130
131
{
131
- fprintf (stderr , "backend id query failed: %s" ,
132
+ fprintf (stderr , "backend pid query failed: %s" ,
132
133
PQerrorMessage (conns [i ]));
133
134
exit_nicely ();
134
135
}
@@ -145,15 +146,95 @@ main(int argc, char **argv)
145
146
session -> steps [stepindex ]-> session = i ;
146
147
}
147
148
148
- res = PQprepare (conns [0 ], PREP_WAITING ,
149
- "SELECT 1 WHERE pg_stat_get_backend_waiting($1)" , 0 , NULL );
149
+ /*
150
+ * Build the query we'll use to detect lock contention among sessions in
151
+ * the test specification. Most of the time, we could get away with
152
+ * simply checking whether a session is waiting for *any* lock: we don't
153
+ * exactly expect concurrent use of test tables. However, autovacuum will
154
+ * occasionally take AccessExclusiveLock to truncate a table, and we must
155
+ * ignore that transient wait.
156
+ */
157
+ initPQExpBuffer (& wait_query );
158
+ appendPQExpBufferStr (& wait_query ,
159
+ "SELECT 1 FROM pg_locks holder, pg_locks waiter "
160
+ "WHERE NOT waiter.granted AND waiter.pid = $1 "
161
+ "AND holder.granted "
162
+ "AND holder.pid <> $1 AND holder.pid IN (" );
163
+ /* The spec syntax requires at least one session; assume that here. */
164
+ appendPQExpBuffer (& wait_query , "%s" , backend_pids [1 ]);
165
+ for (i = 2 ; i < nconns ; i ++ )
166
+ appendPQExpBuffer (& wait_query , ", %s" , backend_pids [i ]);
167
+ appendPQExpBufferStr (& wait_query ,
168
+ ") "
169
+
170
+ "AND holder.mode = ANY (CASE waiter.mode "
171
+ "WHEN 'AccessShareLock' THEN ARRAY["
172
+ "'AccessExclusiveLock'] "
173
+ "WHEN 'RowShareLock' THEN ARRAY["
174
+ "'ExclusiveLock',"
175
+ "'AccessExclusiveLock'] "
176
+ "WHEN 'RowExclusiveLock' THEN ARRAY["
177
+ "'ShareLock',"
178
+ "'ShareRowExclusiveLock',"
179
+ "'ExclusiveLock',"
180
+ "'AccessExclusiveLock'] "
181
+ "WHEN 'ShareUpdateExclusiveLock' THEN ARRAY["
182
+ "'ShareUpdateExclusiveLock',"
183
+ "'ShareLock',"
184
+ "'ShareRowExclusiveLock',"
185
+ "'ExclusiveLock',"
186
+ "'AccessExclusiveLock'] "
187
+ "WHEN 'ShareLock' THEN ARRAY["
188
+ "'RowExclusiveLock',"
189
+ "'ShareUpdateExclusiveLock',"
190
+ "'ShareRowExclusiveLock',"
191
+ "'ExclusiveLock',"
192
+ "'AccessExclusiveLock'] "
193
+ "WHEN 'ShareRowExclusiveLock' THEN ARRAY["
194
+ "'RowExclusiveLock',"
195
+ "'ShareUpdateExclusiveLock',"
196
+ "'ShareLock',"
197
+ "'ShareRowExclusiveLock',"
198
+ "'ExclusiveLock',"
199
+ "'AccessExclusiveLock'] "
200
+ "WHEN 'ExclusiveLock' THEN ARRAY["
201
+ "'RowShareLock',"
202
+ "'RowExclusiveLock',"
203
+ "'ShareUpdateExclusiveLock',"
204
+ "'ShareLock',"
205
+ "'ShareRowExclusiveLock',"
206
+ "'ExclusiveLock',"
207
+ "'AccessExclusiveLock'] "
208
+ "WHEN 'AccessExclusiveLock' THEN ARRAY["
209
+ "'AccessShareLock',"
210
+ "'RowShareLock',"
211
+ "'RowExclusiveLock',"
212
+ "'ShareUpdateExclusiveLock',"
213
+ "'ShareLock',"
214
+ "'ShareRowExclusiveLock',"
215
+ "'ExclusiveLock',"
216
+ "'AccessExclusiveLock'] END) "
217
+
218
+ "AND holder.locktype IS NOT DISTINCT FROM waiter.locktype "
219
+ "AND holder.database IS NOT DISTINCT FROM waiter.database "
220
+ "AND holder.relation IS NOT DISTINCT FROM waiter.relation "
221
+ "AND holder.page IS NOT DISTINCT FROM waiter.page "
222
+ "AND holder.tuple IS NOT DISTINCT FROM waiter.tuple "
223
+ "AND holder.virtualxid IS NOT DISTINCT FROM waiter.virtualxid "
224
+ "AND holder.transactionid IS NOT DISTINCT FROM waiter.transactionid "
225
+ "AND holder.classid IS NOT DISTINCT FROM waiter.classid "
226
+ "AND holder.objid IS NOT DISTINCT FROM waiter.objid "
227
+ "AND holder.objsubid IS NOT DISTINCT FROM waiter.objsubid " );
228
+
229
+ res = PQprepare (conns [0 ], PREP_WAITING , wait_query .data , 0 , NULL );
150
230
if (PQresultStatus (res ) != PGRES_COMMAND_OK )
151
231
{
152
232
fprintf (stderr , "prepare of lock wait query failed: %s" ,
153
233
PQerrorMessage (conns [0 ]));
154
234
exit_nicely ();
155
235
}
156
236
PQclear (res );
237
+ termPQExpBuffer (& wait_query );
157
238
158
239
/*
159
240
* Run the permutations specified in the spec, or all if none were
@@ -411,9 +492,7 @@ run_permutation(TestSpec * testspec, int nsteps, Step ** steps)
411
492
* Our caller already sent the query associated with this step. Wait for it
412
493
* to either complete or (if given the STEP_NONBLOCK flag) to block while
413
494
* waiting for a lock. We assume that any lock wait will persist until we
414
- * have executed additional steps in the permutation. This is not fully
415
- * robust -- a concurrent autovacuum could briefly take a lock with which we
416
- * conflict. The risk may be low enough to discount.
495
+ * have executed additional steps in the permutation.
417
496
*
418
497
* When calling this function on behalf of a given step for a second or later
419
498
* time, pass the STEP_RETRY flag. This only affects the messages printed.
@@ -450,7 +529,7 @@ try_complete_step(Step *step, int flags)
450
529
int ntuples ;
451
530
452
531
res = PQexecPrepared (conns [0 ], PREP_WAITING , 1 ,
453
- & backend_ids [step -> session + 1 ],
532
+ & backend_pids [step -> session + 1 ],
454
533
NULL , NULL , 0 );
455
534
if (PQresultStatus (res ) != PGRES_TUPLES_OK )
456
535
{
0 commit comments