aboutsummaryrefslogtreecommitdiff
path: root/src/test/isolation/isolationtester.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/isolation/isolationtester.c')
-rw-r--r--src/test/isolation/isolationtester.c215
1 files changed, 185 insertions, 30 deletions
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 0f77917fb53..126e1856f08 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -11,18 +11,39 @@
#include <windows.h>
#endif
+#ifndef WIN32
+#include <sys/time.h>
+#include <unistd.h>
+#endif /* ! WIN32 */
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
#include "libpq-fe.h"
#include "isolationtester.h"
+#define PREP_WAITING "isolationtester_waiting"
+
+/*
+ * conns[0] is the global setup, teardown, and watchdog connection. Additional
+ * connections represent spec-defined sessions.
+ */
static PGconn **conns = NULL;
+static const char **backend_ids = NULL;
static int nconns = 0;
static void run_all_permutations(TestSpec * testspec);
-static void run_all_permutations_recurse(TestSpec * testspec, int nsteps, Step ** steps);
+static void run_all_permutations_recurse(TestSpec * testspec, int nsteps,
+ Step ** steps);
static void run_named_permutations(TestSpec * testspec);
static void run_permutation(TestSpec * testspec, int nsteps, Step ** steps);
+#define STEP_NONBLOCK 0x1 /* return 0 as soon as cmd waits for a lock */
+#define STEP_RETRY 0x2 /* this is a retry of a previously-waiting cmd */
+static bool try_complete_step(Step *step, int flags);
+
static int step_qsort_cmp(const void *a, const void *b);
static int step_bsearch_cmp(const void *a, const void *b);
@@ -45,6 +66,7 @@ main(int argc, char **argv)
const char *conninfo;
TestSpec *testspec;
int i;
+ PGresult *res;
/*
* If the user supplies a parameter on the command line, use it as the
@@ -61,13 +83,15 @@ main(int argc, char **argv)
testspec = &parseresult;
printf("Parsed test spec with %d sessions\n", testspec->nsessions);
- /* Establish connections to the database, one for each session */
- nconns = testspec->nsessions;
+ /*
+ * Establish connections to the database, one for each session and an extra
+ * for lock wait detection and global work.
+ */
+ nconns = 1 + testspec->nsessions;
conns = calloc(nconns, sizeof(PGconn *));
- for (i = 0; i < testspec->nsessions; i++)
+ backend_ids = calloc(nconns, sizeof(*backend_ids));
+ for (i = 0; i < nconns; i++)
{
- PGresult *res;
-
conns[i] = PQconnectdb(conninfo);
if (PQstatus(conns[i]) != CONNECTION_OK)
{
@@ -87,6 +111,28 @@ main(int argc, char **argv)
exit_nicely();
}
PQclear(res);
+
+ /* Get the backend ID for lock wait checking. */
+ res = PQexec(conns[i], "SELECT i FROM pg_stat_get_backend_idset() t(i) "
+ "WHERE pg_stat_get_backend_pid(i) = pg_backend_pid()");
+ if (PQresultStatus(res) == PGRES_TUPLES_OK)
+ {
+ if (PQntuples(res) == 1 && PQnfields(res) == 1)
+ backend_ids[i] = strdup(PQgetvalue(res, 0, 0));
+ else
+ {
+ fprintf(stderr, "backend id query returned %d rows and %d columns, expected 1 row and 1 column",
+ PQntuples(res), PQnfields(res));
+ exit_nicely();
+ }
+ }
+ else
+ {
+ fprintf(stderr, "backend id query failed: %s",
+ PQerrorMessage(conns[i]));
+ exit_nicely();
+ }
+ PQclear(res);
}
/* Set the session index fields in steps. */
@@ -99,6 +145,16 @@ main(int argc, char **argv)
session->steps[stepindex]->session = i;
}
+ res = PQprepare(conns[0], PREP_WAITING,
+ "SELECT 1 WHERE pg_stat_get_backend_waiting($1)", 0, NULL);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "prepare of lock wait query failed: %s",
+ PQerrorMessage(conns[0]));
+ exit_nicely();
+ }
+ PQclear(res);
+
/*
* Run the permutations specified in the spec, or all if none were
* explicitly specified.
@@ -254,6 +310,7 @@ run_permutation(TestSpec * testspec, int nsteps, Step ** steps)
{
PGresult *res;
int i;
+ Step *waiting = NULL;
printf("\nstarting permutation:");
for (i = 0; i < nsteps; i++)
@@ -277,12 +334,12 @@ run_permutation(TestSpec * testspec, int nsteps, Step ** steps)
{
if (testspec->sessions[i]->setupsql)
{
- res = PQexec(conns[i], testspec->sessions[i]->setupsql);
+ res = PQexec(conns[i + 1], testspec->sessions[i]->setupsql);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "setup of session %s failed: %s",
testspec->sessions[i]->name,
- PQerrorMessage(conns[0]));
+ PQerrorMessage(conns[i + 1]));
exit_nicely();
}
PQclear(res);
@@ -292,44 +349,43 @@ run_permutation(TestSpec * testspec, int nsteps, Step ** steps)
/* Perform steps */
for (i = 0; i < nsteps; i++)
{
- Step *step = steps[i];
+ Step *step = steps[i];
- printf("step %s: %s\n", step->name, step->sql);
- res = PQexec(conns[step->session], step->sql);
-
- switch (PQresultStatus(res))
+ if (!PQsendQuery(conns[1 + step->session], step->sql))
{
- case PGRES_COMMAND_OK:
- break;
-
- case PGRES_TUPLES_OK:
- printResultSet(res);
- break;
+ fprintf(stdout, "failed to send query: %s\n",
+ PQerrorMessage(conns[1 + step->session]));
+ exit_nicely();
+ }
- case PGRES_FATAL_ERROR:
- /* Detail may contain xid values, so just show primary. */
- printf("%s: %s\n", PQresultErrorField(res, PG_DIAG_SEVERITY),
- PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY));
- break;
+ if (waiting != NULL)
+ {
+ /* Some other step is already waiting: just block. */
+ try_complete_step(step, 0);
- default:
- printf("unexpected result status: %s\n",
- PQresStatus(PQresultStatus(res)));
+ /* See if this step unblocked the waiting step. */
+ if (!try_complete_step(waiting, STEP_NONBLOCK | STEP_RETRY))
+ waiting = NULL;
}
- PQclear(res);
+ else if (try_complete_step(step, STEP_NONBLOCK))
+ waiting = step;
}
+ /* Finish any waiting query. */
+ if (waiting != NULL)
+ try_complete_step(waiting, STEP_RETRY);
+
/* Perform per-session teardown */
for (i = 0; i < testspec->nsessions; i++)
{
if (testspec->sessions[i]->teardownsql)
{
- res = PQexec(conns[i], testspec->sessions[i]->teardownsql);
+ res = PQexec(conns[i + 1], testspec->sessions[i]->teardownsql);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "teardown of session %s failed: %s",
testspec->sessions[i]->name,
- PQerrorMessage(conns[0]));
+ PQerrorMessage(conns[i + 1]));
/* don't exit on teardown failure */
}
PQclear(res);
@@ -351,6 +407,105 @@ run_permutation(TestSpec * testspec, int nsteps, Step ** steps)
}
}
+/*
+ * Our caller already sent the query associated with this step. Wait for it
+ * to either complete or (if given the STEP_NONBLOCK flag) to block while
+ * waiting for a lock. We assume that any lock wait will persist until we
+ * have executed additional steps in the permutation. This is not fully
+ * robust -- a concurrent autovacuum could briefly take a lock with which we
+ * conflict. The risk may be low enough to discount.
+ *
+ * When calling this function on behalf of a given step for a second or later
+ * time, pass the STEP_RETRY flag. This only affects the messages printed.
+ *
+ * If the STEP_NONBLOCK flag was specified and the query is waiting to acquire
+ * a lock, returns true. Otherwise, returns false.
+ */
+static bool
+try_complete_step(Step *step, int flags)
+{
+ PGconn *conn = conns[1 + step->session];
+ fd_set read_set;
+ struct timeval timeout;
+ int sock = PQsocket(conn);
+ int ret;
+ PGresult *res;
+
+ FD_ZERO(&read_set);
+
+ while (flags & STEP_NONBLOCK && PQisBusy(conn))
+ {
+ FD_SET(sock, &read_set);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 10000; /* Check for lock waits every 10ms. */
+
+ ret = select(sock + 1, &read_set, NULL, NULL, &timeout);
+ if (ret < 0) /* error in select() */
+ {
+ fprintf(stderr, "select failed: %s\n", strerror(errno));
+ exit_nicely();
+ }
+ else if (ret == 0) /* select() timeout: check for lock wait */
+ {
+ int ntuples;
+
+ res = PQexecPrepared(conns[0], PREP_WAITING, 1,
+ &backend_ids[step->session + 1],
+ NULL, NULL, 0);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "lock wait query failed: %s",
+ PQerrorMessage(conn));
+ exit_nicely();
+ }
+ ntuples = PQntuples(res);
+ PQclear(res);
+
+ if (ntuples >= 1) /* waiting to acquire a lock */
+ {
+ if (!(flags & STEP_RETRY))
+ printf("step %s: %s <waiting ...>\n",
+ step->name, step->sql);
+ return true;
+ }
+ /* else, not waiting: give it more time */
+ }
+ else if (!PQconsumeInput(conn)) /* select(): data available */
+ {
+ fprintf(stderr, "PQconsumeInput failed: %s", PQerrorMessage(conn));
+ exit_nicely();
+ }
+ }
+
+ if (flags & STEP_RETRY)
+ printf("step %s: <... completed>\n", step->name);
+ else
+ printf("step %s: %s\n", step->name, step->sql);
+
+ while ((res = PQgetResult(conn)))
+ {
+ switch (PQresultStatus(res))
+ {
+ case PGRES_COMMAND_OK:
+ break;
+ case PGRES_TUPLES_OK:
+ printResultSet(res);
+ break;
+ case PGRES_FATAL_ERROR:
+ /* Detail may contain xid values, so just show primary. */
+ printf("%s: %s\n", PQresultErrorField(res, PG_DIAG_SEVERITY),
+ PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY));
+ break;
+ default:
+ printf("unexpected result status: %s\n",
+ PQresStatus(PQresultStatus(res)));
+ }
+ PQclear(res);
+ }
+
+ return false;
+}
+
static void
printResultSet(PGresult *res)
{