PostgreSQL Source Code  git master
isolationtester.c File Reference
#include "postgres_fe.h"
#include <sys/select.h>
#include <sys/time.h>
#include "datatype/timestamp.h"
#include "isolationtester.h"
#include "libpq-fe.h"
#include "pg_getopt.h"
#include "pqexpbuffer.h"
Include dependency graph for isolationtester.c:

Go to the source code of this file.

Data Structures

struct  IsoConnInfo
 

Macros

#define PREP_WAITING   "isolationtester_waiting"
 
#define STEP_NONBLOCK   0x1 /* return as soon as cmd waits for a lock */
 
#define STEP_RETRY   0x2 /* this is a retry of a previously-waiting cmd */
 

Typedefs

typedef struct IsoConnInfo IsoConnInfo
 

Functions

static void check_testspec (TestSpec *testspec)
 
static void run_testspec (TestSpec *testspec)
 
static void run_all_permutations (TestSpec *testspec)
 
static void run_all_permutations_recurse (TestSpec *testspec, int *piles, int nsteps, PermutationStep **steps)
 
static void run_named_permutations (TestSpec *testspec)
 
static void run_permutation (TestSpec *testspec, int nsteps, PermutationStep **steps)
 
static int try_complete_steps (TestSpec *testspec, PermutationStep **waiting, int nwaiting, int flags)
 
static bool try_complete_step (TestSpec *testspec, PermutationStep *pstep, 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 bool step_has_blocker (PermutationStep *pstep)
 
static void printResultSet (PGresult *res)
 
static void isotesterNoticeProcessor (void *arg, const char *message)
 
static void blackholeNoticeProcessor (void *arg, const char *message)
 
static void disconnect_atexit (void)
 
int main (int argc, char **argv)
 

Variables

static IsoConnInfoconns = NULL
 
static int nconns = 0
 
static bool any_new_notice = false
 
static int64 max_step_wait = 360 * USECS_PER_SEC
 

Macro Definition Documentation

◆ PREP_WAITING

#define PREP_WAITING   "isolationtester_waiting"

Definition at line 19 of file isolationtester.c.

◆ STEP_NONBLOCK

#define STEP_NONBLOCK   0x1 /* return as soon as cmd waits for a lock */

Definition at line 60 of file isolationtester.c.

◆ STEP_RETRY

#define STEP_RETRY   0x2 /* this is a retry of a previously-waiting cmd */

Definition at line 61 of file isolationtester.c.

Typedef Documentation

◆ IsoConnInfo

typedef struct IsoConnInfo IsoConnInfo

Function Documentation

◆ blackholeNoticeProcessor()

static void blackholeNoticeProcessor ( void *  arg,
const char *  message 
)
static

Definition at line 1139 of file isolationtester.c.

1140 {
1141  /* do nothing */
1142 }

Referenced by main().

◆ check_testspec()

static void check_testspec ( TestSpec testspec)
static

Definition at line 252 of file isolationtester.c.

253 {
254  int nallsteps;
255  Step **allsteps;
256  int i,
257  j,
258  k;
259 
260  /* Create a sorted lookup table of all steps. */
261  nallsteps = 0;
262  for (i = 0; i < testspec->nsessions; i++)
263  nallsteps += testspec->sessions[i]->nsteps;
264 
265  allsteps = pg_malloc(nallsteps * sizeof(Step *));
266 
267  k = 0;
268  for (i = 0; i < testspec->nsessions; i++)
269  {
270  for (j = 0; j < testspec->sessions[i]->nsteps; j++)
271  allsteps[k++] = testspec->sessions[i]->steps[j];
272  }
273 
274  qsort(allsteps, nallsteps, sizeof(Step *), step_qsort_cmp);
275 
276  /* Verify that all step names are unique. */
277  for (i = 1; i < nallsteps; i++)
278  {
279  if (strcmp(allsteps[i - 1]->name,
280  allsteps[i]->name) == 0)
281  {
282  fprintf(stderr, "duplicate step name: %s\n",
283  allsteps[i]->name);
284  exit(1);
285  }
286  }
287 
288  /* Set the session index fields in steps. */
289  for (i = 0; i < testspec->nsessions; i++)
290  {
291  Session *session = testspec->sessions[i];
292 
293  for (j = 0; j < session->nsteps; j++)
294  session->steps[j]->session = i;
295  }
296 
297  /*
298  * If we have manually-specified permutations, link PermutationSteps to
299  * Steps, and fill in blocker links.
300  */
301  for (i = 0; i < testspec->npermutations; i++)
302  {
303  Permutation *p = testspec->permutations[i];
304 
305  for (j = 0; j < p->nsteps; j++)
306  {
307  PermutationStep *pstep = p->steps[j];
308  Step **this = (Step **) bsearch(pstep->name,
309  allsteps,
310  nallsteps,
311  sizeof(Step *),
313 
314  if (this == NULL)
315  {
316  fprintf(stderr, "undefined step \"%s\" specified in permutation\n",
317  pstep->name);
318  exit(1);
319  }
320  pstep->step = *this;
321 
322  /* Mark the step used, for check below */
323  pstep->step->used = true;
324  }
325 
326  /*
327  * Identify any blocker steps. We search only the current
328  * permutation, since steps not used there couldn't be concurrent.
329  * Note that it's OK to reference later permutation steps, so this
330  * can't be combined with the previous loop.
331  */
332  for (j = 0; j < p->nsteps; j++)
333  {
334  PermutationStep *pstep = p->steps[j];
335 
336  for (k = 0; k < pstep->nblockers; k++)
337  {
338  PermutationStepBlocker *blocker = pstep->blockers[k];
339  int n;
340 
341  if (blocker->blocktype == PSB_ONCE)
342  continue; /* nothing to link to */
343 
344  blocker->step = NULL;
345  for (n = 0; n < p->nsteps; n++)
346  {
347  PermutationStep *otherp = p->steps[n];
348 
349  if (strcmp(otherp->name, blocker->stepname) == 0)
350  {
351  blocker->step = otherp->step;
352  break;
353  }
354  }
355  if (blocker->step == NULL)
356  {
357  fprintf(stderr, "undefined blocking step \"%s\" referenced in permutation step \"%s\"\n",
358  blocker->stepname, pstep->name);
359  exit(1);
360  }
361  /* can't block on completion of step of own session */
362  if (blocker->step->session == pstep->step->session)
363  {
364  fprintf(stderr, "permutation step \"%s\" cannot block on its own session\n",
365  pstep->name);
366  exit(1);
367  }
368  }
369  }
370  }
371 
372  /*
373  * If we have manually-specified permutations, verify that all steps have
374  * been used, warning about anything defined but not used. We can skip
375  * this when using automatically-generated permutations.
376  */
377  if (testspec->permutations)
378  {
379  for (i = 0; i < nallsteps; i++)
380  {
381  if (!allsteps[i]->used)
382  fprintf(stderr, "unused step name: %s\n", allsteps[i]->name);
383  }
384  }
385 
386  free(allsteps);
387 }
void * pg_malloc(size_t size)
Definition: fe_memutils.c:47
#define free(a)
Definition: header.h:65
int j
Definition: isn.c:74
int i
Definition: isn.c:73
static int step_bsearch_cmp(const void *a, const void *b)
static int step_qsort_cmp(const void *a, const void *b)
@ PSB_ONCE
exit(1)
#define fprintf
Definition: port.h:242
#define qsort(a, b, c, d)
Definition: port.h:449
PermutationStepBlockerType blocktype
PermutationStepBlocker ** blockers
PermutationStep ** steps
Step ** steps
int session
bool used
Session ** sessions
Permutation ** permutations
int npermutations
const char * name

References PermutationStep::blockers, PermutationStepBlocker::blocktype, exit(), fprintf, free, i, j, name, PermutationStep::name, PermutationStep::nblockers, TestSpec::npermutations, TestSpec::nsessions, Session::nsteps, Permutation::nsteps, TestSpec::permutations, pg_malloc(), PSB_ONCE, qsort, Step::session, TestSpec::sessions, PermutationStepBlocker::step, PermutationStep::step, step_bsearch_cmp(), step_qsort_cmp(), PermutationStepBlocker::stepname, Session::steps, Permutation::steps, and Step::used.

Referenced by main().

◆ disconnect_atexit()

static void disconnect_atexit ( void  )
static

Definition at line 77 of file isolationtester.c.

78 {
79  int i;
80 
81  for (i = 0; i < nconns; i++)
82  if (conns[i].conn)
83  PQfinish(conns[i].conn);
84 }
void PQfinish(PGconn *conn)
Definition: fe-connect.c:4868
static IsoConnInfo * conns
static int nconns
PGconn * conn
Definition: streamutil.c:55

References conn, conns, i, nconns, and PQfinish().

Referenced by main().

◆ isotesterNoticeProcessor()

static void isotesterNoticeProcessor ( void *  arg,
const char *  message 
)
static

Definition at line 1126 of file isolationtester.c.

1127 {
1128  IsoConnInfo *myconn = (IsoConnInfo *) arg;
1129 
1130  /* Prefix the backend's message with the session name. */
1131  printf("%s: %s", myconn->sessionname, message);
1132  /* Record notices, since we may need this to decide to unblock a step. */
1133  myconn->total_notices++;
1134  any_new_notice = true;
1135 }
static bool any_new_notice
void * arg
#define printf(...)
Definition: port.h:244
const char * sessionname

References any_new_notice, arg, printf, IsoConnInfo::sessionname, and IsoConnInfo::total_notices.

Referenced by main().

◆ main()

int main ( int  argc,
char **  argv 
)

Definition at line 87 of file isolationtester.c.

88 {
89  const char *conninfo;
90  const char *env_wait;
91  TestSpec *testspec;
92  PGresult *res;
93  PQExpBufferData wait_query;
94  int opt;
95  int i;
96 
97  while ((opt = getopt(argc, argv, "V")) != -1)
98  {
99  switch (opt)
100  {
101  case 'V':
102  puts("isolationtester (PostgreSQL) " PG_VERSION);
103  exit(0);
104  default:
105  fprintf(stderr, "Usage: isolationtester [CONNINFO]\n");
106  return EXIT_FAILURE;
107  }
108  }
109 
110  /*
111  * Make stdout unbuffered to match stderr; and ensure stderr is unbuffered
112  * too, which it should already be everywhere except sometimes in Windows.
113  */
114  setbuf(stdout, NULL);
115  setbuf(stderr, NULL);
116 
117  /*
118  * If the user supplies a non-option parameter on the command line, use it
119  * as the conninfo string; otherwise default to setting dbname=postgres
120  * and using environment variables or defaults for all other connection
121  * parameters.
122  */
123  if (argc > optind)
124  conninfo = argv[optind];
125  else
126  conninfo = "dbname = postgres";
127 
128  /*
129  * If PG_TEST_TIMEOUT_DEFAULT is set, adopt its value (given in seconds)
130  * as half the max time to wait for any one step to complete.
131  */
132  env_wait = getenv("PG_TEST_TIMEOUT_DEFAULT");
133  if (env_wait != NULL)
134  max_step_wait = 2 * ((int64) atoi(env_wait)) * USECS_PER_SEC;
135 
136  /* Read the test spec from stdin */
137  spec_yyparse();
138  testspec = &parseresult;
139 
140  /* Perform post-parse checking, and fill in linking fields */
141  check_testspec(testspec);
142 
143  printf("Parsed test spec with %d sessions\n", testspec->nsessions);
144 
145  /*
146  * Establish connections to the database, one for each session and an
147  * extra for lock wait detection and global work.
148  */
149  nconns = 1 + testspec->nsessions;
150  conns = (IsoConnInfo *) pg_malloc0(nconns * sizeof(IsoConnInfo));
151  atexit(disconnect_atexit);
152 
153  for (i = 0; i < nconns; i++)
154  {
155  const char *sessionname;
156 
157  if (i == 0)
158  sessionname = "control connection";
159  else
160  sessionname = testspec->sessions[i - 1]->name;
161 
162  conns[i].sessionname = sessionname;
163 
164  conns[i].conn = PQconnectdb(conninfo);
165  if (PQstatus(conns[i].conn) != CONNECTION_OK)
166  {
167  fprintf(stderr, "Connection %d failed: %s",
169  exit(1);
170  }
171 
172  /*
173  * Set up notice processors for the user-defined connections, so that
174  * messages can get printed prefixed with the session names. The
175  * control connection gets a "blackhole" processor instead (hides all
176  * messages).
177  */
178  if (i != 0)
181  (void *) &conns[i]);
182  else
185  NULL);
186 
187  /*
188  * Similarly, append the session name to application_name to make it
189  * easier to map spec file sessions to log output and
190  * pg_stat_activity. The reason to append instead of just setting the
191  * name is that we don't know the name of the test currently running.
192  */
194  "SELECT set_config('application_name',\n"
195  " current_setting('application_name') || '/' || $1,\n"
196  " false)",
197  1, NULL,
198  &sessionname,
199  NULL, NULL, 0);
201  {
202  fprintf(stderr, "setting of application name failed: %s",
204  exit(1);
205  }
206 
207  /* Save each connection's backend PID for subsequent use. */
209  conns[i].backend_pid_str = psprintf("%d", conns[i].backend_pid);
210  }
211 
212  /*
213  * Build the query we'll use to detect lock contention among sessions in
214  * the test specification. Most of the time, we could get away with
215  * simply checking whether a session is waiting for *any* lock: we don't
216  * exactly expect concurrent use of test tables. However, autovacuum will
217  * occasionally take AccessExclusiveLock to truncate a table, and we must
218  * ignore that transient wait.
219  */
220  initPQExpBuffer(&wait_query);
221  appendPQExpBufferStr(&wait_query,
222  "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
223  /* The spec syntax requires at least one session; assume that here. */
224  appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
225  for (i = 2; i < nconns; i++)
226  appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
227  appendPQExpBufferStr(&wait_query, "}')");
228 
229  res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
231  {
232  fprintf(stderr, "prepare of lock wait query failed: %s",
234  exit(1);
235  }
236  PQclear(res);
237  termPQExpBuffer(&wait_query);
238 
239  /*
240  * Run the permutations specified in the spec, or all if none were
241  * explicitly specified.
242  */
243  run_testspec(testspec);
244 
245  return 0;
246 }
#define USECS_PER_SEC
Definition: timestamp.h:134
char * PQerrorMessage(const PGconn *conn)
Definition: fe-connect.c:7147
ConnStatusType PQstatus(const PGconn *conn)
Definition: fe-connect.c:7094
PGconn * PQconnectdb(const char *conninfo)
Definition: fe-connect.c:744
int PQbackendPID(const PGconn *conn)
Definition: fe-connect.c:7181
PQnoticeProcessor PQsetNoticeProcessor(PGconn *conn, PQnoticeProcessor proc, void *arg)
Definition: fe-connect.c:7326
PGresult * PQprepare(PGconn *conn, const char *stmtName, const char *query, int nParams, const Oid *paramTypes)
Definition: fe-exec.c:2306
PGresult * PQexecParams(PGconn *conn, const char *command, int nParams, const Oid *paramTypes, const char *const *paramValues, const int *paramLengths, const int *paramFormats, int resultFormat)
Definition: fe-exec.c:2276
ExecStatusType PQresultStatus(const PGresult *res)
Definition: fe-exec.c:3411
void * pg_malloc0(size_t size)
Definition: fe_memutils.c:53
static void run_testspec(TestSpec *testspec)
static void blackholeNoticeProcessor(void *arg, const char *message)
static void check_testspec(TestSpec *testspec)
static void isotesterNoticeProcessor(void *arg, const char *message)
static int64 max_step_wait
#define PREP_WAITING
static void disconnect_atexit(void)
TestSpec parseresult
int spec_yyparse(void)
@ CONNECTION_OK
Definition: libpq-fe.h:61
@ PGRES_COMMAND_OK
Definition: libpq-fe.h:100
@ PGRES_TUPLES_OK
Definition: libpq-fe.h:103
PGDLLIMPORT int optind
Definition: getopt.c:50
int getopt(int nargc, char *const *nargv, const char *ostr)
Definition: getopt.c:71
void initPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:90
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
Definition: pqexpbuffer.c:265
void appendPQExpBufferStr(PQExpBuffer str, const char *data)
Definition: pqexpbuffer.c:367
void termPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:129
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
#define EXIT_FAILURE
Definition: settings.h:167
const char * backend_pid_str
PGconn * conn
char * name

References appendPQExpBuffer(), appendPQExpBufferStr(), IsoConnInfo::backend_pid, IsoConnInfo::backend_pid_str, blackholeNoticeProcessor(), check_testspec(), conn, IsoConnInfo::conn, CONNECTION_OK, conns, PQExpBufferData::data, disconnect_atexit(), exit(), EXIT_FAILURE, fprintf, getopt(), i, initPQExpBuffer(), isotesterNoticeProcessor(), max_step_wait, Session::name, nconns, TestSpec::nsessions, optind, parseresult, pg_malloc0(), PGRES_COMMAND_OK, PGRES_TUPLES_OK, PQbackendPID(), PQclear(), PQconnectdb(), PQerrorMessage(), PQexecParams(), PQprepare(), PQresultStatus(), PQsetNoticeProcessor(), PQstatus(), PREP_WAITING, printf, psprintf(), res, run_testspec(), IsoConnInfo::sessionname, TestSpec::sessions, spec_yyparse(), generate_unaccent_rules::stdout, termPQExpBuffer(), and USECS_PER_SEC.

◆ printResultSet()

static void printResultSet ( PGresult res)
static

Definition at line 1113 of file isolationtester.c.

1114 {
1115  PQprintOpt popt;
1116 
1117  memset(&popt, 0, sizeof(popt));
1118  popt.header = true;
1119  popt.align = true;
1120  popt.fieldSep = "|";
1121  PQprint(stdout, res, &popt);
1122 }
void PQprint(FILE *fout, const PGresult *res, const PQprintOpt *po)
Definition: fe-print.c:68
pqbool align
Definition: libpq-fe.h:215
pqbool header
Definition: libpq-fe.h:214
char * fieldSep
Definition: libpq-fe.h:220

References _PQprintOpt::align, _PQprintOpt::fieldSep, _PQprintOpt::header, PQprint(), res, and generate_unaccent_rules::stdout.

Referenced by run_permutation(), and try_complete_step().

◆ run_all_permutations()

static void run_all_permutations ( TestSpec testspec)
static

Definition at line 406 of file isolationtester.c.

407 {
408  int nsteps;
409  int i;
410  PermutationStep *steps;
411  PermutationStep **stepptrs;
412  int *piles;
413 
414  /* Count the total number of steps in all sessions */
415  nsteps = 0;
416  for (i = 0; i < testspec->nsessions; i++)
417  nsteps += testspec->sessions[i]->nsteps;
418 
419  /* Create PermutationStep workspace array */
420  steps = (PermutationStep *) pg_malloc0(sizeof(PermutationStep) * nsteps);
421  stepptrs = (PermutationStep **) pg_malloc(sizeof(PermutationStep *) * nsteps);
422  for (i = 0; i < nsteps; i++)
423  stepptrs[i] = steps + i;
424 
425  /*
426  * To generate the permutations, we conceptually put the steps of each
427  * session on a pile. To generate a permutation, we pick steps from the
428  * piles until all piles are empty. By picking steps from piles in
429  * different order, we get different permutations.
430  *
431  * A pile is actually just an integer which tells how many steps we've
432  * already picked from this pile.
433  */
434  piles = pg_malloc(sizeof(int) * testspec->nsessions);
435  for (i = 0; i < testspec->nsessions; i++)
436  piles[i] = 0;
437 
438  run_all_permutations_recurse(testspec, piles, 0, stepptrs);
439 
440  free(steps);
441  free(stepptrs);
442  free(piles);
443 }
static void run_all_permutations_recurse(TestSpec *testspec, int *piles, int nsteps, PermutationStep **steps)

References free, i, TestSpec::nsessions, Session::nsteps, pg_malloc(), pg_malloc0(), run_all_permutations_recurse(), and TestSpec::sessions.

Referenced by run_testspec().

◆ run_all_permutations_recurse()

static void run_all_permutations_recurse ( TestSpec testspec,
int *  piles,
int  nsteps,
PermutationStep **  steps 
)
static

Definition at line 446 of file isolationtester.c.

448 {
449  int i;
450  bool found = false;
451 
452  for (i = 0; i < testspec->nsessions; i++)
453  {
454  /* If there's any more steps in this pile, pick it and recurse */
455  if (piles[i] < testspec->sessions[i]->nsteps)
456  {
457  Step *newstep = testspec->sessions[i]->steps[piles[i]];
458 
459  /*
460  * These automatically-generated PermutationSteps never have
461  * blocker conditions. So we need only fill these fields, relying
462  * on run_all_permutations() to have zeroed the rest:
463  */
464  steps[nsteps]->name = newstep->name;
465  steps[nsteps]->step = newstep;
466 
467  piles[i]++;
468 
469  run_all_permutations_recurse(testspec, piles, nsteps + 1, steps);
470 
471  piles[i]--;
472 
473  found = true;
474  }
475  }
476 
477  /* If all the piles were empty, this permutation is completed. Run it */
478  if (!found)
479  run_permutation(testspec, nsteps, steps);
480 }
static void run_permutation(TestSpec *testspec, int nsteps, PermutationStep **steps)
char * name

References i, Step::name, PermutationStep::name, TestSpec::nsessions, Session::nsteps, run_permutation(), TestSpec::sessions, PermutationStep::step, and Session::steps.

Referenced by run_all_permutations().

◆ run_named_permutations()

static void run_named_permutations ( TestSpec testspec)
static

Definition at line 486 of file isolationtester.c.

487 {
488  int i;
489 
490  for (i = 0; i < testspec->npermutations; i++)
491  {
492  Permutation *p = testspec->permutations[i];
493 
494  run_permutation(testspec, p->nsteps, p->steps);
495  }
496 }

References i, TestSpec::npermutations, Permutation::nsteps, TestSpec::permutations, run_permutation(), and Permutation::steps.

Referenced by run_testspec().

◆ run_permutation()

static void run_permutation ( TestSpec testspec,
int  nsteps,
PermutationStep **  steps 
)
static

Definition at line 520 of file isolationtester.c.

521 {
522  PGresult *res;
523  int i;
524  int nwaiting = 0;
526 
527  waiting = pg_malloc(sizeof(PermutationStep *) * testspec->nsessions);
528 
529  printf("\nstarting permutation:");
530  for (i = 0; i < nsteps; i++)
531  printf(" %s", steps[i]->name);
532  printf("\n");
533 
534  /* Perform setup */
535  for (i = 0; i < testspec->nsetupsqls; i++)
536  {
537  res = PQexec(conns[0].conn, testspec->setupsqls[i]);
539  {
541  }
542  else if (PQresultStatus(res) != PGRES_COMMAND_OK)
543  {
544  fprintf(stderr, "setup failed: %s", PQerrorMessage(conns[0].conn));
545  exit(1);
546  }
547  PQclear(res);
548  }
549 
550  /* Perform per-session setup */
551  for (i = 0; i < testspec->nsessions; i++)
552  {
553  if (testspec->sessions[i]->setupsql)
554  {
555  res = PQexec(conns[i + 1].conn, testspec->sessions[i]->setupsql);
557  {
559  }
560  else if (PQresultStatus(res) != PGRES_COMMAND_OK)
561  {
562  fprintf(stderr, "setup of session %s failed: %s",
563  conns[i + 1].sessionname,
564  PQerrorMessage(conns[i + 1].conn));
565  exit(1);
566  }
567  PQclear(res);
568  }
569  }
570 
571  /* Perform steps */
572  for (i = 0; i < nsteps; i++)
573  {
574  PermutationStep *pstep = steps[i];
575  Step *step = pstep->step;
576  IsoConnInfo *iconn = &conns[1 + step->session];
577  PGconn *conn = iconn->conn;
578  bool mustwait;
579  int j;
580 
581  /*
582  * Check whether the session that needs to perform the next step is
583  * still blocked on an earlier step. If so, wait for it to finish.
584  */
585  if (iconn->active_step != NULL)
586  {
587  struct timeval start_time;
588 
589  gettimeofday(&start_time, NULL);
590 
591  while (iconn->active_step != NULL)
592  {
593  PermutationStep *oldstep = iconn->active_step;
594 
595  /*
596  * Wait for oldstep. But even though we don't use
597  * STEP_NONBLOCK, it might not complete because of blocker
598  * conditions.
599  */
600  if (!try_complete_step(testspec, oldstep, STEP_RETRY))
601  {
602  /* Done, so remove oldstep from the waiting[] array. */
603  int w;
604 
605  for (w = 0; w < nwaiting; w++)
606  {
607  if (oldstep == waiting[w])
608  break;
609  }
610  if (w >= nwaiting)
611  abort(); /* can't happen */
612  if (w + 1 < nwaiting)
613  memmove(&waiting[w], &waiting[w + 1],
614  (nwaiting - (w + 1)) * sizeof(PermutationStep *));
615  nwaiting--;
616  }
617 
618  /*
619  * Check for other steps that have finished. We should do
620  * this if oldstep completed, as it might have unblocked
621  * something. On the other hand, if oldstep hasn't completed,
622  * we must poll all the active steps in hopes of unblocking
623  * oldstep. So either way, poll them.
624  */
625  nwaiting = try_complete_steps(testspec, waiting, nwaiting,
627 
628  /*
629  * If the target session is still busy, apply a timeout to
630  * keep from hanging indefinitely, which could happen with
631  * incorrect blocker annotations. Use the same 2 *
632  * max_step_wait limit as try_complete_step does for deciding
633  * to die. (We don't bother with trying to cancel anything,
634  * since it's unclear what to cancel in this case.)
635  */
636  if (iconn->active_step != NULL)
637  {
638  struct timeval current_time;
639  int64 td;
640 
641  gettimeofday(&current_time, NULL);
642  td = (int64) current_time.tv_sec - (int64) start_time.tv_sec;
643  td *= USECS_PER_SEC;
644  td += (int64) current_time.tv_usec - (int64) start_time.tv_usec;
645  if (td > 2 * max_step_wait)
646  {
647  fprintf(stderr, "step %s timed out after %d seconds\n",
648  iconn->active_step->name,
649  (int) (td / USECS_PER_SEC));
650  fprintf(stderr, "active steps are:");
651  for (j = 1; j < nconns; j++)
652  {
653  IsoConnInfo *oconn = &conns[j];
654 
655  if (oconn->active_step != NULL)
656  fprintf(stderr, " %s",
657  oconn->active_step->name);
658  }
659  fprintf(stderr, "\n");
660  exit(1);
661  }
662  }
663  }
664  }
665 
666  /* Send the query for this step. */
667  if (!PQsendQuery(conn, step->sql))
668  {
669  fprintf(stdout, "failed to send query for step %s: %s\n",
670  step->name, PQerrorMessage(conn));
671  exit(1);
672  }
673 
674  /* Remember we launched a step. */
675  iconn->active_step = pstep;
676 
677  /* Remember target number of NOTICEs for any blocker conditions. */
678  for (j = 0; j < pstep->nblockers; j++)
679  {
680  PermutationStepBlocker *blocker = pstep->blockers[j];
681 
682  if (blocker->blocktype == PSB_NUM_NOTICES)
683  blocker->target_notices = blocker->num_notices +
684  conns[blocker->step->session + 1].total_notices;
685  }
686 
687  /* Try to complete this step without blocking. */
688  mustwait = try_complete_step(testspec, pstep, STEP_NONBLOCK);
689 
690  /* Check for completion of any steps that were previously waiting. */
691  nwaiting = try_complete_steps(testspec, waiting, nwaiting,
693 
694  /* If this step is waiting, add it to the array of waiters. */
695  if (mustwait)
696  waiting[nwaiting++] = pstep;
697  }
698 
699  /* Wait for any remaining queries. */
700  nwaiting = try_complete_steps(testspec, waiting, nwaiting, STEP_RETRY);
701  if (nwaiting != 0)
702  {
703  fprintf(stderr, "failed to complete permutation due to mutually-blocking steps\n");
704  exit(1);
705  }
706 
707  /* Perform per-session teardown */
708  for (i = 0; i < testspec->nsessions; i++)
709  {
710  if (testspec->sessions[i]->teardownsql)
711  {
712  res = PQexec(conns[i + 1].conn, testspec->sessions[i]->teardownsql);
714  {
716  }
717  else if (PQresultStatus(res) != PGRES_COMMAND_OK)
718  {
719  fprintf(stderr, "teardown of session %s failed: %s",
720  conns[i + 1].sessionname,
721  PQerrorMessage(conns[i + 1].conn));
722  /* don't exit on teardown failure */
723  }
724  PQclear(res);
725  }
726  }
727 
728  /* Perform teardown */
729  if (testspec->teardownsql)
730  {
731  res = PQexec(conns[0].conn, testspec->teardownsql);
733  {
735  }
736  else if (PQresultStatus(res) != PGRES_COMMAND_OK)
737  {
738  fprintf(stderr, "teardown failed: %s",
740  /* don't exit on teardown failure */
741  }
742  PQclear(res);
743  }
744 
745  free(waiting);
746 }
PGresult * PQexec(PGconn *conn, const char *query)
Definition: fe-exec.c:2262
int PQsendQuery(PGconn *conn, const char *query)
Definition: fe-exec.c:1416
static void printResultSet(PGresult *res)
#define STEP_NONBLOCK
static int try_complete_steps(TestSpec *testspec, PermutationStep **waiting, int nwaiting, int flags)
static bool try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags)
#define STEP_RETRY
@ PSB_NUM_NOTICES
static volatile sig_atomic_t waiting
Definition: latch.c:162
static time_t start_time
Definition: pg_ctl.c:94
PermutationStep * active_step
char * teardownsql
char * setupsql
char * sql
char ** setupsqls
char * teardownsql
int gettimeofday(struct timeval *tp, void *tzp)

References IsoConnInfo::active_step, PermutationStep::blockers, PermutationStepBlocker::blocktype, conn, IsoConnInfo::conn, conns, exit(), fprintf, free, gettimeofday(), i, j, max_step_wait, name, Step::name, PermutationStep::name, PermutationStep::nblockers, nconns, TestSpec::nsessions, TestSpec::nsetupsqls, PermutationStepBlocker::num_notices, pg_malloc(), PGRES_COMMAND_OK, PGRES_TUPLES_OK, PQclear(), PQerrorMessage(), PQexec(), PQresultStatus(), PQsendQuery(), printf, printResultSet(), PSB_NUM_NOTICES, res, Step::session, TestSpec::sessions, Session::setupsql, TestSpec::setupsqls, Step::sql, start_time, generate_unaccent_rules::stdout, PermutationStepBlocker::step, PermutationStep::step, STEP_NONBLOCK, STEP_RETRY, PermutationStepBlocker::target_notices, Session::teardownsql, TestSpec::teardownsql, IsoConnInfo::total_notices, try_complete_step(), try_complete_steps(), USECS_PER_SEC, and waiting.

Referenced by run_all_permutations_recurse(), and run_named_permutations().

◆ run_testspec()

static void run_testspec ( TestSpec testspec)
static

Definition at line 394 of file isolationtester.c.

395 {
396  if (testspec->permutations)
397  run_named_permutations(testspec);
398  else
399  run_all_permutations(testspec);
400 }
static void run_named_permutations(TestSpec *testspec)
static void run_all_permutations(TestSpec *testspec)

References TestSpec::permutations, run_all_permutations(), and run_named_permutations().

Referenced by main().

◆ step_bsearch_cmp()

static int step_bsearch_cmp ( const void *  a,
const void *  b 
)
static

Definition at line 508 of file isolationtester.c.

509 {
510  char *stepname = (char *) a;
511  Step *step = *((Step **) b);
512 
513  return strcmp(stepname, step->name);
514 }
int b
Definition: isn.c:70
int a
Definition: isn.c:69

References a, b, and Step::name.

Referenced by check_testspec().

◆ step_has_blocker()

static bool step_has_blocker ( PermutationStep pstep)
static

Definition at line 1080 of file isolationtester.c.

1081 {
1082  int i;
1083 
1084  for (i = 0; i < pstep->nblockers; i++)
1085  {
1086  PermutationStepBlocker *blocker = pstep->blockers[i];
1087  IsoConnInfo *iconn;
1088 
1089  switch (blocker->blocktype)
1090  {
1091  case PSB_ONCE:
1092  /* Ignore; try_complete_step handles this specially */
1093  break;
1094  case PSB_OTHER_STEP:
1095  /* Block if referenced step is active */
1096  iconn = &conns[1 + blocker->step->session];
1097  if (iconn->active_step &&
1098  iconn->active_step->step == blocker->step)
1099  return true;
1100  break;
1101  case PSB_NUM_NOTICES:
1102  /* Block if not enough notices received yet */
1103  iconn = &conns[1 + blocker->step->session];
1104  if (iconn->total_notices < blocker->target_notices)
1105  return true;
1106  break;
1107  }
1108  }
1109  return false;
1110 }
@ PSB_OTHER_STEP

References IsoConnInfo::active_step, PermutationStep::blockers, PermutationStepBlocker::blocktype, conns, i, PermutationStep::nblockers, PSB_NUM_NOTICES, PSB_ONCE, PSB_OTHER_STEP, Step::session, PermutationStepBlocker::step, PermutationStep::step, PermutationStepBlocker::target_notices, and IsoConnInfo::total_notices.

Referenced by try_complete_step().

◆ step_qsort_cmp()

static int step_qsort_cmp ( const void *  a,
const void *  b 
)
static

Definition at line 499 of file isolationtester.c.

500 {
501  Step *stepa = *((Step **) a);
502  Step *stepb = *((Step **) b);
503 
504  return strcmp(stepa->name, stepb->name);
505 }

References a, b, and Step::name.

Referenced by check_testspec().

◆ try_complete_step()

static bool try_complete_step ( TestSpec testspec,
PermutationStep pstep,
int  flags 
)
static

Definition at line 818 of file isolationtester.c.

819 {
820  Step *step = pstep->step;
821  IsoConnInfo *iconn = &conns[1 + step->session];
822  PGconn *conn = iconn->conn;
823  fd_set read_set;
824  struct timeval start_time;
825  struct timeval timeout;
826  int sock = PQsocket(conn);
827  int ret;
828  PGresult *res;
829  PGnotify *notify;
830  bool canceled = false;
831 
832  /*
833  * If the step is annotated with (*), then on the first call, force it to
834  * wait. This is useful for ensuring consistent output when the step
835  * might or might not complete so fast that we don't observe it waiting.
836  */
837  if (!(flags & STEP_RETRY))
838  {
839  int i;
840 
841  for (i = 0; i < pstep->nblockers; i++)
842  {
843  PermutationStepBlocker *blocker = pstep->blockers[i];
844 
845  if (blocker->blocktype == PSB_ONCE)
846  {
847  printf("step %s: %s <waiting ...>\n",
848  step->name, step->sql);
849  return true;
850  }
851  }
852  }
853 
854  if (sock < 0)
855  {
856  fprintf(stderr, "invalid socket: %s", PQerrorMessage(conn));
857  exit(1);
858  }
859 
860  gettimeofday(&start_time, NULL);
861  FD_ZERO(&read_set);
862 
863  while (PQisBusy(conn))
864  {
865  FD_SET(sock, &read_set);
866  timeout.tv_sec = 0;
867  timeout.tv_usec = 10000; /* Check for lock waits every 10ms. */
868 
869  ret = select(sock + 1, &read_set, NULL, NULL, &timeout);
870  if (ret < 0) /* error in select() */
871  {
872  if (errno == EINTR)
873  continue;
874  fprintf(stderr, "select failed: %m\n");
875  exit(1);
876  }
877  else if (ret == 0) /* select() timeout: check for lock wait */
878  {
879  struct timeval current_time;
880  int64 td;
881 
882  /* If it's OK for the step to block, check whether it has. */
883  if (flags & STEP_NONBLOCK)
884  {
885  bool waiting;
886 
888  &conns[step->session + 1].backend_pid_str,
889  NULL, NULL, 0);
891  PQntuples(res) != 1)
892  {
893  fprintf(stderr, "lock wait query failed: %s",
895  exit(1);
896  }
897  waiting = ((PQgetvalue(res, 0, 0))[0] == 't');
898  PQclear(res);
899 
900  if (waiting) /* waiting to acquire a lock */
901  {
902  /*
903  * Since it takes time to perform the lock-check query,
904  * some data --- notably, NOTICE messages --- might have
905  * arrived since we looked. We must call PQconsumeInput
906  * and then PQisBusy to collect and process any such
907  * messages. In the (unlikely) case that PQisBusy then
908  * returns false, we might as well go examine the
909  * available result.
910  */
911  if (!PQconsumeInput(conn))
912  {
913  fprintf(stderr, "PQconsumeInput failed: %s\n",
915  exit(1);
916  }
917  if (!PQisBusy(conn))
918  break;
919 
920  /*
921  * conn is still busy, so conclude that the step really is
922  * waiting.
923  */
924  if (!(flags & STEP_RETRY))
925  printf("step %s: %s <waiting ...>\n",
926  step->name, step->sql);
927  return true;
928  }
929  /* else, not waiting */
930  }
931 
932  /* Figure out how long we've been waiting for this step. */
933  gettimeofday(&current_time, NULL);
934  td = (int64) current_time.tv_sec - (int64) start_time.tv_sec;
935  td *= USECS_PER_SEC;
936  td += (int64) current_time.tv_usec - (int64) start_time.tv_usec;
937 
938  /*
939  * After max_step_wait microseconds, try to cancel the query.
940  *
941  * If the user tries to test an invalid permutation, we don't want
942  * to hang forever, especially when this is running in the
943  * buildfarm. This will presumably lead to this permutation
944  * failing, but remaining permutations and tests should still be
945  * OK.
946  */
947  if (td > max_step_wait && !canceled)
948  {
949  PGcancelConn *cancel_conn = PQcancelCreate(conn);
950 
951  if (PQcancelBlocking(cancel_conn))
952  {
953  /*
954  * print to stdout not stderr, as this should appear in
955  * the test case's results
956  */
957  printf("isolationtester: canceling step %s after %d seconds\n",
958  step->name, (int) (td / USECS_PER_SEC));
959  canceled = true;
960  }
961  else
962  fprintf(stderr, "PQcancel failed: %s\n", PQcancelErrorMessage(cancel_conn));
963  PQcancelFinish(cancel_conn);
964  }
965 
966  /*
967  * After twice max_step_wait, just give up and die.
968  *
969  * Since cleanup steps won't be run in this case, this may cause
970  * later tests to fail. That stinks, but it's better than waiting
971  * forever for the server to respond to the cancel.
972  */
973  if (td > 2 * max_step_wait)
974  {
975  fprintf(stderr, "step %s timed out after %d seconds\n",
976  step->name, (int) (td / USECS_PER_SEC));
977  exit(1);
978  }
979  }
980  else if (!PQconsumeInput(conn)) /* select(): data available */
981  {
982  fprintf(stderr, "PQconsumeInput failed: %s\n",
984  exit(1);
985  }
986  }
987 
988  /*
989  * The step is done, but we won't report it as complete so long as there
990  * are blockers.
991  */
992  if (step_has_blocker(pstep))
993  {
994  if (!(flags & STEP_RETRY))
995  printf("step %s: %s <waiting ...>\n",
996  step->name, step->sql);
997  return true;
998  }
999 
1000  /* Otherwise, go ahead and complete it. */
1001  if (flags & STEP_RETRY)
1002  printf("step %s: <... completed>\n", step->name);
1003  else
1004  printf("step %s: %s\n", step->name, step->sql);
1005 
1006  while ((res = PQgetResult(conn)))
1007  {
1008  switch (PQresultStatus(res))
1009  {
1010  case PGRES_COMMAND_OK:
1011  case PGRES_EMPTY_QUERY:
1012  break;
1013  case PGRES_TUPLES_OK:
1015  break;
1016  case PGRES_FATAL_ERROR:
1017 
1018  /*
1019  * Detail may contain XID values, so we want to just show
1020  * primary. Beware however that libpq-generated error results
1021  * may not contain subfields, only an old-style message.
1022  */
1023  {
1024  const char *sev = PQresultErrorField(res,
1026  const char *msg = PQresultErrorField(res,
1028 
1029  if (sev && msg)
1030  printf("%s: %s\n", sev, msg);
1031  else
1032  printf("%s\n", PQresultErrorMessage(res));
1033  }
1034  break;
1035  default:
1036  printf("unexpected result status: %s\n",
1038  }
1039  PQclear(res);
1040  }
1041 
1042  /* Report any available NOTIFY messages, too */
1044  while ((notify = PQnotifies(conn)) != NULL)
1045  {
1046  /* Try to identify which session it came from */
1047  const char *sendername = NULL;
1048  char pidstring[32];
1049  int i;
1050 
1051  for (i = 0; i < testspec->nsessions; i++)
1052  {
1053  if (notify->be_pid == conns[i + 1].backend_pid)
1054  {
1055  sendername = conns[i + 1].sessionname;
1056  break;
1057  }
1058  }
1059  if (sendername == NULL)
1060  {
1061  /* Doesn't seem to be any test session, so show the hard way */
1062  snprintf(pidstring, sizeof(pidstring), "PID %d", notify->be_pid);
1063  sendername = pidstring;
1064  }
1065  printf("%s: NOTIFY \"%s\" with payload \"%s\" from %s\n",
1066  testspec->sessions[step->session]->name,
1067  notify->relname, notify->extra, sendername);
1068  PQfreemem(notify);
1070  }
1071 
1072  /* Connection is now idle. */
1073  iconn->active_step = NULL;
1074 
1075  return false;
1076 }
PGcancelConn * PQcancelCreate(PGconn *conn)
Definition: fe-cancel.c:65
int PQcancelBlocking(PGcancelConn *cancelConn)
Definition: fe-cancel.c:171
char * PQcancelErrorMessage(const PGcancelConn *cancelConn)
Definition: fe-cancel.c:305
void PQcancelFinish(PGcancelConn *cancelConn)
Definition: fe-cancel.c:333
int PQsocket(const PGconn *conn)
Definition: fe-connect.c:7173
void PQfreemem(void *ptr)
Definition: fe-exec.c:4032
PGnotify * PQnotifies(PGconn *conn)
Definition: fe-exec.c:2667
char * PQresultErrorMessage(const PGresult *res)
Definition: fe-exec.c:3427
int PQntuples(const PGresult *res)
Definition: fe-exec.c:3481
PGresult * PQexecPrepared(PGconn *conn, const char *stmtName, int nParams, const char *const *paramValues, const int *paramLengths, const int *paramFormats, int resultFormat)
Definition: fe-exec.c:2323
int PQconsumeInput(PGconn *conn)
Definition: fe-exec.c:1984
char * PQgetvalue(const PGresult *res, int tup_num, int field_num)
Definition: fe-exec.c:3876
char * PQresStatus(ExecStatusType status)
Definition: fe-exec.c:3419
int PQisBusy(PGconn *conn)
Definition: fe-exec.c:2031
char * PQresultErrorField(const PGresult *res, int fieldcode)
Definition: fe-exec.c:3466
PGresult * PQgetResult(PGconn *conn)
Definition: fe-exec.c:2062
static bool step_has_blocker(PermutationStep *pstep)
@ PGRES_FATAL_ERROR
Definition: libpq-fe.h:111
@ PGRES_EMPTY_QUERY
Definition: libpq-fe.h:99
#define snprintf
Definition: port.h:238
#define PG_DIAG_MESSAGE_PRIMARY
Definition: postgres_ext.h:57
#define PG_DIAG_SEVERITY
Definition: postgres_ext.h:54
int be_pid
Definition: libpq-fe.h:199
char * relname
Definition: libpq-fe.h:198
char * extra
Definition: libpq-fe.h:200
#define EINTR
Definition: win32_port.h:374
#define select(n, r, w, e, timeout)
Definition: win32_port.h:495

References IsoConnInfo::active_step, IsoConnInfo::backend_pid, IsoConnInfo::backend_pid_str, pgNotify::be_pid, PermutationStep::blockers, PermutationStepBlocker::blocktype, conn, IsoConnInfo::conn, conns, EINTR, exit(), pgNotify::extra, fprintf, gettimeofday(), i, max_step_wait, Session::name, Step::name, PermutationStep::nblockers, TestSpec::nsessions, PG_DIAG_MESSAGE_PRIMARY, PG_DIAG_SEVERITY, PGRES_COMMAND_OK, PGRES_EMPTY_QUERY, PGRES_FATAL_ERROR, PGRES_TUPLES_OK, PQcancelBlocking(), PQcancelCreate(), PQcancelErrorMessage(), PQcancelFinish(), PQclear(), PQconsumeInput(), PQerrorMessage(), PQexecPrepared(), PQfreemem(), PQgetResult(), PQgetvalue(), PQisBusy(), PQnotifies(), PQntuples(), PQresStatus(), PQresultErrorField(), PQresultErrorMessage(), PQresultStatus(), PQsocket(), PREP_WAITING, printf, printResultSet(), PSB_ONCE, pgNotify::relname, res, select, Step::session, IsoConnInfo::sessionname, TestSpec::sessions, snprintf, Step::sql, start_time, PermutationStep::step, step_has_blocker(), STEP_NONBLOCK, STEP_RETRY, USECS_PER_SEC, and waiting.

Referenced by run_permutation(), and try_complete_steps().

◆ try_complete_steps()

static int try_complete_steps ( TestSpec testspec,
PermutationStep **  waiting,
int  nwaiting,
int  flags 
)
static

Definition at line 755 of file isolationtester.c.

757 {
758  int old_nwaiting;
759  bool have_blocker;
760 
761  do
762  {
763  int w = 0;
764 
765  /* Reset latch; we only care about notices received within loop. */
766  any_new_notice = false;
767 
768  /* Likewise, these variables reset for each retry. */
769  old_nwaiting = nwaiting;
770  have_blocker = false;
771 
772  /* Scan the array, try to complete steps. */
773  while (w < nwaiting)
774  {
775  if (try_complete_step(testspec, waiting[w], flags))
776  {
777  /* Still blocked, leave it alone. */
778  if (waiting[w]->nblockers > 0)
779  have_blocker = true;
780  w++;
781  }
782  else
783  {
784  /* Done, remove it from array. */
785  if (w + 1 < nwaiting)
786  memmove(&waiting[w], &waiting[w + 1],
787  (nwaiting - (w + 1)) * sizeof(PermutationStep *));
788  nwaiting--;
789  }
790  }
791 
792  /*
793  * If any of the still-waiting steps have blocker conditions attached,
794  * it's possible that one of the steps we examined afterwards has
795  * released them (either by completing, or by sending a NOTICE). If
796  * any step completions or NOTICEs happened, repeat the loop until
797  * none occurs. Without this provision, completion timing could vary
798  * depending on the order in which the steps appear in the array.
799  */
800  } while (have_blocker && (nwaiting < old_nwaiting || any_new_notice));
801  return nwaiting;
802 }

References any_new_notice, try_complete_step(), and waiting.

Referenced by run_permutation().

Variable Documentation

◆ any_new_notice

bool any_new_notice = false
static

Definition at line 44 of file isolationtester.c.

Referenced by isotesterNoticeProcessor(), and try_complete_steps().

◆ conns

IsoConnInfo* conns = NULL
static

◆ max_step_wait

int64 max_step_wait = 360 * USECS_PER_SEC
static

Definition at line 47 of file isolationtester.c.

Referenced by main(), run_permutation(), and try_complete_step().

◆ nconns

int nconns = 0
static

Definition at line 41 of file isolationtester.c.

Referenced by disconnect_atexit(), main(), and run_permutation().