static void prepare_input_buffer (int fd, size_t size) { if (size <= allocated_size) return; if (record_buffer) free (record_buffer); record_buffer = malloc (size); if (! record_buffer) { DEBUG (_("rmtd: Cannot allocate buffer space\n")); report_error_message (N_("Cannot allocate buffer space")); exit (EXIT_FAILURE); /* exit status used to be 4 */ } allocated_size = size; #ifdef SO_RCVBUF if (0 <= fd) { int isize = size < INT_MAX ? size : INT_MAX; while (setsockopt (fd, SOL_SOCKET, SO_RCVBUF, (char *) &isize, sizeof isize) && 1024 < isize) isize >>= 1; }
static void get_string_n (char *string) { size_t counter; for (counter = 0; ; counter++) { if (safe_read (STDIN_FILENO, string + counter, 1) != 1) exit (EXIT_SUCCESS); if (string[counter] == '\n') break; if (counter == STRING_SIZE - 1) report_error_message (N_("Input string too long")); } string[counter] = '\0'; }
static long int get_long (char const *string) { char *p; long int n; errno = 0; n = strtol (string, &p, 10); if (errno == ERANGE) { report_numbered_error (errno); exit (EXIT_FAILURE); } if (!*string || *p) { report_error_message (N_("Number syntax error")); exit (EXIT_FAILURE); } return n; }
/* * As above, but reports messages possibly emitted by multiple steps. This is * useful when we have a blocked command awakened by another one; we want to * report all messages identically, for the case where we don't care which * one fails due to a timeout such as deadlock timeout. */ static void report_multiple_error_messages(Step *step, int nextra, Step **extrastep) { PQExpBufferData buffer; int n; if (nextra == 0) { report_error_message(step); return; } initPQExpBuffer(&buffer); appendPQExpBufferStr(&buffer, step->name); for (n = 0; n < nextra; ++n) appendPQExpBuffer(&buffer, " %s", extrastep[n]->name); if (step->errormsg) { fprintf(stdout, "error in steps %s: %s\n", buffer.data, step->errormsg); free(step->errormsg); step->errormsg = NULL; } for (n = 0; n < nextra; ++n) { if (extrastep[n]->errormsg == NULL) continue; fprintf(stdout, "error in steps %s: %s\n", buffer.data, extrastep[n]->errormsg); free(extrastep[n]->errormsg); extrastep[n]->errormsg = NULL; } termPQExpBuffer(&buffer); }
/* * 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); }