Skip to content

Commit fdf26bb

Browse files
Dmitrii Bondarhackorum
authored andcommitted
Remove synchronous prepare from pgbench
Pgbench waits for the result of a prepare packet synchronously when the prepared protocol flag is provided. For this reason it is impossible to use it with some poolers (such as PgBouncer) in the session pooling mode. Replace the blocking PQprepare() call in the single-command path with an async PQsendPrepare(), and add a new CSTATE_WAIT_PREPARE_RESULT state to the connection state machine that drains the prepare result without stalling the thread's event loop. After the prepare succeeds, the prepared flag is set and the actual query is sent via PQsendQueryPrepared as before. The pipeline path (prepareCommandsInPipeline) is left synchronous because prepares must complete before PQenterPipelineMode to avoid acquiring a snapshot that would break BEGIN ISOLATION LEVEL SERIALIZABLE. Also fix a latent bug: prepareCommand used to set the prepared flag to true even when the prepare failed, so subsequent attempts ran PQsendQueryPrepared against a non-existent statement. The flag is now set only on success, and prepare failures are routed through the normal error path (CSTATE_ERROR for retryable errors, CSTATE_ABORTED otherwise). Update the sql-syntax-error TAP test accordingly: it no longer expects the downstream "prepared statement does not exist" error.
1 parent 44056f6 commit fdf26bb

2 files changed

Lines changed: 123 additions & 16 deletions

File tree

src/bin/pgbench/pgbench.c

Lines changed: 123 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,11 @@ typedef enum
537537
* CSTATE_WAIT_RESULT waits until we get a result set back from the server
538538
* for the current command.
539539
*
540+
* CSTATE_WAIT_PREPARE_RESULT waits for the result of an async PQsendPrepare
541+
* issued from CSTATE_START_COMMAND in QUERY_PREPARED mode. Once the prepare
542+
* succeeds, the prepared flag is set and we loop back to CSTATE_START_COMMAND
543+
* to send the actual query. On failure we go to CSTATE_ERROR.
544+
*
540545
* CSTATE_SLEEP waits until the end of \sleep.
541546
*
542547
* CSTATE_END_COMMAND records the end-of-command timestamp, increments the
@@ -549,6 +554,7 @@ typedef enum
549554
*/
550555
CSTATE_START_COMMAND,
551556
CSTATE_WAIT_RESULT,
557+
CSTATE_WAIT_PREPARE_RESULT,
552558
CSTATE_SLEEP,
553559
CSTATE_END_COMMAND,
554560
CSTATE_SKIP_COMMAND,
@@ -3108,6 +3114,30 @@ prepareCommand(CState *st, int command_num)
31083114
}
31093115
}
31103116

3117+
/*
3118+
* Asynchronously start preparing the SQL command from st->use_file at
3119+
* command_num. Caller must ensure st->prepared is allocated and that the
3120+
* command is not yet prepared. Returns true if the Prepare was sent; the
3121+
* caller must then wait for the result in CSTATE_WAIT_PREPARE_RESULT and set
3122+
* the prepared flag only after the result is read successfully.
3123+
*
3124+
* Unlike prepareCommand (synchronous, used for the pipeline path), this does
3125+
* not set st->prepared[...].
3126+
*/
3127+
static bool
3128+
sendPrepareCommand(CState *st, int command_num)
3129+
{
3130+
Command *command = sql_script[st->use_file].commands[command_num];
3131+
3132+
Assert(command->type == SQL_COMMAND);
3133+
Assert(st->prepared != NULL);
3134+
Assert(!st->prepared[st->use_file][command_num]);
3135+
3136+
pg_log_debug("client %d preparing %s", st->id, command->prepname);
3137+
return PQsendPrepare(st->con, command->prepname,
3138+
command->argv[0], command->argc - 1, NULL) != 0;
3139+
}
3140+
31113141
/*
31123142
* Prepare all the commands in the script that come after the \startpipeline
31133143
* that's at position st->command, and the first \endpipeline we find.
@@ -3147,8 +3177,16 @@ prepareCommandsInPipeline(CState *st)
31473177
st->prepared[st->use_file][st->command] = true;
31483178
}
31493179

3180+
/* Result of sendCommand(). */
3181+
typedef enum
3182+
{
3183+
SEND_FAILED, /* could not send */
3184+
SEND_OK, /* sent; wait for query result */
3185+
SEND_PREPARE_PENDING, /* async Prepare issued; wait for prepare result */
3186+
} SendCommandResult;
3187+
31503188
/* Send a SQL command, using the chosen querymode */
3151-
static bool
3189+
static SendCommandResult
31523190
sendCommand(CState *st, Command *command)
31533191
{
31543192
int r;
@@ -3179,7 +3217,20 @@ sendCommand(CState *st, Command *command)
31793217
{
31803218
const char *params[MAX_ARGS];
31813219

3182-
prepareCommand(st, st->command);
3220+
if (!st->prepared)
3221+
allocCStatePrepared(st);
3222+
3223+
if (!st->prepared[st->use_file][st->command])
3224+
{
3225+
if (!sendPrepareCommand(st, st->command))
3226+
{
3227+
pg_log_debug("client %d could not send %s",
3228+
st->id, command->argv[0]);
3229+
return SEND_FAILED;
3230+
}
3231+
return SEND_PREPARE_PENDING;
3232+
}
3233+
31833234
getQueryParams(&st->variables, command, params);
31843235

31853236
pg_log_debug("client %d sending %s", st->id, command->prepname);
@@ -3192,10 +3243,10 @@ sendCommand(CState *st, Command *command)
31923243
if (r == 0)
31933244
{
31943245
pg_log_debug("client %d could not send %s", st->id, command->argv[0]);
3195-
return false;
3246+
return SEND_FAILED;
31963247
}
31973248
else
3198-
return true;
3249+
return SEND_OK;
31993250
}
32003251

32013252
/*
@@ -3894,18 +3945,23 @@ advanceConnectionState(TState *thread, CState *st, StatsData *agg)
38943945
}
38953946
}
38963947

3897-
if (!sendCommand(st, command))
3948+
switch (sendCommand(st, command))
38983949
{
3899-
commandFailed(st, "SQL", "SQL command send failed");
3900-
st->state = CSTATE_ABORTED;
3901-
}
3902-
else
3903-
{
3904-
/* Wait for results, unless in pipeline mode */
3905-
if (PQpipelineStatus(st->con) == PQ_PIPELINE_OFF)
3906-
st->state = CSTATE_WAIT_RESULT;
3907-
else
3908-
st->state = CSTATE_END_COMMAND;
3950+
case SEND_FAILED:
3951+
commandFailed(st, "SQL", "SQL command send failed");
3952+
st->state = CSTATE_ABORTED;
3953+
break;
3954+
case SEND_PREPARE_PENDING:
3955+
/* Wait for the async Prepare result */
3956+
st->state = CSTATE_WAIT_PREPARE_RESULT;
3957+
break;
3958+
case SEND_OK:
3959+
/* Wait for results, unless in pipeline mode */
3960+
if (PQpipelineStatus(st->con) == PQ_PIPELINE_OFF)
3961+
st->state = CSTATE_WAIT_RESULT;
3962+
else
3963+
st->state = CSTATE_END_COMMAND;
3964+
break;
39093965
}
39103966
}
39113967
else if (command->type == META_COMMAND)
@@ -3927,6 +3983,7 @@ advanceConnectionState(TState *thread, CState *st, StatsData *agg)
39273983
* something bad happened.
39283984
*/
39293985
Assert(st->state == CSTATE_WAIT_RESULT ||
3986+
st->state == CSTATE_WAIT_PREPARE_RESULT ||
39303987
st->state == CSTATE_END_COMMAND ||
39313988
st->state == CSTATE_SLEEP ||
39323989
st->state == CSTATE_ABORTED);
@@ -4070,6 +4127,55 @@ advanceConnectionState(TState *thread, CState *st, StatsData *agg)
40704127
st->state = CSTATE_ABORTED;
40714128
break;
40724129

4130+
/*
4131+
* Wait for the async PQsendPrepare result.
4132+
*/
4133+
case CSTATE_WAIT_PREPARE_RESULT:
4134+
{
4135+
PGresult *res;
4136+
4137+
pg_log_debug("client %d receiving prepare result", st->id);
4138+
4139+
if (PQisBusy(st->con) && !PQconsumeInput(st->con))
4140+
{
4141+
commandFailed(st, "SQL", "perhaps the backend died while preparing");
4142+
st->state = CSTATE_ABORTED;
4143+
break;
4144+
}
4145+
if (PQisBusy(st->con))
4146+
return; /* don't have the whole result yet */
4147+
4148+
res = PQgetResult(st->con);
4149+
if (PQresultStatus(res) == PGRES_COMMAND_OK)
4150+
{
4151+
PQclear(res);
4152+
/* null terminator expected after a single result */
4153+
res = PQgetResult(st->con);
4154+
Assert(res == NULL);
4155+
4156+
st->prepared[st->use_file][st->command] = true;
4157+
/* Re-enter START_COMMAND to send the actual query */
4158+
st->state = CSTATE_START_COMMAND;
4159+
}
4160+
else
4161+
{
4162+
const char *sqlstate = PQresultErrorField(res,
4163+
PG_DIAG_SQLSTATE);
4164+
st->estatus = getSQLErrorStatus(st, sqlstate);
4165+
pg_log_error("%s", PQerrorMessage(st->con));
4166+
PQclear(res);
4167+
/* drain anything else pending on this command */
4168+
while ((res = PQgetResult(st->con)) != NULL)
4169+
PQclear(res);
4170+
if (canRetryError(st->estatus) ||
4171+
canContinueOnError(st->estatus))
4172+
st->state = CSTATE_ERROR;
4173+
else
4174+
st->state = CSTATE_ABORTED;
4175+
}
4176+
break;
4177+
}
4178+
40734179
/*
40744180
* Wait until sleep is done. This state is entered after a
40754181
* \sleep metacommand. The behavior is similar to
@@ -7631,6 +7737,7 @@ threadRun(void *arg)
76317737
min_usec = this_usec;
76327738
}
76337739
else if (st->state == CSTATE_WAIT_RESULT ||
7740+
st->state == CSTATE_WAIT_PREPARE_RESULT ||
76347741
st->state == CSTATE_WAIT_ROLLBACK_RESULT)
76357742
{
76367743
/*
@@ -7721,6 +7828,7 @@ threadRun(void *arg)
77217828
CState *st = &state[i];
77227829

77237830
if (st->state == CSTATE_WAIT_RESULT ||
7831+
st->state == CSTATE_WAIT_PREPARE_RESULT ||
77247832
st->state == CSTATE_WAIT_ROLLBACK_RESULT)
77257833
{
77267834
/* don't call advanceConnectionState unless data is available */

src/bin/pgbench/t/001_pgbench_with_server.pl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1231,7 +1231,6 @@ sub check_data_state
12311231
2,
12321232
[
12331233
qr{ERROR: syntax error},
1234-
qr{prepared statement .* does not exist}
12351234
],
12361235
q{-- SQL syntax error
12371236
SELECT 1 + ;

0 commit comments

Comments
 (0)