/* * Run one permutation */ static void run_permutation(TestSpec * testspec, int nsteps, Step ** steps) { PGresult *res; int i; Step *waiting = NULL; printf("\nstarting permutation:"); for (i = 0; i < nsteps; i++) printf(" %s", steps[i]->name); printf("\n"); /* Perform setup */ if (testspec->setupsql) { res = PQexec(conns[0], testspec->setupsql); 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]; if (!PQsendQuery(conns[1 + step->session], step->sql)) { fprintf(stdout, "failed to send query: %s\n", 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. */ if (!try_complete_step(waiting, STEP_NONBLOCK | STEP_RETRY)) waiting = NULL; } 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 + 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 */ } 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 */ } PQclear(res); } }
/* * Run one permutation */ static void run_permutation(TestSpec * testspec, int nsteps, Step ** steps) { 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_TUPLES_OK) { printResultSet(res); } else 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_TUPLES_OK) { printResultSet(res); } else 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); } }
/* * Run one permutation */ static void run_permutation(TestSpec *testspec, int nsteps, Step **steps) { PGresult *res; int i; int w; int nwaiting = 0; int nerrorstep = 0; Step **waiting; Step **errorstep; /* * 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; } waiting = malloc(sizeof(Step *) * testspec->nsessions); errorstep = malloc(sizeof(Step *) * testspec->nsessions); 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_TUPLES_OK) { printResultSet(res); } else 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]; Step *oldstep = NULL; bool mustwait; /* * Check whether the session that needs to perform the next step is * still blocked on an earlier step. If so, wait for it to finish. * * (In older versions of this tool, we allowed precisely one session * to be waiting at a time. If we reached a step that required that * session to execute the next command, we would declare the whole * permutation invalid, cancel everything, and move on to the next * one. Unfortunately, that made it impossible to test the deadlock * detector using this framework, unless the number of processes * involved in the deadlock was precisely two. We now assume that if * we reach a step that is still blocked, we need to wait for it to * unblock itself.) */ for (w = 0; w < nwaiting; ++w) { if (step->session == waiting[w]->session) { oldstep = waiting[w]; /* Wait for previous step on this connection. */ try_complete_step(oldstep, STEP_RETRY); /* Remove that step from the waiting[] array. */ if (w + 1 < nwaiting) memcpy(&waiting[w], &waiting[w + 1], (nwaiting - (w + 1)) * sizeof(Step *)); nwaiting--; break; } } if (oldstep != NULL) { /* * Check for completion of any steps that were previously waiting. * Remove any that have completed from waiting[], and include them * in the list for report_multiple_error_messages(). */ w = 0; nerrorstep = 0; while (w < nwaiting) { if (try_complete_step(waiting[w], STEP_NONBLOCK | STEP_RETRY)) { /* Still blocked on a lock, leave it alone. */ w++; } else { /* This one finished, too! */ errorstep[nerrorstep++] = waiting[w]; if (w + 1 < nwaiting) memcpy(&waiting[w], &waiting[w + 1], (nwaiting - (w + 1)) * sizeof(Step *)); nwaiting--; } } /* Report all errors together. */ report_multiple_error_messages(oldstep, nerrorstep, errorstep); } /* Send the query for this step. */ 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(); } /* Try to complete this step without blocking. */ mustwait = try_complete_step(step, STEP_NONBLOCK); /* Check for completion of any steps that were previously waiting. */ w = 0; nerrorstep = 0; while (w < nwaiting) { if (try_complete_step(waiting[w], STEP_NONBLOCK | STEP_RETRY)) w++; else { errorstep[nerrorstep++] = waiting[w]; if (w + 1 < nwaiting) memcpy(&waiting[w], &waiting[w + 1], (nwaiting - (w + 1)) * sizeof(Step *)); nwaiting--; } } /* Report any error from this step, and any steps that it unblocked. */ report_multiple_error_messages(step, nerrorstep, errorstep); /* If this step is waiting, add it to the array of waiters. */ if (mustwait) waiting[nwaiting++] = step; } /* Wait for any remaining queries. */ for (w = 0; w < nwaiting; ++w) { try_complete_step(waiting[w], STEP_RETRY); report_error_message(waiting[w]); } /* 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_TUPLES_OK) { printResultSet(res); } else 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 */ } 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 */ } PQclear(res); } free(waiting); free(errorstep); }