aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/test/isolation/README13
-rw-r--r--src/test/isolation/expected/fk-contention.out17
-rw-r--r--src/test/isolation/expected/fk-deadlock.out67
-rw-r--r--src/test/isolation/expected/fk-deadlock2.out107
-rw-r--r--src/test/isolation/isolation_schedule3
-rw-r--r--src/test/isolation/isolationtester.c215
-rw-r--r--src/test/isolation/specs/fk-contention.spec19
-rw-r--r--src/test/isolation/specs/fk-deadlock.spec54
-rw-r--r--src/test/isolation/specs/fk-deadlock2.spec59
9 files changed, 524 insertions, 30 deletions
diff --git a/src/test/isolation/README b/src/test/isolation/README
index 087ac279c7a..58fe50e58a2 100644
--- a/src/test/isolation/README
+++ b/src/test/isolation/README
@@ -63,3 +63,16 @@ permutation "<step name>" ...
all possible overlapping orderings of the given sessions.
Lines beginning with a # are considered comments.
+
+
+Support for blocking commands
+=============================
+
+Each spec may contain commands that block until further action has been taken
+(most likely, some other session runs a step that unblocks it or causes a
+deadlock). Such a spec needs to be careful to manually specify valid
+permutations, i.e. those that would not expect a blocked session to execute a
+command. If the spec fails to follow that rule, the spec is aborted.
+
+Only one command can be waiting at a time. As long as one command is waiting,
+other commands are run to completion synchronously.
diff --git a/src/test/isolation/expected/fk-contention.out b/src/test/isolation/expected/fk-contention.out
new file mode 100644
index 00000000000..61e84d158ae
--- /dev/null
+++ b/src/test/isolation/expected/fk-contention.out
@@ -0,0 +1,17 @@
+Parsed test spec with 2 sessions
+
+starting permutation: ins com upd
+step ins: INSERT INTO bar VALUES (42);
+step com: COMMIT;
+step upd: UPDATE foo SET b = 'Hello World';
+
+starting permutation: ins upd com
+step ins: INSERT INTO bar VALUES (42);
+step upd: UPDATE foo SET b = 'Hello World'; <waiting ...>
+step com: COMMIT;
+step upd: <... completed>
+
+starting permutation: upd ins com
+step upd: UPDATE foo SET b = 'Hello World';
+step ins: INSERT INTO bar VALUES (42);
+step com: COMMIT;
diff --git a/src/test/isolation/expected/fk-deadlock.out b/src/test/isolation/expected/fk-deadlock.out
new file mode 100644
index 00000000000..6b6ee163c7c
--- /dev/null
+++ b/src/test/isolation/expected/fk-deadlock.out
@@ -0,0 +1,67 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1i s1u s1c s2i s2u s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+
+starting permutation: s1i s1u s2i s1c s2u s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar';
+step s2i: INSERT INTO child VALUES (2, 1); <waiting ...>
+step s1c: COMMIT;
+step s2i: <... completed>
+step s2u: UPDATE parent SET aux = 'baz';
+step s2c: COMMIT;
+
+starting permutation: s1i s2i s1u s2u s1c s2c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
+step s2u: UPDATE parent SET aux = 'baz';
+step s1u: <... completed>
+ERROR: deadlock detected
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1i s2i s2u s1u s2c s1c
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
+step s1u: UPDATE parent SET aux = 'bar';
+step s2u: <... completed>
+ERROR: deadlock detected
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s2i s1i s1u s2u s1c s2c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1i: INSERT INTO child VALUES (1, 1);
+step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
+step s2u: UPDATE parent SET aux = 'baz';
+step s1u: <... completed>
+ERROR: deadlock detected
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2i s1i s2u s1u s2c s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s1i: INSERT INTO child VALUES (1, 1);
+step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
+step s1u: UPDATE parent SET aux = 'bar';
+step s2u: <... completed>
+ERROR: deadlock detected
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s2i s2u s1i s2c s1u s1c
+step s2i: INSERT INTO child VALUES (2, 1);
+step s2u: UPDATE parent SET aux = 'baz';
+step s1i: INSERT INTO child VALUES (1, 1); <waiting ...>
+step s2c: COMMIT;
+step s1i: <... completed>
+step s1u: UPDATE parent SET aux = 'bar';
+step s1c: COMMIT;
diff --git a/src/test/isolation/expected/fk-deadlock2.out b/src/test/isolation/expected/fk-deadlock2.out
new file mode 100644
index 00000000000..af3ce8ecfd3
--- /dev/null
+++ b/src/test/isolation/expected/fk-deadlock2.out
@@ -0,0 +1,107 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1u1 s1u2 s1c s2u1 s2u2 s2c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1c: COMMIT;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+
+starting permutation: s1u1 s1u2 s2u1 s1c s2u2 s2c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s1c: COMMIT;
+step s2u1: <... completed>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2c: COMMIT;
+
+starting permutation: s1u1 s2u1 s1u2 s2u2 s1c s2c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: <... completed>
+ERROR: deadlock detected
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1u1 s2u1 s1u2 s2u2 s2c s1c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: <... completed>
+ERROR: deadlock detected
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s1u1 s2u1 s2u2 s1u2 s1c s2c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: <... completed>
+ERROR: deadlock detected
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1u1 s2u1 s2u2 s1u2 s2c s1c
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: <... completed>
+ERROR: deadlock detected
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s2u1 s1u1 s1u2 s2u2 s1c s2c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: <... completed>
+ERROR: deadlock detected
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2u1 s1u1 s1u2 s2u2 s2c s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u2: <... completed>
+ERROR: deadlock detected
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s2u1 s1u1 s2u2 s1u2 s1c s2c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: <... completed>
+ERROR: deadlock detected
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2u1 s1u1 s2u2 s1u2 s2c s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: <... completed>
+ERROR: deadlock detected
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s2u1 s2u2 s1u1 s2c s1u2 s1c
+step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; <waiting ...>
+step s2c: COMMIT;
+step s1u1: <... completed>
+step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
+step s1c: COMMIT;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 6ea8a29f490..dc154735eae 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -9,3 +9,6 @@ test: ri-trigger
test: partial-index
test: two-ids
test: multiple-row-versions
+test: fk-contention
+test: fk-deadlock
+test: fk-deadlock2
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)
{
diff --git a/src/test/isolation/specs/fk-contention.spec b/src/test/isolation/specs/fk-contention.spec
new file mode 100644
index 00000000000..8481ae4e332
--- /dev/null
+++ b/src/test/isolation/specs/fk-contention.spec
@@ -0,0 +1,19 @@
+setup
+{
+ CREATE TABLE foo (a int PRIMARY KEY, b text);
+ CREATE TABLE bar (a int NOT NULL REFERENCES foo);
+ INSERT INTO foo VALUES (42);
+}
+
+teardown
+{
+ DROP TABLE foo, bar;
+}
+
+session "s1"
+setup { BEGIN; }
+step "ins" { INSERT INTO bar VALUES (42); }
+step "com" { COMMIT; }
+
+session "s2"
+step "upd" { UPDATE foo SET b = 'Hello World'; }
diff --git a/src/test/isolation/specs/fk-deadlock.spec b/src/test/isolation/specs/fk-deadlock.spec
new file mode 100644
index 00000000000..530cf108399
--- /dev/null
+++ b/src/test/isolation/specs/fk-deadlock.spec
@@ -0,0 +1,54 @@
+setup
+{
+ CREATE TABLE parent (
+ parent_key int PRIMARY KEY,
+ aux text NOT NULL
+ );
+
+ CREATE TABLE child (
+ child_key int PRIMARY KEY,
+ parent_key int NOT NULL REFERENCES parent
+ );
+
+ INSERT INTO parent VALUES (1, 'foo');
+}
+
+teardown
+{
+ DROP TABLE parent, child;
+}
+
+session "s1"
+setup { BEGIN; }
+step "s1i" { INSERT INTO child VALUES (1, 1); }
+step "s1u" { UPDATE parent SET aux = 'bar'; }
+step "s1c" { COMMIT; }
+
+session "s2"
+setup { BEGIN; }
+step "s2i" { INSERT INTO child VALUES (2, 1); }
+step "s2u" { UPDATE parent SET aux = 'baz'; }
+step "s2c" { COMMIT; }
+
+## Most theoretical permutations require that a blocked session execute a
+## command, making them impossible in practice.
+permutation "s1i" "s1u" "s1c" "s2i" "s2u" "s2c"
+permutation "s1i" "s1u" "s2i" "s1c" "s2u" "s2c"
+#permutation "s1i" "s1u" "s2i" "s2u" "s1c" "s2c"
+#permutation "s1i" "s1u" "s2i" "s2u" "s2c" "s1c"
+#permutation "s1i" "s2i" "s1u" "s1c" "s2u" "s2c"
+permutation "s1i" "s2i" "s1u" "s2u" "s1c" "s2c"
+#permutation "s1i" "s2i" "s1u" "s2u" "s2c" "s1c"
+#permutation "s1i" "s2i" "s2u" "s1u" "s1c" "s2c"
+permutation "s1i" "s2i" "s2u" "s1u" "s2c" "s1c"
+#permutation "s1i" "s2i" "s2u" "s2c" "s1u" "s1c"
+#permutation "s2i" "s1i" "s1u" "s1c" "s2u" "s2c"
+permutation "s2i" "s1i" "s1u" "s2u" "s1c" "s2c"
+#permutation "s2i" "s1i" "s1u" "s2u" "s2c" "s1c"
+#permutation "s2i" "s1i" "s2u" "s1u" "s1c" "s2c"
+permutation "s2i" "s1i" "s2u" "s1u" "s2c" "s1c"
+#permutation "s2i" "s1i" "s2u" "s2c" "s1u" "s1c"
+#permutation "s2i" "s2u" "s1i" "s1u" "s1c" "s2c"
+#permutation "s2i" "s2u" "s1i" "s1u" "s2c" "s1c"
+permutation "s2i" "s2u" "s1i" "s2c" "s1u" "s1c"
+#permutation "s2i" "s2u" "s2c" "s1i" "s1u" "s1c"
diff --git a/src/test/isolation/specs/fk-deadlock2.spec b/src/test/isolation/specs/fk-deadlock2.spec
new file mode 100644
index 00000000000..91a87d13ef5
--- /dev/null
+++ b/src/test/isolation/specs/fk-deadlock2.spec
@@ -0,0 +1,59 @@
+setup
+{
+ CREATE TABLE A (
+ AID integer not null,
+ Col1 integer,
+ PRIMARY KEY (AID)
+ );
+
+ CREATE TABLE B (
+ BID integer not null,
+ AID integer not null,
+ Col2 integer,
+ PRIMARY KEY (BID),
+ FOREIGN KEY (AID) REFERENCES A(AID)
+ );
+
+ INSERT INTO A (AID) VALUES (1);
+ INSERT INTO B (BID,AID) VALUES (2,1);
+}
+
+teardown
+{
+ DROP TABLE a, b;
+}
+
+session "s1"
+setup { BEGIN; }
+step "s1u1" { UPDATE A SET Col1 = 1 WHERE AID = 1; }
+step "s1u2" { UPDATE B SET Col2 = 1 WHERE BID = 2; }
+step "s1c" { COMMIT; }
+
+session "s2"
+setup { BEGIN; }
+step "s2u1" { UPDATE B SET Col2 = 1 WHERE BID = 2; }
+step "s2u2" { UPDATE B SET Col2 = 1 WHERE BID = 2; }
+step "s2c" { COMMIT; }
+
+## Many theoretical permutations require that a blocked session execute a
+## command, making them impossible in practice.
+permutation "s1u1" "s1u2" "s1c" "s2u1" "s2u2" "s2c"
+permutation "s1u1" "s1u2" "s2u1" "s1c" "s2u2" "s2c"
+#permutation "s1u1" "s1u2" "s2u1" "s2u2" "s1c" "s2c"
+#permutation "s1u1" "s1u2" "s2u1" "s2u2" "s2c" "s1c"
+#permutation "s1u1" "s2u1" "s1u2" "s1c" "s2u2" "s2c"
+permutation "s1u1" "s2u1" "s1u2" "s2u2" "s1c" "s2c"
+permutation "s1u1" "s2u1" "s1u2" "s2u2" "s2c" "s1c"
+permutation "s1u1" "s2u1" "s2u2" "s1u2" "s1c" "s2c"
+permutation "s1u1" "s2u1" "s2u2" "s1u2" "s2c" "s1c"
+#permutation "s1u1" "s2u1" "s2u2" "s2c" "s1u2" "s1c"
+#permutation "s2u1" "s1u1" "s1u2" "s1c" "s2u2" "s2c"
+permutation "s2u1" "s1u1" "s1u2" "s2u2" "s1c" "s2c"
+permutation "s2u1" "s1u1" "s1u2" "s2u2" "s2c" "s1c"
+permutation "s2u1" "s1u1" "s2u2" "s1u2" "s1c" "s2c"
+permutation "s2u1" "s1u1" "s2u2" "s1u2" "s2c" "s1c"
+#permutation "s2u1" "s1u1" "s2u2" "s2c" "s1u2" "s1c"
+#permutation "s2u1" "s2u2" "s1u1" "s1u2" "s1c" "s2c"
+#permutation "s2u1" "s2u2" "s1u1" "s1u2" "s2c" "s1c"
+permutation "s2u1" "s2u2" "s1u1" "s2c" "s1u2" "s1c"
+#permutation "s2u1" "s2u2" "s2c" "s1u1" "s1u2" "s1c"