int main(int argc, char **argv) { const char *conninfo; TestSpec *testspec; int i; /* * If the user supplies a 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 > 1) conninfo = argv[1]; else conninfo = "dbname = postgres"; /* Read the test spec from stdin */ spec_yyparse(); testspec = &parseresult; printf("Parsed test spec with %d sessions\n", testspec->nsessions); /* Establish connections to the database, one for each session */ nconns = testspec->nsessions; conns = calloc(nconns, sizeof(PGconn *)); for (i = 0; i < testspec->nsessions; i++) { PGresult *res; 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); } /* 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; } /* * Run the permutations specified in the spec, or all if none were * explicitly specified. */ if (testspec->permutations) run_named_permutations(testspec); else run_all_permutations(testspec); /* Clean up and exit */ for (i = 0; i < nconns; i++) PQfinish(conns[i]); return 0; }
int main(int argc, char **argv) { 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; }
int main(int argc, char **argv) { const char *conninfo; TestSpec *testspec; int i, j; int n; PGresult *res; PQExpBufferData wait_query; int opt; int nallsteps; Step **allsteps; while ((opt = getopt(argc, argv, "nV")) != -1) { switch (opt) { case 'n': dry_run = true; break; case 'V': puts("isolationtester (PostgreSQL) " PG_VERSION); exit(0); default: fprintf(stderr, "Usage: isolationtester [-n] [CONNINFO]\n"); return EXIT_FAILURE; } } /* * Make stdout unbuffered to match stderr; and ensure stderr is unbuffered * too, which it should already be everywhere except sometimes in Windows. */ setbuf(stdout, NULL); setbuf(stderr, NULL); /* * 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; /* 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); testspec->nallsteps = nallsteps; testspec->allsteps = allsteps; /* Verify that all step names are unique */ for (i = 1; i < testspec->nallsteps; i++) { if (strcmp(testspec->allsteps[i - 1]->name, testspec->allsteps[i]->name) == 0) { fprintf(stderr, "duplicate step name: %s\n", testspec->allsteps[i]->name); exit_nicely(); } } /* * 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 pg_catalog.pg_blocking_pids($1) && '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, backend_pids[1]); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", backend_pids[i]); appendPQExpBufferStr(&wait_query, "}'::integer[]"); 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]); return 0; }