#include "postgres_fe.h"#include <sys/time.h>#include <unistd.h>#include "libpq-fe.h"#include "pqexpbuffer.h"#include "isolationtester.h"
Go to the source code of this file.
Defines | |
| #define | PREP_WAITING "isolationtester_waiting" |
| #define | STEP_NONBLOCK 0x1 |
| #define | STEP_RETRY 0x2 |
Functions | |
| static void | run_testspec (TestSpec *testspec) |
| static void | run_all_permutations (TestSpec *testspec) |
| 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) |
| 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) |
| static void | printResultSet (PGresult *res) |
| static void | exit_nicely (void) |
| int | main (int argc, char **argv) |
| static void | report_error_message (Step *step) |
| static void | report_two_error_messages (Step *step1, Step *step2) |
Variables | |
| int | optind |
| static PGconn ** | conns = NULL |
| static const char ** | backend_pids = NULL |
| static int | nconns = 0 |
| static int | dry_run = false |
| static int * | piles |
| #define PREP_WAITING "isolationtester_waiting" |
Definition at line 36 of file isolationtester.c.
Referenced by main(), and try_complete_step().
| #define STEP_NONBLOCK 0x1 |
Definition at line 56 of file isolationtester.c.
Referenced by run_permutation(), and try_complete_step().
| #define STEP_RETRY 0x2 |
Definition at line 57 of file isolationtester.c.
Referenced by run_permutation(), and try_complete_step().
| static void exit_nicely | ( | void | ) | [static] |
Definition at line 67 of file isolationtester.c.
References i, nconns, and PQfinish().
| int main | ( | int | argc, | |
| char ** | argv | |||
| ) |
Definition at line 79 of file isolationtester.c.
References appendPQExpBuffer(), appendPQExpBufferStr(), backend_pids, calloc, CONNECTION_OK, PQExpBufferData::data, dry_run, exit_nicely, getopt(), i, initPQExpBuffer(), nconns, TestSpec::nsessions, Session::nsteps, NULL, optind, parseresult, PGRES_COMMAND_OK, PGRES_TUPLES_OK, PQclear(), PQconnectdb(), PQerrorMessage(), PQexec(), PQfinish(), PQgetvalue(), PQnfields(), PQntuples(), PQprepare(), PQresultStatus(), PQstatus(), PREP_WAITING, run_testspec(), Step::session, TestSpec::sessions, spec_yyparse(), Session::steps, and termPQExpBuffer().
{
const char *conninfo;
TestSpec *testspec;
int i;
PGresult *res;
PQExpBufferData wait_query;
int opt;
while ((opt = getopt(argc, argv, "n")) != -1)
{
switch (opt)
{
case 'n':
dry_run = true;
break;
default:
fprintf(stderr, "Usage: isolationtester [-n] [CONNINFO]\n");
return EXIT_FAILURE;
}
}
/*
* If the user supplies a non-option parameter on the command line, use it
* as the conninfo string; otherwise default to setting dbname=postgres
* and using environment variables or defaults for all other connection
* parameters.
*/
if (argc > optind)
conninfo = argv[optind];
else
conninfo = "dbname = postgres";
/* Read the test spec from stdin */
spec_yyparse();
testspec = &parseresult;
/*
* In dry-run mode, just print the permutations that would be run, and
* exit.
*/
if (dry_run)
{
run_testspec(testspec);
return 0;
}
printf("Parsed test spec with %d sessions\n", 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 *));
backend_pids = calloc(nconns, sizeof(*backend_pids));
for (i = 0; i < nconns; i++)
{
conns[i] = PQconnectdb(conninfo);
if (PQstatus(conns[i]) != CONNECTION_OK)
{
fprintf(stderr, "Connection %d to database failed: %s",
i, PQerrorMessage(conns[i]));
exit_nicely();
}
/*
* Suppress NOTIFY messages, which otherwise pop into results at odd
* places.
*/
res = PQexec(conns[i], "SET client_min_messages = warning;");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "message level setup failed: %s", PQerrorMessage(conns[i]));
exit_nicely();
}
PQclear(res);
/* Get the backend pid for lock wait checking. */
res = PQexec(conns[i], "SELECT pg_backend_pid()");
if (PQresultStatus(res) == PGRES_TUPLES_OK)
{
if (PQntuples(res) == 1 && PQnfields(res) == 1)
backend_pids[i] = strdup(PQgetvalue(res, 0, 0));
else
{
fprintf(stderr, "backend pid query returned %d rows and %d columns, expected 1 row and 1 column",
PQntuples(res), PQnfields(res));
exit_nicely();
}
}
else
{
fprintf(stderr, "backend pid query failed: %s",
PQerrorMessage(conns[i]));
exit_nicely();
}
PQclear(res);
}
/* Set the session index fields in steps. */
for (i = 0; i < testspec->nsessions; i++)
{
Session *session = testspec->sessions[i];
int stepindex;
for (stepindex = 0; stepindex < session->nsteps; stepindex++)
session->steps[stepindex]->session = i;
}
/*
* Build the query we'll use to detect lock contention among sessions in
* the test specification. Most of the time, we could get away with
* simply checking whether a session is waiting for *any* lock: we don't
* exactly expect concurrent use of test tables. However, autovacuum will
* occasionally take AccessExclusiveLock to truncate a table, and we must
* ignore that transient wait.
*/
initPQExpBuffer(&wait_query);
appendPQExpBufferStr(&wait_query,
"SELECT 1 FROM pg_locks holder, pg_locks waiter "
"WHERE NOT waiter.granted AND waiter.pid = $1 "
"AND holder.granted "
"AND holder.pid <> $1 AND holder.pid IN (");
/* The spec syntax requires at least one session; assume that here. */
appendPQExpBuffer(&wait_query, "%s", backend_pids[1]);
for (i = 2; i < nconns; i++)
appendPQExpBuffer(&wait_query, ", %s", backend_pids[i]);
appendPQExpBufferStr(&wait_query,
") "
"AND holder.mode = ANY (CASE waiter.mode "
"WHEN 'AccessShareLock' THEN ARRAY["
"'AccessExclusiveLock'] "
"WHEN 'RowShareLock' THEN ARRAY["
"'ExclusiveLock',"
"'AccessExclusiveLock'] "
"WHEN 'RowExclusiveLock' THEN ARRAY["
"'ShareLock',"
"'ShareRowExclusiveLock',"
"'ExclusiveLock',"
"'AccessExclusiveLock'] "
"WHEN 'ShareUpdateExclusiveLock' THEN ARRAY["
"'ShareUpdateExclusiveLock',"
"'ShareLock',"
"'ShareRowExclusiveLock',"
"'ExclusiveLock',"
"'AccessExclusiveLock'] "
"WHEN 'ShareLock' THEN ARRAY["
"'RowExclusiveLock',"
"'ShareUpdateExclusiveLock',"
"'ShareRowExclusiveLock',"
"'ExclusiveLock',"
"'AccessExclusiveLock'] "
"WHEN 'ShareRowExclusiveLock' THEN ARRAY["
"'RowExclusiveLock',"
"'ShareUpdateExclusiveLock',"
"'ShareLock',"
"'ShareRowExclusiveLock',"
"'ExclusiveLock',"
"'AccessExclusiveLock'] "
"WHEN 'ExclusiveLock' THEN ARRAY["
"'RowShareLock',"
"'RowExclusiveLock',"
"'ShareUpdateExclusiveLock',"
"'ShareLock',"
"'ShareRowExclusiveLock',"
"'ExclusiveLock',"
"'AccessExclusiveLock'] "
"WHEN 'AccessExclusiveLock' THEN ARRAY["
"'AccessShareLock',"
"'RowShareLock',"
"'RowExclusiveLock',"
"'ShareUpdateExclusiveLock',"
"'ShareLock',"
"'ShareRowExclusiveLock',"
"'ExclusiveLock',"
"'AccessExclusiveLock'] END) "
"AND holder.locktype IS NOT DISTINCT FROM waiter.locktype "
"AND holder.database IS NOT DISTINCT FROM waiter.database "
"AND holder.relation IS NOT DISTINCT FROM waiter.relation "
"AND holder.page IS NOT DISTINCT FROM waiter.page "
"AND holder.tuple IS NOT DISTINCT FROM waiter.tuple "
"AND holder.virtualxid IS NOT DISTINCT FROM waiter.virtualxid "
"AND holder.transactionid IS NOT DISTINCT FROM waiter.transactionid "
"AND holder.classid IS NOT DISTINCT FROM waiter.classid "
"AND holder.objid IS NOT DISTINCT FROM waiter.objid "
"AND holder.objsubid IS NOT DISTINCT FROM waiter.objsubid ");
res = PQprepare(conns[0], PREP_WAITING, wait_query.data, 0, NULL);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "prepare of lock wait query failed: %s",
PQerrorMessage(conns[0]));
exit_nicely();
}
PQclear(res);
termPQExpBuffer(&wait_query);
/*
* Run the permutations specified in the spec, or all if none were
* explicitly specified.
*/
run_testspec(testspec);
/* Clean up and exit */
for (i = 0; i < nconns; i++)
PQfinish(conns[i]);
fflush(stderr);
fflush(stdout);
return 0;
}
| static void printResultSet | ( | PGresult * | res | ) | [static] |
Definition at line 809 of file isolationtester.c.
References i, PQfname(), PQgetvalue(), PQnfields(), and PQntuples().
Referenced by run_permutation(), and try_complete_step().
{
int nFields;
int i,
j;
/* first, print out the attribute names */
nFields = PQnfields(res);
for (i = 0; i < nFields; i++)
printf("%-15s", PQfname(res, i));
printf("\n\n");
/* next, print out the rows */
for (i = 0; i < PQntuples(res); i++)
{
for (j = 0; j < nFields; j++)
printf("%-15s", PQgetvalue(res, i, j));
printf("\n");
}
}
| static void report_error_message | ( | Step * | step | ) | [static] |
Definition at line 448 of file isolationtester.c.
References Step::errormsg, and free.
Referenced by run_permutation().
Definition at line 465 of file isolationtester.c.
References Step::errormsg, free, malloc, and Step::name.
Referenced by run_permutation().
{
char *prefix;
prefix = malloc(strlen(step1->name) + strlen(step2->name) + 2);
sprintf(prefix, "%s %s", step1->name, step2->name);
if (step1->errormsg)
{
fprintf(stdout, "error in steps %s: %s\n", prefix,
step1->errormsg);
free(step1->errormsg);
step1->errormsg = NULL;
}
if (step2->errormsg)
{
fprintf(stdout, "error in steps %s: %s\n", prefix,
step2->errormsg);
free(step2->errormsg);
step2->errormsg = NULL;
}
free(prefix);
}
| static void run_all_permutations | ( | TestSpec * | testspec | ) | [static] |
Definition at line 312 of file isolationtester.c.
References i, malloc, TestSpec::nsessions, Session::nsteps, piles, run_all_permutations_recurse(), and TestSpec::sessions.
Referenced by run_testspec().
{
int nsteps;
int i;
Step **steps;
/* Count the total number of steps in all sessions */
nsteps = 0;
for (i = 0; i < testspec->nsessions; i++)
nsteps += testspec->sessions[i]->nsteps;
steps = malloc(sizeof(Step *) * nsteps);
/*
* To generate the permutations, we conceptually put the steps of each
* session on a pile. To generate a permutation, we pick steps from the
* piles until all piles are empty. By picking steps from piles in
* different order, we get different permutations.
*
* A pile is actually just an integer which tells how many steps we've
* already picked from this pile.
*/
piles = malloc(sizeof(int) * testspec->nsessions);
for (i = 0; i < testspec->nsessions; i++)
piles[i] = 0;
run_all_permutations_recurse(testspec, 0, steps);
}
| static void run_all_permutations_recurse | ( | TestSpec * | testspec, | |
| int | nsteps, | |||
| Step ** | steps | |||
| ) | [static] |
Definition at line 342 of file isolationtester.c.
References i, TestSpec::nsessions, Session::nsteps, piles, run_permutation(), TestSpec::sessions, and Session::steps.
Referenced by run_all_permutations().
{
int i;
int found = 0;
for (i = 0; i < testspec->nsessions; i++)
{
/* If there's any more steps in this pile, pick it and recurse */
if (piles[i] < testspec->sessions[i]->nsteps)
{
steps[nsteps] = testspec->sessions[i]->steps[piles[i]];
piles[i]++;
run_all_permutations_recurse(testspec, nsteps + 1, steps);
piles[i]--;
found = 1;
}
}
/* If all the piles were empty, this permutation is completed. Run it */
if (!found)
run_permutation(testspec, nsteps, steps);
}
| static void run_named_permutations | ( | TestSpec * | testspec | ) | [static] |
Definition at line 372 of file isolationtester.c.
References exit_nicely, free, i, malloc, TestSpec::npermutations, TestSpec::nsessions, Permutation::nsteps, Session::nsteps, NULL, TestSpec::permutations, qsort, run_permutation(), TestSpec::sessions, step_qsort_cmp(), Permutation::stepnames, and Session::steps.
Referenced by run_testspec().
{
int i,
j;
int n;
int nallsteps;
Step **allsteps;
/* First create a lookup table of all steps */
nallsteps = 0;
for (i = 0; i < testspec->nsessions; i++)
nallsteps += testspec->sessions[i]->nsteps;
allsteps = malloc(nallsteps * sizeof(Step *));
n = 0;
for (i = 0; i < testspec->nsessions; i++)
{
for (j = 0; j < testspec->sessions[i]->nsteps; j++)
allsteps[n++] = testspec->sessions[i]->steps[j];
}
qsort(allsteps, nallsteps, sizeof(Step *), &step_qsort_cmp);
for (i = 0; i < testspec->npermutations; i++)
{
Permutation *p = testspec->permutations[i];
Step **steps;
steps = malloc(p->nsteps * sizeof(Step *));
/* Find all the named steps using the lookup table */
for (j = 0; j < p->nsteps; j++)
{
Step **this = (Step **) bsearch(p->stepnames[j], allsteps,
nallsteps, sizeof(Step *),
&step_bsearch_cmp);
if (this == NULL)
{
fprintf(stderr, "undefined step \"%s\" specified in permutation\n",
p->stepnames[j]);
exit_nicely();
}
steps[j] = *this;
}
/* And run them */
run_permutation(testspec, p->nsteps, steps);
free(steps);
}
}
Definition at line 494 of file isolationtester.c.
References buf, conn, dry_run, exit_nicely, i, Step::name, Session::name, name, nconns, TestSpec::nsessions, TestSpec::nsetupsqls, NULL, PGRES_COMMAND_OK, PGRES_TUPLES_OK, PQcancel(), PQclear(), PQerrorMessage(), PQexec(), PQfreeCancel(), PQgetCancel(), PQgetResult(), PQresultStatus(), PQsendQuery(), printResultSet(), report_error_message(), report_two_error_messages(), Step::session, TestSpec::sessions, Session::setupsql, TestSpec::setupsqls, Step::sql, STEP_NONBLOCK, STEP_RETRY, TestSpec::teardownsql, Session::teardownsql, try_complete_step(), and waiting.
Referenced by run_all_permutations_recurse(), and run_named_permutations().
{
PGresult *res;
int i;
Step *waiting = NULL;
/*
* In dry run mode, just display the permutation in the same format used
* by spec files, and return.
*/
if (dry_run)
{
printf("permutation");
for (i = 0; i < nsteps; i++)
printf(" \"%s\"", steps[i]->name);
printf("\n");
return;
}
printf("\nstarting permutation:");
for (i = 0; i < nsteps; i++)
printf(" %s", steps[i]->name);
printf("\n");
/* Perform setup */
for (i = 0; i < testspec->nsetupsqls; i++)
{
res = PQexec(conns[0], testspec->setupsqls[i]);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "setup failed: %s", PQerrorMessage(conns[0]));
exit_nicely();
}
PQclear(res);
}
/* Perform per-session setup */
for (i = 0; i < testspec->nsessions; i++)
{
if (testspec->sessions[i]->setupsql)
{
res = PQexec(conns[i + 1], testspec->sessions[i]->setupsql);
if (PQresultStatus(res) == PGRES_TUPLES_OK)
{
printResultSet(res);
}
else if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "setup of session %s failed: %s",
testspec->sessions[i]->name,
PQerrorMessage(conns[i + 1]));
exit_nicely();
}
PQclear(res);
}
}
/* Perform steps */
for (i = 0; i < nsteps; i++)
{
Step *step = steps[i];
PGconn *conn = conns[1 + step->session];
if (waiting != NULL && step->session == waiting->session)
{
PGcancel *cancel;
PGresult *res;
int j;
/*
* This permutation is invalid: it can never happen in real life.
*
* A session is blocked on an earlier step (waiting) and no
* further steps from this session can run until it is unblocked,
* but it can only be unblocked by running steps from other
* sessions.
*/
fflush(stdout);
fprintf(stderr, "invalid permutation detected\n");
fflush(stderr);
/* Cancel the waiting statement from this session. */
cancel = PQgetCancel(conn);
if (cancel != NULL)
{
char buf[256];
if (!PQcancel(cancel, buf, sizeof(buf)))
fprintf(stderr, "PQcancel failed: %s\n", buf);
/* Be sure to consume the error message. */
while ((res = PQgetResult(conn)) != NULL)
PQclear(res);
PQfreeCancel(cancel);
}
/*
* Now we really have to complete all the running transactions to
* make sure teardown doesn't block.
*/
for (j = 1; j < nconns; j++)
{
res = PQexec(conns[j], "ROLLBACK");
if (res != NULL)
PQclear(res);
}
goto teardown;
}
if (!PQsendQuery(conn, step->sql))
{
fprintf(stdout, "failed to send query for step %s: %s\n",
step->name, PQerrorMessage(conns[1 + step->session]));
exit_nicely();
}
if (waiting != NULL)
{
/* Some other step is already waiting: just block. */
try_complete_step(step, 0);
/*
* See if this step unblocked the waiting step; report both error
* messages together if so.
*/
if (!try_complete_step(waiting, STEP_NONBLOCK | STEP_RETRY))
{
report_two_error_messages(step, waiting);
waiting = NULL;
}
else
report_error_message(step);
}
else
{
if (try_complete_step(step, STEP_NONBLOCK))
waiting = step;
report_error_message(step);
}
}
/* Finish any waiting query. */
if (waiting != NULL)
{
try_complete_step(waiting, STEP_RETRY);
report_error_message(waiting);
}
teardown:
/* Perform per-session teardown */
for (i = 0; i < testspec->nsessions; i++)
{
if (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[i + 1]));
/* don't exit on teardown failure */
fflush(stderr);
}
PQclear(res);
}
}
/* Perform teardown */
if (testspec->teardownsql)
{
res = PQexec(conns[0], testspec->teardownsql);
if (PQresultStatus(res) == PGRES_TUPLES_OK)
{
printResultSet(res);
}
else if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "teardown failed: %s",
PQerrorMessage(conns[0]));
/* don't exit on teardown failure */
fflush(stderr);
}
PQclear(res);
}
}
| static void run_testspec | ( | TestSpec * | testspec | ) | [static] |
Definition at line 300 of file isolationtester.c.
References TestSpec::permutations, run_all_permutations(), and run_named_permutations().
Referenced by main().
{
if (testspec->permutations)
run_named_permutations(testspec);
else
run_all_permutations(testspec);
}
| static int step_bsearch_cmp | ( | const void * | a, | |
| const void * | b | |||
| ) | [static] |
Definition at line 436 of file isolationtester.c.
References Step::name.
| static int step_qsort_cmp | ( | const void * | a, | |
| const void * | b | |||
| ) | [static] |
Definition at line 427 of file isolationtester.c.
References Step::name.
Referenced by run_named_permutations().
Definition at line 699 of file isolationtester.c.
References backend_pids, conn, EINTR, Step::errormsg, exit_nicely, malloc, Step::name, NULL, PG_DIAG_MESSAGE_PRIMARY, PG_DIAG_SEVERITY, PGRES_COMMAND_OK, PGRES_FATAL_ERROR, PGRES_TUPLES_OK, PQclear(), PQconsumeInput(), PQerrorMessage(), PQexecPrepared(), PQgetResult(), PQisBusy(), PQntuples(), PQresStatus(), PQresultErrorField(), PQresultErrorMessage(), PQresultStatus(), PQsocket(), PREP_WAITING, printResultSet(), select, Step::session, Step::sql, STEP_NONBLOCK, STEP_RETRY, and strerror().
Referenced by run_permutation().
{
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() */
{
if (errno == EINTR)
continue;
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_pids[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\n",
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:
if (step->errormsg != NULL)
{
printf("WARNING: this step had a leftover error message\n");
printf("%s\n", step->errormsg);
}
/*
* Detail may contain XID values, so we want to just show
* primary. Beware however that libpq-generated error results
* may not contain subfields, only an old-style message.
*/
{
const char *sev = PQresultErrorField(res,
PG_DIAG_SEVERITY);
const char *msg = PQresultErrorField(res,
PG_DIAG_MESSAGE_PRIMARY);
if (sev && msg)
{
step->errormsg = malloc(5 + strlen(sev) + strlen(msg));
sprintf(step->errormsg, "%s: %s", sev, msg);
}
else
step->errormsg = strdup(PQresultErrorMessage(res));
}
break;
default:
printf("unexpected result status: %s\n",
PQresStatus(PQresultStatus(res)));
}
PQclear(res);
}
return false;
}
const char** backend_pids = NULL [static] |
Definition at line 43 of file isolationtester.c.
Referenced by main(), and try_complete_step().
Definition at line 42 of file isolationtester.c.
Referenced by describeRoles().
int dry_run = false [static] |
Definition at line 47 of file isolationtester.c.
Referenced by main(), and run_permutation().
int nconns = 0 [static] |
Definition at line 44 of file isolationtester.c.
Referenced by exit_nicely(), main(), and run_permutation().
int* piles [static] |
Definition at line 293 of file isolationtester.c.
Referenced by run_all_permutations(), and run_all_permutations_recurse().
1.7.1